streamdown_syntax/
languages.rs

1//! Language alias mapping.
2//!
3//! Maps common language aliases to syntect syntax names.
4//! This handles cases like "py" → "Python", "js" → "JavaScript", etc.
5
6use std::collections::HashMap;
7use std::sync::LazyLock;
8
9/// Static mapping of language aliases.
10///
11/// Each entry maps a lowercase alias to the canonical syntect syntax name.
12pub static LANGUAGE_ALIASES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
13    let mut m = HashMap::new();
14
15    // Python
16    m.insert("python", "Python");
17    m.insert("py", "Python");
18    m.insert("python3", "Python");
19    m.insert("py3", "Python");
20
21    // JavaScript
22    m.insert("javascript", "JavaScript");
23    m.insert("js", "JavaScript");
24    m.insert("node", "JavaScript");
25    m.insert("nodejs", "JavaScript");
26
27    // TypeScript
28    m.insert("typescript", "TypeScript");
29    m.insert("ts", "TypeScript");
30
31    // Rust
32    m.insert("rust", "Rust");
33    m.insert("rs", "Rust");
34
35    // Shell/Bash
36    m.insert("bash", "Bourne Again Shell (bash)");
37    m.insert("sh", "Bourne Again Shell (bash)");
38    m.insert("shell", "Bourne Again Shell (bash)");
39    m.insert("zsh", "Bourne Again Shell (bash)");
40    m.insert("fish", "Bourne Again Shell (bash)");
41
42    // C
43    m.insert("c", "C");
44    m.insert("h", "C");
45
46    // C++
47    m.insert("cpp", "C++");
48    m.insert("c++", "C++");
49    m.insert("cxx", "C++");
50    m.insert("hpp", "C++");
51    m.insert("hxx", "C++");
52
53    // C#
54    m.insert("csharp", "C#");
55    m.insert("cs", "C#");
56
57    // Go
58    m.insert("go", "Go");
59    m.insert("golang", "Go");
60
61    // Java
62    m.insert("java", "Java");
63
64    // Kotlin
65    m.insert("kotlin", "Kotlin");
66    m.insert("kt", "Kotlin");
67
68    // Swift
69    m.insert("swift", "Swift");
70
71    // Ruby
72    m.insert("ruby", "Ruby");
73    m.insert("rb", "Ruby");
74
75    // PHP
76    m.insert("php", "PHP");
77
78    // Perl
79    m.insert("perl", "Perl");
80    m.insert("pl", "Perl");
81
82    // Lua
83    m.insert("lua", "Lua");
84
85    // R
86    m.insert("r", "R");
87
88    // Scala
89    m.insert("scala", "Scala");
90
91    // Haskell
92    m.insert("haskell", "Haskell");
93    m.insert("hs", "Haskell");
94
95    // OCaml
96    m.insert("ocaml", "OCaml");
97    m.insert("ml", "OCaml");
98
99    // F#
100    m.insert("fsharp", "F#");
101    m.insert("fs", "F#");
102
103    // Elixir
104    m.insert("elixir", "Elixir");
105    m.insert("ex", "Elixir");
106    m.insert("exs", "Elixir");
107
108    // Erlang
109    m.insert("erlang", "Erlang");
110    m.insert("erl", "Erlang");
111
112    // Clojure
113    m.insert("clojure", "Clojure");
114    m.insert("clj", "Clojure");
115
116    // SQL
117    m.insert("sql", "SQL");
118    m.insert("mysql", "SQL");
119    m.insert("postgresql", "SQL");
120    m.insert("postgres", "SQL");
121    m.insert("sqlite", "SQL");
122
123    // HTML
124    m.insert("html", "HTML");
125    m.insert("htm", "HTML");
126    m.insert("xhtml", "HTML");
127
128    // CSS
129    m.insert("css", "CSS");
130    m.insert("scss", "SCSS");
131    m.insert("sass", "Sass");
132    m.insert("less", "Less");
133
134    // JSON
135    m.insert("json", "JSON");
136    m.insert("jsonc", "JSON");
137
138    // YAML
139    m.insert("yaml", "YAML");
140    m.insert("yml", "YAML");
141
142    // TOML
143    m.insert("toml", "TOML");
144
145    // XML
146    m.insert("xml", "XML");
147    m.insert("xsl", "XML");
148    m.insert("xslt", "XML");
149    m.insert("svg", "XML");
150
151    // Markdown
152    m.insert("markdown", "Markdown");
153    m.insert("md", "Markdown");
154    m.insert("mdown", "Markdown");
155
156    // LaTeX
157    m.insert("latex", "LaTeX");
158    m.insert("tex", "TeX");
159
160    // Makefile
161    m.insert("makefile", "Makefile");
162    m.insert("make", "Makefile");
163    m.insert("mk", "Makefile");
164
165    // Docker
166    m.insert("dockerfile", "Dockerfile");
167    m.insert("docker", "Dockerfile");
168
169    // Nginx
170    m.insert("nginx", "nginx");
171
172    // INI
173    m.insert("ini", "INI");
174    m.insert("conf", "INI");
175    m.insert("cfg", "INI");
176
177    // Diff
178    m.insert("diff", "Diff");
179    m.insert("patch", "Diff");
180
181    // Git
182    m.insert("git", "Git Commit");
183    m.insert("gitcommit", "Git Commit");
184    m.insert("gitignore", "Git Ignore");
185
186    // Lisp
187    m.insert("lisp", "Lisp");
188    m.insert("elisp", "Lisp");
189    m.insert("emacs-lisp", "Lisp");
190    m.insert("commonlisp", "Lisp");
191    m.insert("cl", "Lisp");
192
193    // Scheme
194    m.insert("scheme", "Scheme");
195    m.insert("racket", "Scheme");
196
197    // Dart
198    m.insert("dart", "Dart");
199
200    // Vue
201    m.insert("vue", "Vue Component");
202
203    // GraphQL
204    m.insert("graphql", "GraphQL");
205    m.insert("gql", "GraphQL");
206
207    // Protocol Buffers
208    m.insert("protobuf", "Protocol Buffers");
209    m.insert("proto", "Protocol Buffers");
210
211    // Terraform
212    m.insert("terraform", "Terraform");
213    m.insert("tf", "Terraform");
214    m.insert("hcl", "Terraform");
215
216    // Assembly
217    m.insert("asm", "Assembly (x86_64)");
218    m.insert("assembly", "Assembly (x86_64)");
219    m.insert("nasm", "Assembly (x86_64)");
220
221    // Plain text
222    m.insert("text", "Plain Text");
223    m.insert("txt", "Plain Text");
224    m.insert("plain", "Plain Text");
225
226    // Objective-C
227    m.insert("objc", "Objective-C");
228    m.insert("objective-c", "Objective-C");
229    m.insert("objectivec", "Objective-C");
230
231    // Objective-C++
232    m.insert("objcpp", "Objective-C++");
233    m.insert("objective-c++", "Objective-C++");
234
235    // Pascal/Delphi
236    m.insert("pascal", "Pascal");
237    m.insert("delphi", "Pascal");
238
239    // Groovy
240    m.insert("groovy", "Groovy");
241
242    // PowerShell
243    m.insert("powershell", "PowerShell");
244    m.insert("ps1", "PowerShell");
245    m.insert("pwsh", "PowerShell");
246
247    // Batch/CMD
248    m.insert("batch", "Batch File");
249    m.insert("bat", "Batch File");
250    m.insert("cmd", "Batch File");
251
252    // Regular Expressions
253    m.insert("regex", "Regular Expression");
254    m.insert("regexp", "Regular Expression");
255
256    // AppleScript
257    m.insert("applescript", "AppleScript");
258
259    // JSX/TSX
260    m.insert("jsx", "JavaScript (Babel)");
261    m.insert("tsx", "TypeScript");
262
263    // CoffeeScript
264    m.insert("coffeescript", "CoffeeScript");
265    m.insert("coffee", "CoffeeScript");
266
267    // D
268    m.insert("d", "D");
269    m.insert("dlang", "D");
270
271    // Nim
272    m.insert("nim", "Nim");
273    m.insert("nimrod", "Nim");
274
275    // Zig
276    m.insert("zig", "Zig");
277
278    // Crystal
279    m.insert("crystal", "Crystal");
280    m.insert("cr", "Crystal");
281
282    // Julia
283    m.insert("julia", "Julia");
284    m.insert("jl", "Julia");
285
286    // Solidity
287    m.insert("solidity", "Solidity");
288    m.insert("sol", "Solidity");
289
290    // Vyper
291    m.insert("vyper", "Vyper");
292    m.insert("vy", "Vyper");
293
294    // Fortran
295    m.insert("fortran", "Fortran (Modern)");
296    m.insert("f90", "Fortran (Modern)");
297    m.insert("f95", "Fortran (Modern)");
298    m.insert("f03", "Fortran (Modern)");
299
300    // COBOL
301    m.insert("cobol", "COBOL");
302    m.insert("cob", "COBOL");
303
304    // ActionScript
305    m.insert("actionscript", "ActionScript");
306    m.insert("as", "ActionScript");
307
308    // Handlebars
309    m.insert("handlebars", "Handlebars");
310    m.insert("hbs", "Handlebars");
311    m.insert("mustache", "Handlebars");
312
313    // Jinja
314    m.insert("jinja", "Jinja");
315    m.insert("jinja2", "Jinja");
316
317    // Puppet
318    m.insert("puppet", "Puppet");
319    m.insert("pp", "Puppet");
320
321    // ReStructuredText
322    m.insert("rst", "reStructuredText");
323    m.insert("restructuredtext", "reStructuredText");
324    m.insert("rest", "reStructuredText");
325
326    // AsciiDoc
327    m.insert("asciidoc", "AsciiDoc");
328    m.insert("adoc", "AsciiDoc");
329
330    // Org Mode
331    m.insert("org", "orgmode");
332    m.insert("orgmode", "orgmode");
333
334    m
335});
336
337/// Look up the canonical syntax name for a language alias.
338///
339/// Returns the canonical name if found, or the original input if not.
340///
341/// # Example
342/// ```
343/// use streamdown_syntax::language_alias;
344///
345/// assert_eq!(language_alias("py"), "Python");
346/// assert_eq!(language_alias("js"), "JavaScript");
347/// assert_eq!(language_alias("rust"), "Rust");
348/// assert_eq!(language_alias("unknown"), "unknown"); // Returns original
349/// ```
350pub fn language_alias(name: &str) -> &str {
351    let lower = name.to_lowercase();
352    LANGUAGE_ALIASES
353        .get(lower.as_str())
354        .copied()
355        .unwrap_or(name)
356}
357
358/// Get all known language aliases.
359///
360/// Returns an iterator over (alias, canonical_name) pairs.
361pub fn all_aliases() -> impl Iterator<Item = (&'static str, &'static str)> {
362    LANGUAGE_ALIASES.iter().map(|(k, v)| (*k, *v))
363}
364
365/// Get all aliases that map to a specific syntax name.
366///
367/// # Example
368/// ```
369/// use streamdown_syntax::aliases_for;
370///
371/// let python_aliases = aliases_for("Python");
372/// assert!(python_aliases.contains(&"py"));
373/// assert!(python_aliases.contains(&"python"));
374/// ```
375pub fn aliases_for(syntax_name: &str) -> Vec<&'static str> {
376    LANGUAGE_ALIASES
377        .iter()
378        .filter_map(|(alias, name)| {
379            if *name == syntax_name {
380                Some(*alias)
381            } else {
382                None
383            }
384        })
385        .collect()
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn test_python_aliases() {
394        assert_eq!(language_alias("python"), "Python");
395        assert_eq!(language_alias("py"), "Python");
396        assert_eq!(language_alias("Python"), "Python"); // case insensitive
397        assert_eq!(language_alias("PY"), "Python");
398    }
399
400    #[test]
401    fn test_javascript_aliases() {
402        assert_eq!(language_alias("javascript"), "JavaScript");
403        assert_eq!(language_alias("js"), "JavaScript");
404        assert_eq!(language_alias("node"), "JavaScript");
405    }
406
407    #[test]
408    fn test_rust_aliases() {
409        assert_eq!(language_alias("rust"), "Rust");
410        assert_eq!(language_alias("rs"), "Rust");
411    }
412
413    #[test]
414    fn test_shell_aliases() {
415        let expected = "Bourne Again Shell (bash)";
416        assert_eq!(language_alias("bash"), expected);
417        assert_eq!(language_alias("sh"), expected);
418        assert_eq!(language_alias("shell"), expected);
419        assert_eq!(language_alias("zsh"), expected);
420    }
421
422    #[test]
423    fn test_unknown_returns_original() {
424        assert_eq!(language_alias("unknown-lang"), "unknown-lang");
425        assert_eq!(language_alias("foo"), "foo");
426    }
427
428    #[test]
429    fn test_aliases_for() {
430        let python_aliases = aliases_for("Python");
431        assert!(python_aliases.contains(&"py"));
432        assert!(python_aliases.contains(&"python"));
433        assert!(python_aliases.contains(&"python3"));
434    }
435
436    #[test]
437    fn test_case_insensitivity() {
438        assert_eq!(language_alias("PYTHON"), "Python");
439        assert_eq!(language_alias("JavaScript"), "JavaScript");
440        assert_eq!(language_alias("RUST"), "Rust");
441    }
442
443    #[test]
444    fn test_all_aliases_not_empty() {
445        let count = all_aliases().count();
446        assert!(count > 100); // We have lots of aliases
447    }
448}