use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
Tsx,
Go,
Java,
C,
Cpp,
CSharp,
Ruby,
Php,
Kotlin,
Scala,
Swift,
OCaml,
Solidity,
Lua,
Zig,
Elisp,
Unknown,
}
impl Language {
pub fn from_extension(ext: &str) -> Self {
match ext {
"rs" => Self::Rust,
"py" => Self::Python,
"js" | "mjs" | "cjs" | "jsx" => Self::JavaScript,
"ts" => Self::TypeScript,
"tsx" => Self::Tsx,
"go" => Self::Go,
"java" => Self::Java,
"c" | "h" => Self::C,
"cpp" | "cc" | "cxx" | "hpp" | "hh" | "hxx" => Self::Cpp,
"cs" => Self::CSharp,
"rb" => Self::Ruby,
"php" => Self::Php,
"kt" | "kts" => Self::Kotlin,
"scala" | "sc" => Self::Scala,
"swift" => Self::Swift,
"ml" | "mli" => Self::OCaml,
"sol" => Self::Solidity,
"lua" => Self::Lua,
"zig" => Self::Zig,
"el" => Self::Elisp,
_ => Self::Unknown,
}
}
pub fn from_path(path: &std::path::Path) -> Self {
path.extension()
.and_then(|e| e.to_str())
.map(Self::from_extension)
.unwrap_or(Self::Unknown)
}
pub fn from_name(name: &str) -> Option<Self> {
Some(match name.to_lowercase().as_str() {
"rust" | "rs" => Self::Rust,
"python" | "py" => Self::Python,
"javascript" | "js" => Self::JavaScript,
"typescript" | "ts" => Self::TypeScript,
"tsx" => Self::Tsx,
"go" | "golang" => Self::Go,
"java" => Self::Java,
"c" => Self::C,
"cpp" | "c++" => Self::Cpp,
"csharp" | "c#" | "cs" => Self::CSharp,
"ruby" | "rb" => Self::Ruby,
"php" => Self::Php,
"kotlin" | "kt" => Self::Kotlin,
"scala" => Self::Scala,
"swift" => Self::Swift,
"ocaml" | "ml" => Self::OCaml,
"solidity" | "sol" => Self::Solidity,
"lua" => Self::Lua,
"zig" => Self::Zig,
"elisp" | "emacs-lisp" | "el" => Self::Elisp,
_ => return None,
})
}
pub const ALL: &'static [Self] = &[
Self::Rust,
Self::Python,
Self::JavaScript,
Self::TypeScript,
Self::Tsx,
Self::Go,
Self::Java,
Self::C,
Self::Cpp,
Self::CSharp,
Self::Ruby,
Self::Php,
Self::Kotlin,
Self::Scala,
Self::Swift,
Self::OCaml,
Self::Solidity,
Self::Lua,
Self::Zig,
Self::Elisp,
];
}
impl fmt::Display for Language {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Rust => "rust",
Self::Python => "python",
Self::JavaScript => "javascript",
Self::TypeScript => "typescript",
Self::Tsx => "tsx",
Self::Go => "go",
Self::Java => "java",
Self::C => "c",
Self::Cpp => "cpp",
Self::CSharp => "csharp",
Self::Ruby => "ruby",
Self::Php => "php",
Self::Kotlin => "kotlin",
Self::Scala => "scala",
Self::Swift => "swift",
Self::OCaml => "ocaml",
Self::Solidity => "solidity",
Self::Lua => "lua",
Self::Zig => "zig",
Self::Elisp => "elisp",
Self::Unknown => "unknown",
};
write!(f, "{s}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_language_parses_back_from_its_own_name() {
for lang in Language::ALL {
assert_eq!(
Language::from_name(&lang.to_string()),
Some(*lang),
"{lang} does not round-trip through from_name"
);
}
}
#[test]
fn names_are_case_insensitive_and_accept_short_forms() {
assert_eq!(Language::from_name("Rust"), Some(Language::Rust));
assert_eq!(Language::from_name("PY"), Some(Language::Python));
assert_eq!(Language::from_name("c++"), Some(Language::Cpp));
assert_eq!(Language::from_name("emacs-lisp"), Some(Language::Elisp));
}
#[test]
fn unknown_is_not_a_filterable_language() {
assert_eq!(Language::from_name("nope"), None);
assert_eq!(Language::from_name("unknown"), None);
assert!(!Language::ALL.contains(&Language::Unknown));
}
}