-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathlanguage_config.go
More file actions
303 lines (253 loc) · 9.24 KB
/
language_config.go
File metadata and controls
303 lines (253 loc) · 9.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package tools
import (
"fmt"
"log"
"os"
"path/filepath"
"slices"
"sort"
"strings"
codacyclient "codacy/cli-v2/codacy-client"
"codacy/cli-v2/config"
"codacy/cli-v2/constants"
"codacy/cli-v2/domain"
"codacy/cli-v2/utils/logger"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
//
// This file is responsible for building the languages-config.yaml file.
//
// buildToolLanguageInfoFromAPI builds tool language information from API data
// This is the core shared logic used by both GetToolLanguageMappingFromAPI and buildToolLanguageConfigFromAPI
func buildToolLanguageInfoFromAPI() (map[string]domain.ToolLanguageInfo, error) {
// Get all tools from API with their languages
allTools, err := codacyclient.GetToolsVersions()
if err != nil {
return nil, fmt.Errorf("failed to get tools from API: %w", err)
}
// Get language file extensions from API
languageTools, err := codacyclient.GetLanguageTools()
if err != nil {
return nil, fmt.Errorf("failed to get language tools from API: %w", err)
}
// Create map of language name to file extensions
languageExtensionsMap := make(map[string][]string)
for _, langTool := range languageTools {
languageExtensionsMap[strings.ToLower(langTool.Name)] = langTool.FileExtensions
}
// Build tool language configurations from API data
result := make(map[string]domain.ToolLanguageInfo)
supportedToolNames := make(map[string]bool)
// Get supported tool names from metadata
for _, meta := range domain.SupportedToolsMetadata {
supportedToolNames[meta.Name] = true
}
// Group tools by name and keep only supported ones
toolsByName := make(map[string]domain.Tool)
for _, tool := range allTools {
if supportedToolNames[strings.ToLower(tool.ShortName)] {
// Keep the tool with latest version (first one in the response)
if _, exists := toolsByName[strings.ToLower(tool.ShortName)]; !exists {
toolsByName[strings.ToLower(tool.ShortName)] = tool
}
}
}
// Build configuration for each supported tool
for toolName, tool := range toolsByName {
configTool := domain.ToolLanguageInfo{
Name: toolName,
Languages: tool.Languages,
Extensions: []string{},
}
// Build extensions from API language data
extensionsSet := make(map[string]struct{})
for _, apiLang := range tool.Languages {
lowerLang := strings.ToLower(apiLang)
if extensions, exists := languageExtensionsMap[lowerLang]; exists {
for _, ext := range extensions {
extensionsSet[ext] = struct{}{}
}
}
}
// Convert set to sorted slice
for ext := range extensionsSet {
configTool.Extensions = append(configTool.Extensions, ext)
}
slices.Sort(configTool.Extensions)
// Sort languages alphabetically
slices.Sort(configTool.Languages)
result[toolName] = configTool
}
return result, nil
}
// GetToolLanguageMappingFromAPI gets the tool language mapping from the public API
//
// TODO: cache this with TTL time
func GetToolLanguageMappingFromAPI() (map[string]domain.ToolLanguageInfo, error) {
return buildToolLanguageInfoFromAPI()
}
// GetDefaultToolLanguageMapping returns the default mapping of tools to their supported languages and file extensions
// This function now uses the public API instead of hardcoded mappings.
func GetDefaultToolLanguageMapping() map[string]domain.ToolLanguageInfo {
// Try to get the mapping from API, fallback to hardcoded only if API fails
apiMapping, err := GetToolLanguageMappingFromAPI()
if err != nil {
logger.Error("Failed to get tool language mapping from API", logrus.Fields{
"error": err,
})
// print fatal error and exit
log.Fatalf("Failed to get tool language mapping from API: %v", err)
}
return apiMapping
}
// buildToolLanguageConfigFromAPI builds tool language configuration using only API data
func buildToolLanguageConfigFromAPI() ([]domain.ToolLanguageInfo, error) {
// Use the shared logic to get tool info map
toolInfoMap, err := buildToolLanguageInfoFromAPI()
if err != nil {
return nil, err
}
// Convert map to slice
var configTools []domain.ToolLanguageInfo
for _, toolInfo := range toolInfoMap {
configTools = append(configTools, toolInfo)
}
// Sort tools by name for consistent output
sort.Slice(configTools, func(i, j int) bool {
return configTools[i].Name < configTools[j].Name
})
return configTools, nil
}
// BuildLanguagesConfigFromAPI builds the tool language configuration from API data
func BuildLanguagesConfigFromAPI() ([]domain.ToolLanguageInfo, error) {
return buildToolLanguageConfigFromAPI()
}
// CreateLanguagesConfigFile creates languages-config.yaml based on API response
func CreateLanguagesConfigFile(apiTools []domain.Tool, toolsConfigDir string, toolIDMap map[string]string, initFlags domain.InitFlags) error {
// Check if we're in remote mode
currentCliMode, err := config.Config.GetCliMode()
if err != nil {
// If we can't determine mode, default to local behavior
currentCliMode = "local"
}
isRemoteMode := currentCliMode == "remote"
var configTools []domain.ToolLanguageInfo
if isRemoteMode {
// Remote mode: Use GetRepositoryLanguages as the single source of truth
configTools, err = buildRemoteModeLanguagesConfig(apiTools, toolIDMap, initFlags)
if err != nil {
return fmt.Errorf("failed to build remote mode languages config: %w", err)
}
} else {
// Local mode: Show all supported tools
configTools, err = buildToolLanguageConfigFromAPI()
if err != nil {
return fmt.Errorf("failed to build local mode languages config: %w", err)
}
}
// Sort tools by name for consistent output
sort.Slice(configTools, func(i, j int) bool {
return configTools[i].Name < configTools[j].Name
})
// Create the config structure
config := domain.LanguagesConfig{
Tools: configTools,
}
// Marshal to YAML
data, err := yaml.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal languages config to YAML: %w", err)
}
// Write the file
configPath := filepath.Join(toolsConfigDir, "languages-config.yaml")
if err := os.WriteFile(configPath, data, constants.DefaultFilePerms); err != nil {
return fmt.Errorf("failed to write languages config file: %w", err)
}
fmt.Println("Created languages configuration file based on API data")
return nil
}
// buildRemoteModeLanguagesConfig builds the languages config for remote mode using repository languages as source of truth
func buildRemoteModeLanguagesConfig(apiTools []domain.Tool, toolIDMap map[string]string, initFlags domain.InitFlags) ([]domain.ToolLanguageInfo, error) {
// Get language file extensions from API
languageTools, err := codacyclient.GetLanguageTools()
if err != nil {
return nil, fmt.Errorf("failed to get language tools from API: %w", err)
}
// Create map of language name to file extensions
languageExtensionsMap := make(map[string][]string)
for _, langTool := range languageTools {
languageExtensionsMap[strings.ToLower(langTool.Name)] = langTool.FileExtensions
}
// Get repository languages - this is the single source of truth for remote mode
repositoryLanguages, err := getRepositoryLanguages(initFlags)
if err != nil {
return nil, fmt.Errorf("failed to get repository languages: %w", err)
}
var configTools []domain.ToolLanguageInfo
for _, tool := range apiTools {
shortName, exists := toolIDMap[tool.Uuid]
if !exists {
// Skip tools we don't recognize
continue
}
configTool := domain.ToolLanguageInfo{
Name: shortName,
Languages: []string{},
Extensions: []string{},
}
// Use only languages that exist in the repository
extensionsSet := make(map[string]struct{})
for _, lang := range tool.Languages {
lowerLang := strings.ToLower(lang)
if repoExts, exists := repositoryLanguages[lowerLang]; exists && len(repoExts) > 0 {
configTool.Languages = append(configTool.Languages, lang)
// Add repository-specific extensions
for _, ext := range repoExts {
extensionsSet[ext] = struct{}{}
}
}
}
// Convert extensions set to sorted slice
for ext := range extensionsSet {
configTool.Extensions = append(configTool.Extensions, ext)
}
slices.Sort(configTool.Extensions)
// Sort languages alphabetically
slices.Sort(configTool.Languages)
// Add the tool (even if it has no languages - this is what repository configured)
configTools = append(configTools, configTool)
}
return configTools, nil
}
func getRepositoryLanguages(initFlags domain.InitFlags) (map[string][]string, error) {
response, err := codacyclient.GetRepositoryLanguages(initFlags)
if err != nil {
return nil, fmt.Errorf("failed to get repository languages: %w", err)
}
// Create map to store language name -> combined extensions
result := make(map[string][]string)
// Filter and process languages
for _, lang := range response {
if lang.Enabled && lang.Detected {
// Combine and deduplicate extensions
extensions := make(map[string]struct{})
for _, ext := range lang.CodacyDefaults {
extensions[ext] = struct{}{}
}
for _, ext := range lang.Extensions {
extensions[ext] = struct{}{}
}
// Convert map to slice
extSlice := make([]string, 0, len(extensions))
for ext := range extensions {
extSlice = append(extSlice, ext)
}
// Sort extensions for consistent ordering in the config file
slices.Sort(extSlice)
// Add to result map with lowercase key for case-insensitive matching
result[strings.ToLower(lang.Name)] = extSlice
}
}
return result, nil
}