use std::borrow::Cow;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
Tsx,
Go,
Java,
C,
Cpp,
CSharp,
Ruby,
Php,
Swift,
Dart,
Other,
}
impl Language {
pub const ALL: [Language; 15] = [
Language::Rust,
Language::Python,
Language::JavaScript,
Language::TypeScript,
Language::Tsx,
Language::Go,
Language::Java,
Language::C,
Language::Cpp,
Language::CSharp,
Language::Ruby,
Language::Php,
Language::Swift,
Language::Dart,
Language::Other,
];
pub fn id(self) -> &'static str {
match self {
Language::Rust => "rust",
Language::Python => "python",
Language::JavaScript => "javascript",
Language::TypeScript => "typescript",
Language::Tsx => "tsx",
Language::Go => "go",
Language::Java => "java",
Language::C => "c",
Language::Cpp => "cpp",
Language::CSharp => "csharp",
Language::Ruby => "ruby",
Language::Php => "php",
Language::Swift => "swift",
Language::Dart => "dart",
Language::Other => "other",
}
}
pub fn from_id(s: &str) -> Option<Language> {
Some(match s {
"rust" => Language::Rust,
"python" => Language::Python,
"javascript" => Language::JavaScript,
"typescript" => Language::TypeScript,
"tsx" => Language::Tsx,
"go" => Language::Go,
"java" => Language::Java,
"c" => Language::C,
"cpp" => Language::Cpp,
"csharp" => Language::CSharp,
"ruby" => Language::Ruby,
"php" => Language::Php,
"swift" => Language::Swift,
"dart" => Language::Dart,
"other" => Language::Other,
_ => return None,
})
}
pub fn from_extension(ext: &str) -> Language {
let lower;
let ext = if ext.bytes().any(|b| b.is_ascii_uppercase()) {
lower = ext.to_ascii_lowercase();
lower.as_str()
} else {
ext
};
match ext {
"rs" => Language::Rust,
"py" | "pyi" | "pyw" => Language::Python,
"js" | "mjs" | "cjs" | "jsx" => Language::JavaScript,
"ts" | "mts" | "cts" => Language::TypeScript,
"tsx" => Language::Tsx,
"go" => Language::Go,
"java" => Language::Java,
"c" | "h" => Language::C,
"cc" | "cpp" | "cxx" | "c++" | "hpp" | "hh" | "hxx" | "h++" | "ipp" | "tpp" => {
Language::Cpp
}
"cs" => Language::CSharp,
"rb" | "rake" | "gemspec" => Language::Ruby,
"php" | "php5" | "php7" | "phtml" => Language::Php,
"swift" => Language::Swift,
"dart" => Language::Dart,
_ => Language::Other,
}
}
pub fn grammar(self) -> Option<tree_sitter::Language> {
let lang = match self {
Language::Rust => tree_sitter_rust::LANGUAGE.into(),
Language::Python => tree_sitter_python::LANGUAGE.into(),
Language::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
Language::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
Language::Go => tree_sitter_go::LANGUAGE.into(),
Language::Java => tree_sitter_java::LANGUAGE.into(),
Language::C => tree_sitter_c::LANGUAGE.into(),
Language::Cpp => tree_sitter_cpp::LANGUAGE.into(),
Language::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
Language::Ruby => tree_sitter_ruby::LANGUAGE.into(),
Language::Php => tree_sitter_php::LANGUAGE_PHP.into(),
Language::Swift => tree_sitter_swift::LANGUAGE.into(),
Language::Dart => tree_sitter_dart::LANGUAGE.into(),
Language::Other => return None,
};
Some(lang)
}
}
impl Serialize for Language {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.id())
}
}
impl<'de> Deserialize<'de> for Language {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = <Cow<'de, str>>::deserialize(deserializer)?;
Language::from_id(&s)
.ok_or_else(|| serde::de::Error::custom(format!("unknown language id: {s:?}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn id_roundtrips_through_from_id() {
for lang in Language::ALL {
assert_eq!(Language::from_id(lang.id()), Some(lang));
}
}
#[test]
fn serde_matches_id() {
for lang in Language::ALL {
let json = serde_json::to_string(&lang).unwrap();
assert_eq!(json, format!("{:?}", lang.id()));
let back: Language = serde_json::from_str(&json).unwrap();
assert_eq!(back, lang);
}
}
#[test]
fn from_extension_is_case_insensitive() {
assert_eq!(Language::from_extension("RS"), Language::Rust);
assert_eq!(Language::from_extension("Cpp"), Language::Cpp);
assert_eq!(Language::from_extension("rs"), Language::Rust);
assert_eq!(Language::from_extension("dart"), Language::Dart);
assert_eq!(Language::from_extension("unknownext"), Language::Other);
}
#[test]
fn parseable_languages_have_grammars() {
for lang in Language::ALL {
if lang == Language::Other {
assert!(lang.grammar().is_none());
} else {
assert!(lang.grammar().is_some(), "{lang:?} missing grammar");
}
}
}
}