1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[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 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 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 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}