Skip to main content

semtree_core/
language.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4// `Ord` follows declaration order, which is what stable, grouped output (stats,
5// help text) sorts by when counts tie.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum Language {
9    Rust,
10    Python,
11    JavaScript,
12    TypeScript,
13    Tsx,
14    Go,
15    Java,
16    C,
17    Cpp,
18    CSharp,
19    Ruby,
20    Php,
21    Kotlin,
22    Scala,
23    Swift,
24    OCaml,
25    Solidity,
26    Lua,
27    Zig,
28    Elisp,
29    Unknown,
30}
31
32impl Language {
33    pub fn from_extension(ext: &str) -> Self {
34        match ext {
35            "rs" => Self::Rust,
36            "py" => Self::Python,
37            "js" | "mjs" | "cjs" | "jsx" => Self::JavaScript,
38            "ts" => Self::TypeScript,
39            "tsx" => Self::Tsx,
40            "go" => Self::Go,
41            "java" => Self::Java,
42            "c" | "h" => Self::C,
43            "cpp" | "cc" | "cxx" | "hpp" | "hh" | "hxx" => Self::Cpp,
44            "cs" => Self::CSharp,
45            "rb" => Self::Ruby,
46            "php" => Self::Php,
47            "kt" | "kts" => Self::Kotlin,
48            "scala" | "sc" => Self::Scala,
49            "swift" => Self::Swift,
50            "ml" | "mli" => Self::OCaml,
51            "sol" => Self::Solidity,
52            "lua" => Self::Lua,
53            "zig" => Self::Zig,
54            "el" => Self::Elisp,
55            _ => Self::Unknown,
56        }
57    }
58
59    pub fn from_path(path: &std::path::Path) -> Self {
60        path.extension()
61            .and_then(|e| e.to_str())
62            .map(Self::from_extension)
63            .unwrap_or(Self::Unknown)
64    }
65
66    /// Parse a user-facing language name, as accepted by a `--lang` filter.
67    ///
68    /// Case-insensitive, and takes the common short forms (`rs`, `py`, `ts`)
69    /// alongside the canonical name [`Display`](std::fmt::Display) prints.
70    /// Returns `None` for anything unrecognized so a caller can report a typo
71    /// rather than silently filtering everything out.
72    pub fn from_name(name: &str) -> Option<Self> {
73        Some(match name.to_lowercase().as_str() {
74            "rust" | "rs" => Self::Rust,
75            "python" | "py" => Self::Python,
76            "javascript" | "js" => Self::JavaScript,
77            "typescript" | "ts" => Self::TypeScript,
78            "tsx" => Self::Tsx,
79            "go" | "golang" => Self::Go,
80            "java" => Self::Java,
81            "c" => Self::C,
82            "cpp" | "c++" => Self::Cpp,
83            "csharp" | "c#" | "cs" => Self::CSharp,
84            "ruby" | "rb" => Self::Ruby,
85            "php" => Self::Php,
86            "kotlin" | "kt" => Self::Kotlin,
87            "scala" => Self::Scala,
88            "swift" => Self::Swift,
89            "ocaml" | "ml" => Self::OCaml,
90            "solidity" | "sol" => Self::Solidity,
91            "lua" => Self::Lua,
92            "zig" => Self::Zig,
93            "elisp" | "emacs-lisp" | "el" => Self::Elisp,
94            _ => return None,
95        })
96    }
97
98    /// Every language semtree can parse, in a stable order. `Unknown` is not a
99    /// language, so it is absent; use it to build help text and error messages.
100    pub const ALL: &'static [Self] = &[
101        Self::Rust,
102        Self::Python,
103        Self::JavaScript,
104        Self::TypeScript,
105        Self::Tsx,
106        Self::Go,
107        Self::Java,
108        Self::C,
109        Self::Cpp,
110        Self::CSharp,
111        Self::Ruby,
112        Self::Php,
113        Self::Kotlin,
114        Self::Scala,
115        Self::Swift,
116        Self::OCaml,
117        Self::Solidity,
118        Self::Lua,
119        Self::Zig,
120        Self::Elisp,
121    ];
122}
123
124impl fmt::Display for Language {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        let s = match self {
127            Self::Rust => "rust",
128            Self::Python => "python",
129            Self::JavaScript => "javascript",
130            Self::TypeScript => "typescript",
131            Self::Tsx => "tsx",
132            Self::Go => "go",
133            Self::Java => "java",
134            Self::C => "c",
135            Self::Cpp => "cpp",
136            Self::CSharp => "csharp",
137            Self::Ruby => "ruby",
138            Self::Php => "php",
139            Self::Kotlin => "kotlin",
140            Self::Scala => "scala",
141            Self::Swift => "swift",
142            Self::OCaml => "ocaml",
143            Self::Solidity => "solidity",
144            Self::Lua => "lua",
145            Self::Zig => "zig",
146            Self::Elisp => "elisp",
147            Self::Unknown => "unknown",
148        };
149        write!(f, "{s}")
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn every_language_parses_back_from_its_own_name() {
159        // Guards the filter path: adding a language to the parser without
160        // teaching `from_name` about it would make `--lang <that language>`
161        // silently unusable.
162        for lang in Language::ALL {
163            assert_eq!(
164                Language::from_name(&lang.to_string()),
165                Some(*lang),
166                "{lang} does not round-trip through from_name"
167            );
168        }
169    }
170
171    #[test]
172    fn names_are_case_insensitive_and_accept_short_forms() {
173        assert_eq!(Language::from_name("Rust"), Some(Language::Rust));
174        assert_eq!(Language::from_name("PY"), Some(Language::Python));
175        assert_eq!(Language::from_name("c++"), Some(Language::Cpp));
176        assert_eq!(Language::from_name("emacs-lisp"), Some(Language::Elisp));
177    }
178
179    #[test]
180    fn unknown_is_not_a_filterable_language() {
181        assert_eq!(Language::from_name("nope"), None);
182        assert_eq!(Language::from_name("unknown"), None);
183        assert!(!Language::ALL.contains(&Language::Unknown));
184    }
185}