1package main
2
3import (
4 "io"
5 "log"
6 "runtime"
7 "sort"
8 "sync"
9 "time"
10
11 "github.com/go-enry/go-enry/v2"
12 "github.com/go-git/go-git/v5/plumbing/object"
13)
14
15var languageColors = map[string]string{
16 "1C Enterprise": "#814CCC",
17 "ActionScript": "#882B0F",
18 "Ada": "#02f88c",
19 "Agda": "#315665",
20 "AGS Script": "#B9D9FF",
21 "Alloy": "#64C800",
22 "AMPL": "#E6EFBB",
23 "Ant Build System": "#A9157E",
24 "ANTLR": "#9DC3FF",
25 "ApacheConf": "#d12127",
26 "Apex": "#1797c0",
27 "API Blueprint": "#2ACCA8",
28 "APL": "#5A8164",
29 "AppleScript": "#101F1F",
30 "Arc": "#aa2afe",
31 "Arduino": "#bd7911",
32 "ASP": "#6a40fd",
33 "AspectJ": "#a957b0",
34 "Assembly": "#6E4C13",
35 "ATS": "#1ac620",
36 "Augeas": "#9CC134",
37 "AutoHotkey": "#6594b9",
38 "AutoIt": "#1C3552",
39 "Awk": "#c30e9b",
40 "Ballerina": "#FF5000",
41 "Batchfile": "#C1F12E",
42 "Befunge": "#2F2530",
43 "Bicep": "#519aba",
44 "Bison": "#6A463F",
45 "BitBake": "#00bce4",
46 "Blade": "#f7523f",
47 "BlitzBasic": "#00FFAE",
48 "BlitzMax": "#cd6400",
49 "Bluespec": "#12223c",
50 "Boo": "#d4bec1",
51 "Brainfuck": "#2F2530",
52 "Brightscript": "#662D91",
53 "C": "#555555",
54 "C#": "#178600",
55 "C++": "#f34b7d",
56 "C3": "#2563eb",
57 "Caddyfile": "#22b638",
58 "Cairo": "#ff4a48",
59 "Ceylon": "#dfa535",
60 "Chapel": "#8dc63f",
61 "ChucK": "#3f8000",
62 "Cirru": "#ccccff",
63 "Clarion": "#db901e",
64 "Clean": "#3F85AF",
65 "Click": "#E4E6F3",
66 "CLIPS": "#00A300",
67 "Clojure": "#db5855",
68 "CMake": "#DA3434",
69 "COBOL": "#1d2021",
70 "CodeQL": "#140f46",
71 "CoffeeScript": "#244776",
72 "ColdFusion": "#ed2cd6",
73 "Common Lisp": "#3fb68b",
74 "Component Pascal": "#B0CE4E",
75 "Crystal": "#000100",
76 "CSON": "#244776",
77 "Csound": "#1a1a1a",
78 "CSS": "#563d7c",
79 "Cuda": "#3A4E3A",
80 "Curry": "#531242",
81 "Cycript": "#000000",
82 "Cython": "#fedf5b",
83 "D": "#ba595e",
84 "Dart": "#00B4AB",
85 "DataWeave": "#003a52",
86 "Dhall": "#dfafff",
87 "Dockerfile": "#384d54",
88 "Dogescript": "#cca760",
89 "DTrace": "#000000",
90 "Dylan": "#6c616e",
91 "E": "#ccce35",
92 "eC": "#913960",
93 "ECL": "#8a1267",
94 "Eiffel": "#4d6977",
95 "EJS": "#a91e50",
96 "Elixir": "#6e4a7e",
97 "Elm": "#60B5CC",
98 "Emacs Lisp": "#c065db",
99 "EmberScript": "#FFF4F3",
100 "EQ": "#a78649",
101 "Erlang": "#B83998",
102 "F#": "#b845fc",
103 "F*": "#572e30",
104 "Factor": "#636746",
105 "Fancy": "#7b9db4",
106 "Fantom": "#14253c",
107 "Faust": "#c37240",
108 "Fennel": "#fff3d7",
109 "fish": "#4aae47",
110 "FLUX": "#88ccff",
111 "Forth": "#341708",
112 "Fortran": "#4d41b1",
113 "FreeBASIC": "#141AC9",
114 "Frege": "#00cafe",
115 "Futhark": "#5f021f",
116 "G-code": "#D08CF2",
117 "Game Maker Language": "#71b417",
118 "GAML": "#FFC766",
119 "GAMS": "#f49a22",
120 "GAP": "#0000cc",
121 "GDScript": "#355570",
122 "Genie": "#fb855d",
123 "Genshi": "#951531",
124 "Gentoo Ebuild": "#9400ff",
125 "Gherkin": "#5B2063",
126 "Gleam": "#ffaff3",
127 "GLSL": "#5686a5",
128 "Glyph": "#c1ac7f",
129 "Gnuplot": "#f0a9f0",
130 "Go": "#00ADD8",
131 "Golo": "#88562A",
132 "Gosu": "#82937f",
133 "Grace": "#615f8b",
134 "Gradle": "#02303a",
135 "GraphQL": "#e10098",
136 "Groovy": "#4298b8",
137 "Hack": "#878787",
138 "Haml": "#ece2a9",
139 "Handlebars": "#f7931e",
140 "Harbour": "#0e60e3",
141 "Haskell": "#5e5086",
142 "Haxe": "#df7900",
143 "HCL": "#844FBA",
144 "HiveQL": "#dce200",
145 "HolyC": "#ffefaf",
146 "HTML": "#e34c26",
147 "Hy": "#7790B2",
148 "IDL": "#a3522f",
149 "Idris": "#b30000",
150 "Ignore List": "#000000",
151 "IGOR Pro": "#0000cc",
152 "Imba": "#16cec6",
153 "Inform 7": "#3d9970",
154 "INI": "#d1dbe0",
155 "Inno Setup": "#264b99",
156 "Io": "#a9188d",
157 "Ioke": "#078193",
158 "Isabelle": "#FEFE00",
159 "J": "#9EEDFF",
160 "Janet": "#0886a5",
161 "Java": "#b07219",
162 "JavaScript": "#f1e05a",
163 "Jinja": "#a52a22",
164 "Jison": "#56b3cb",
165 "Jolie": "#843179",
166 "JSON": "#292929",
167 "Jsonnet": "#0064bd",
168 "Julia": "#a270ba",
169 "Jupyter Notebook": "#DA5B0B",
170 "Just": "#384d54",
171 "Kaitai Struct": "#773b37",
172 "KCL": "#7ABABF",
173 "Kotlin": "#A97BFF",
174 "KRL": "#28430A",
175 "LabVIEW": "#fede06",
176 "Lasso": "#999999",
177 "Latte": "#f2a542",
178 "Lean": "#3d6117",
179 "Less": "#1d365d",
180 "Lex": "#DBCA00",
181 "LigoLANG": "#0e74ff",
182 "LilyPond": "#9ccc7c",
183 "Liquid": "#67b8de",
184 "LiveScript": "#499886",
185 "LLVM": "#185619",
186 "Logtalk": "#295b9a",
187 "LOLCODE": "#cc9900",
188 "LookML": "#652B81",
189 "LSL": "#3d9970",
190 "Lua": "#000080",
191 "Luau": "#00A2FF",
192 "M4": "#000000",
193 "Macaulay2": "#d8ffff",
194 "Makefile": "#427819",
195 "Markdown": "#083fa1",
196 "Marko": "#42bff2",
197 "Mask": "#f97732",
198 "MATLAB": "#e16737",
199 "Max": "#c4a79c",
200 "MAXScript": "#00a6a6",
201 "MDX": "#fcb32c",
202 "Mercury": "#ff2b2b",
203 "Meson": "#007800",
204 "Metal": "#8f14e9",
205 "MiniYAML": "#ff1111",
206 "Mint": "#02b046",
207 "Mirah": "#c7a938",
208 "Modelica": "#de1d31",
209 "Modula-2": "#10253f",
210 "Mojo": "#ff4c1f",
211 "MoonScript": "#ff4585",
212 "Move": "#4a137a",
213 "MQL4": "#62A8D6",
214 "MQL5": "#4A76B8",
215 "MTML": "#b7e1f4",
216 "Mustache": "#724b3b",
217 "Nemerle": "#3d3c6e",
218 "nesC": "#94B0C7",
219 "NetLinx": "#0aa0ff",
220 "NetLogo": "#ff6375",
221 "NewLisp": "#87AED7",
222 "Nextflow": "#3ac486",
223 "Nginx": "#009639",
224 "Nim": "#ffc200",
225 "Nit": "#009917",
226 "Nix": "#7e7eff",
227 "Nushell": "#4E9906",
228 "NWScript": "#111522",
229 "Objective-C": "#438eff",
230 "Objective-C++": "#6866fb",
231 "Objective-J": "#ff0c5a",
232 "OCaml": "#ef7a08",
233 "Odin": "#60AFFE",
234 "Omgrofl": "#cabbff",
235 "ooc": "#b0b77e",
236 "Opal": "#f7ede0",
237 "Open Policy Agent": "#7d9199",
238 "OpenCL": "#ed2e2d",
239 "OpenEdge ABL": "#5ce600",
240 "OpenQASM": "#AA70FF",
241 "OpenSCAD": "#e5cd45",
242 "Org": "#77aa99",
243 "Ox": "#000000",
244 "Oxygene": "#cdd0e3",
245 "Oz": "#fab738",
246 "P4": "#7055b5",
247 "Papyrus": "#660000",
248 "Parrot": "#f3ca0a",
249 "Pascal": "#E3F171",
250 "Pawn": "#dbb284",
251 "Pep8": "#C76F5B",
252 "Perl": "#0298c3",
253 "PHP": "#4f5d95",
254 "PicoLisp": "#6067af",
255 "PigLatin": "#fce7de",
256 "Pike": "#005390",
257 "PLpgSQL": "#336790",
258 "PLSQL": "#dad8d8",
259 "PogoScript": "#d80073",
260 "Polar": "#316880",
261 "Pony": "#000000",
262 "PostScript": "#da291c",
263 "PowerShell": "#012456",
264 "Prisma": "#0c344b",
265 "Processing": "#0096D8",
266 "Prolog": "#74283c",
267 "Promela": "#de3900",
268 "Protocol Buffer": "#000000",
269 "Pug": "#a86454",
270 "Puppet": "#302B6D",
271 "PureBasic": "#5a6986",
272 "PureScript": "#1D222D",
273 "Python": "#3572A5",
274 "QMake": "#000000",
275 "QML": "#44a51c",
276 "Qt Script": "#00b0ff",
277 "Quake": "#882303",
278 "R": "#198CE7",
279 "Racket": "#3c5caa",
280 "Ragel": "#9d5200",
281 "Raku": "#0000fb",
282 "RAML": "#77d9fb",
283 "Razor": "#512be4",
284 "Rebol": "#358a5b",
285 "Red": "#ee0000",
286 "Redcode": "#000000",
287 "Ren'Py": "#ff7f7f",
288 "RenderScript": "#000000",
289 "Rescript": "#ed4e4e",
290 "REXX": "#d90e09",
291 "Ring": "#2D54CB",
292 "Riot": "#A71E22",
293 "RMarkdown": "#198ce7",
294 "RobotFramework": "#00c0b5",
295 "Roff": "#ecdebe",
296 "Rouge": "#cc0000",
297 "Ruby": "#701516",
298 "RUNOFF": "#660000",
299 "Rust": "#dea584",
300 "Sage": "#000000",
301 "SaltStack": "#646464",
302 "SAS": "#B34936",
303 "Sass": "#a53b70",
304 "Scala": "#c22d40",
305 "Scaml": "#bd181a",
306 "Scheme": "#1e4aec",
307 "Scilab": "#ca0f21",
308 "SCSS": "#c6538c",
309 "sed": "#64b970",
310 "Self": "#0579aa",
311 "ShaderLab": "#222c37",
312 "Shell": "#89e051",
313 "Shen": "#120F14",
314 "Sieve": "#000000",
315 "Slash": "#007eff",
316 "Slice": "#003fa2",
317 "Slim": "#2b2b2b",
318 "Smali": "#000000",
319 "Smalltalk": "#596706",
320 "Smarty": "#f0c040",
321 "Smithy": "#c44536",
322 "SmPL": "#c92223",
323 "Solidity": "#AA6746",
324 "SourcePawn": "#f69e1d",
325 "SPARQL": "#0C4597",
326 "SQF": "#3F3F3F",
327 "SQL": "#e38c00",
328 "SQLPL": "#e38c00",
329 "Squirrel": "#800000",
330 "SRecode Template": "#348a34",
331 "Stan": "#b2011d",
332 "Standard ML": "#dc566d",
333 "Starlark": "#76d275",
334 "Stata": "#1a5f91",
335 "STL": "#373b3e",
336 "Stylus": "#ff6347",
337 "SuperCollider": "#46390b",
338 "Svelte": "#ff3e00",
339 "SVG": "#ff9900",
340 "Swift": "#F05138",
341 "SWIG": "#000000",
342 "SystemVerilog": "#DAE1C2",
343 "Tcl": "#e4cc98",
344 "Tcsh": "#000000",
345 "Terra": "#000000",
346 "TeX": "#3D6117",
347 "Thrift": "#D88E35",
348 "TI Program": "#A0AAAD",
349 "TLA": "#4b0082",
350 "TOML": "#9c4221",
351 "TSQL": "#e38c00",
352 "TSX": "#3178c6",
353 "Turing": "#cf142b",
354 "Turtle": "#EEFF11",
355 "Twig": "#c1d026",
356 "TXL": "#0178b8",
357 "TypeScript": "#3178c6",
358 "Typst": "#239dad",
359 "Unified Parallel C": "#4e3617",
360 "Unity3D Asset": "#222c37",
361 "Uno": "#9933cc",
362 "UnrealScript": "#a54c4d",
363 "UrWeb": "#ccc",
364 "V": "#4f87c4",
365 "Vala": "#fbe5cd",
366 "Valve Data Format": "#f26025",
367 "VBA": "#867db1",
368 "VBScript": "#15dcdc",
369 "VCL": "#148AA8",
370 "Verilog": "#b2b7f8",
371 "VHDL": "#adb2cb",
372 "Vim Help File": "#199f4b",
373 "Vim Script": "#199f4b",
374 "Visual Basic .NET": "#9400ff",
375 "Volt": "#1F1F1F",
376 "Vue": "#41b883",
377 "Vyper": "#2980b9",
378 "WDL": "#42f1f4",
379 "WebAssembly": "#04133b",
380 "WebIDL": "#000000",
381 "Whiley": "#d5c397",
382 "Wikitext": "#fc5757",
383 "Windows Registry Entries": "#52a5df",
384 "Witcher Script": "#ff0000",
385 "Wollok": "#a23738",
386 "World of Warcraft Addon Data": "#f7e43a",
387 "Wren": "#383838",
388 "X10": "#4B6BEF",
389 "xBase": "#403a40",
390 "XC": "#99FF33",
391 "XML": "#0060ac",
392 "XML Property List": "#0060ac",
393 "Xojo": "#81bd41",
394 "Xonsh": "#285880",
395 "XOTL": "#000000",
396 "XQuery": "#5232e7",
397 "XSLT": "#EB8E35",
398 "Xtend": "#24255d",
399 "Yacc": "#4B6C4B",
400 "YAML": "#cb171e",
401 "YANG": "#000000",
402 "YARA": "#220000",
403 "YASnippet": "#32AB90",
404 "ZAP": "#0d6616",
405 "Zeek": "#000000",
406 "ZenScript": "#00BCD1",
407 "Zephir": "#118f9e",
408 "Zig": "#ec915c",
409 "ZIL": "#dc75e5",
410 "Zimpl": "#d67711",
411 "Tree-sitter Query": "#9440ff",
412}
413
414func getLanguageStats(repoName string, commitHash string, tree *object.Tree) ([]LanguageStat, error) {
415 key := LangCacheKey{RepoName: repoName, CommitHash: commitHash}
416
417 if val, ok := langCache.Load(key); ok {
418 log.Printf("OPS Language stats cache hit: %s [%s]", repoName, commitHash)
419 return val.([]LanguageStat), nil
420 }
421
422 log.Printf("OPS Calculating language stats: %s [%s]", repoName, commitHash)
423 start := time.Now()
424
425 type result struct {
426 lang string
427 size int64
428 }
429
430 numWorkers := runtime.NumCPU()
431 filesChan := make(chan *object.File, numWorkers*2)
432 resultsChan := make(chan result, numWorkers*2)
433 var wg sync.WaitGroup
434
435 for i := 0; i < numWorkers; i++ {
436 wg.Add(1)
437 go func() {
438 defer wg.Done()
439 for f := range filesChan {
440 if enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) || enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
441 continue
442 }
443
444 // Fast path: detect by extension
445 lang, safe := enry.GetLanguageByExtension(f.Name)
446 if safe && lang != "" && lang != enry.OtherLanguage {
447 resultsChan <- result{lang: lang, size: f.Size}
448 continue
449 }
450
451 // Slow path: detect by content (read only first 16KB)
452 reader, err := f.Reader()
453 if err != nil {
454 continue
455 }
456
457 content := make([]byte, 16384)
458 n, err := reader.Read(content)
459 reader.Close()
460 if err != nil && err != io.EOF {
461 continue
462 }
463 content = content[:n]
464
465 if enry.IsBinary(content) {
466 continue
467 }
468
469 lang = enry.GetLanguage(f.Name, content)
470 if lang != "" && lang != enry.OtherLanguage {
471 resultsChan <- result{lang: lang, size: f.Size}
472 }
473 }
474 }()
475 }
476
477 languages := make(map[string]int64)
478 var totalSize int64
479 done := make(chan struct{})
480 go func() {
481 for res := range resultsChan {
482 languages[res.lang] += res.size
483 totalSize += res.size
484 }
485 close(done)
486 }()
487
488 err := tree.Files().ForEach(func(f *object.File) error {
489 filesChan <- f
490 return nil
491 })
492 close(filesChan)
493 wg.Wait()
494 close(resultsChan)
495 <-done
496
497 if err != nil {
498 return nil, err
499 }
500
501 var stats []LanguageStat
502 for name, size := range languages {
503 percentage := (float64(size) / float64(totalSize)) * 100
504 if percentage < 0.1 {
505 continue
506 }
507 stats = append(stats, LanguageStat{
508 Name: name,
509 Size: size,
510 Percentage: percentage,
511 Color: getLanguageColor(name),
512 })
513 }
514
515 sort.Slice(stats, func(i, j int) bool {
516 return stats[i].Size > stats[j].Size
517 })
518
519 var currentOffset float64
520 for i := range stats {
521 stats[i].Offset = currentOffset
522 currentOffset += stats[i].Percentage
523 }
524
525 langCache.Store(key, stats)
526 NotifySave()
527
528 log.Printf("OPS Language stats calculated for %s [%s] in %v", repoName, commitHash, time.Since(start))
529
530 return stats, nil
531}
532
533func getLanguageColor(lang string) string {
534 if color, ok := languageColors[lang]; ok {
535 return color
536 }
537 return "#8b8b8b"
538}