pub mod generic;
pub trait Language: Send + Sync {
fn id(&self) -> &'static str;
fn claims(&self, path: &[u8]) -> bool;
fn normalize_line(&self, line: &[u8]) -> Vec<u8> {
generic::normalize_line(line)
}
}
pub struct Generic;
impl Language for Generic {
fn id(&self) -> &'static str {
"generic-v1"
}
fn claims(&self, _path: &[u8]) -> bool {
true
}
}
pub struct LanguageRegistry {
langs: Vec<Box<dyn Language>>,
fallback: Generic,
}
impl Default for LanguageRegistry {
fn default() -> Self {
Self::builtin()
}
}
impl LanguageRegistry {
pub fn builtin() -> Self {
LanguageRegistry {
langs: Vec::new(),
fallback: Generic,
}
}
pub fn register(&mut self, lang: Box<dyn Language>) {
self.langs.push(lang);
}
pub fn detect(&self, path: &[u8]) -> &dyn Language {
for l in &self.langs {
if l.claims(path) {
return l.as_ref();
}
}
&self.fallback
}
pub fn fingerprint(&self) -> String {
let mut parts: Vec<&str> = self.langs.iter().map(|l| l.id()).collect();
parts.push(self.fallback.id());
parts.join("+")
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FakeToml;
impl Language for FakeToml {
fn id(&self) -> &'static str {
"fake-toml-v1"
}
fn claims(&self, path: &[u8]) -> bool {
path.ends_with(b".toml")
}
fn normalize_line(&self, _line: &[u8]) -> Vec<u8> {
b"T".to_vec()
}
}
#[test]
fn fallback_claims_everything() {
let reg = LanguageRegistry::builtin();
assert_eq!(reg.detect(b"whatever.xyz").id(), "generic-v1");
assert_eq!(reg.fingerprint(), "generic-v1");
}
#[test]
fn registered_language_wins_for_its_files_only() {
let mut reg = LanguageRegistry::builtin();
reg.register(Box::new(FakeToml));
assert_eq!(reg.detect(b"Cargo.toml").id(), "fake-toml-v1");
assert_eq!(reg.detect(b"src/main.rs").id(), "generic-v1");
assert_eq!(reg.fingerprint(), "fake-toml-v1+generic-v1");
}
}