Krolyxon
Commit ec28036

ferreniumtoolsgen-languagesgen.py

169 lines · 5.1 KB · Python

1#!/usr/bin/env python3
2"""Generate src/index/languages_table.rs from GitHub Linguist's data.
3
4Dev-only; the output is committed. Needs PyYAML.
5
6 tools/gen-languages/gen.py [VERSION] # default: the pinned version
7
8Downloads languages.yml, vendor.yml and documentation.yml for VERSION and
9writes the table. Re-run when bumping Linguist.
10"""
11
12import pathlib
13import sys
14import urllib.request
15
16import yaml
17
18VERSION = sys.argv[1] if len(sys.argv) > 1 else "v9.7.0"
19BASE = f"https://raw.githubusercontent.com/github-linguist/linguist/{VERSION}/lib/linguist/"
20OUT = pathlib.Path(__file__).resolve().parents[2] / "src/index/languages_table.rs"
21
22# Extensions claimed by several languages, settled by hand (Linguist uses
23# content heuristics we don't run). Everything else: see `pick`.
24OVERRIDES = {
25 ".h": "C",
26 ".m": "Objective-C",
27 ".pl": "Perl",
28 ".pm": "Perl",
29 ".t": "Perl",
30 ".md": "Markdown",
31 ".ts": "TypeScript",
32 ".sql": "SQL",
33 ".inc": "PHP",
34 ".v": "Verilog",
35 ".fs": "F#",
36 ".r": "R",
37 ".d": "D",
38 ".cls": "TeX",
39 ".pro": "Prolog",
40 ".s": "Unix Assembly",
41 ".asm": "Assembly",
42 ".l": "Lex",
43 ".ms": "Roff",
44 ".bf": "Brainfuck",
45 ".cs": "C#",
46 ".rs": "Rust",
47 ".html": "HTML",
48 ".txt": "Text",
49 ".json": "JSON",
50 ".yml": "YAML",
51 ".xml": "XML",
52 ".sh": "Shell",
53 ".mod": "Modula-2",
54 ".gs": "JavaScript",
55 ".ex": "Elixir",
56 ".nl": "NewLisp",
57 ".lisp": "Common Lisp",
58 ".cl": "Common Lisp",
59 ".tst": "Scilab",
60 ".x": "Logos",
61 ".b": "Brainfuck",
62 ".n": "Roff",
63 ".i": "SWIG",
64 ".tpl": "Smarty",
65 ".sc": "Scala",
66 ".st": "Smalltalk",
67 ".frm": "VBA",
68 ".bas": "BASIC",
69 ".fx": "HLSL",
70 ".mask": "Unity3D Asset",
71 ".plt": "Gnuplot",
72 ".ncl": "NCL",
73 ".lsp": "Common Lisp",
74 ".brd": "Eagle",
75 ".mdx": "MDX",
76 ".cake": "C#",
77 ".builds": "XML",
78 ".ice": "JSON",
79 ".rpy": "Ren'Py",
80 ".sol": "Solidity",
81 ".yaml": "YAML",
82 ".cfg": "INI",
83 ".f": "Fortran",
84 ".es": "JavaScript",
85}
86
87KIND_RANK = {"programming": 0, "markup": 1, "data": 2, "prose": 3}
88
89
90def fetch(name):
91 with urllib.request.urlopen(BASE + name) as res:
92 return yaml.safe_load(res.read())
93
94
95def rust_str(s):
96 return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
97
98
99def main():
100 languages = fetch("languages.yml")
101 vendor = fetch("vendor.yml")
102 documentation = fetch("documentation.yml")
103
104 names = sorted(languages)
105 index = {name: i for i, name in enumerate(names)}
106
107 def pick(ext, candidates):
108 if ext in OVERRIDES and OVERRIDES[ext] in candidates:
109 return OVERRIDES[ext]
110 primary = [c for c in candidates if languages[c].get("extensions", [None])[0] == ext]
111 pool = primary or candidates
112 return min(pool, key=lambda c: (KIND_RANK.get(languages[c]["type"], 9), c))
113
114 by_ext = {}
115 by_name = {}
116 for name in names:
117 lang = languages[name]
118 for ext in lang.get("extensions", []):
119 by_ext.setdefault(ext.lower(), []).append(name)
120 for filename in lang.get("filenames", []):
121 by_name.setdefault(filename, []).append(name)
122
123 extensions = sorted((ext, index[pick(ext, c)]) for ext, c in by_ext.items())
124 filenames = sorted(
125 (f, index[min(c, key=lambda n: (KIND_RANK.get(languages[n]["type"], 9), n))])
126 for f, c in by_name.items()
127 )
128
129 out = []
130 w = out.append
131 w(f"// @generated by tools/gen-languages from GitHub Linguist {VERSION}. Do not edit.\n")
132 w("// Linguist is MIT licensed: https://github.com/github-linguist/linguist\n\n")
133 w("use super::languages::{Kind, Language};\n\n")
134 w(f"pub const LINGUIST_VERSION: &str = {rust_str(VERSION)};\n\n")
135 w("/// Every language; `group` folds a language into its parent for statistics.\n")
136 w("pub static LANGUAGES: &[Language] = &[\n")
137 for name in names:
138 lang = languages[name]
139 kind = lang["type"].capitalize()
140 group = lang.get("group")
141 group = f"Some({index[group]})" if group in index else "None"
142 w(f" Language {{ name: {rust_str(name)}, kind: Kind::{kind}, group: {group} }},\n")
143 w("];\n\n")
144 w("/// Lowercase extensions (with the dot), sorted, to an index into `LANGUAGES`.\n")
145 w("pub static EXTENSIONS: &[(&str, u16)] = &[\n")
146 for ext, i in extensions:
147 w(f" ({rust_str(ext)}, {i}),\n")
148 w("];\n\n")
149 w("/// Exact file names, sorted, to an index into `LANGUAGES`.\n")
150 w("pub static FILENAMES: &[(&str, u16)] = &[\n")
151 for f, i in filenames:
152 w(f" ({rust_str(f)}, {i}),\n")
153 w("];\n\n")
154 for title, const, patterns in [
155 ("Paths of vendored code (regular expressions).", "VENDOR", vendor),
156 ("Paths of documentation (regular expressions).", "DOCUMENTATION", documentation),
157 ]:
158 w(f"/// {title}\n")
159 w(f"pub static {const}: &[&str] = &[\n")
160 for p in patterns:
161 w(f" {rust_str(p)},\n")
162 w("];\n\n")
163 OUT.write_text("".join(out).rstrip() + "\n")
164 print(f"wrote {OUT} ({len(names)} languages, {len(extensions)} extensions, "
165 f"{len(filenames)} file names)")
166
167
168if __name__ == "__main__":
169 main()