use std::collections::BTreeSet;
use std::path::Path;
use std::sync::LazyLock;
pub mod definitions;
pub mod runner;
pub mod spec;
use spec::LanguageSupport;
pub fn detect(path: &Path) -> Option<&'static LanguageSupport> {
detect_index(path).map(|index| all_languages()[index])
}
fn detect_index(path: &Path) -> Option<usize> {
if let Some(ext) = path.extension().and_then(|e| e.to_str())
&& let Some(index) = by_extension(ext)
{
return Some(index);
}
let name = path.file_name()?.to_str()?;
by_filename(name).or_else(|| by_filename_stem(name))
}
fn by_extension(ext: &str) -> Option<usize> {
all_languages().iter().position(|lang| {
lang.extensions
.iter()
.any(|known| known[1..].eq_ignore_ascii_case(ext))
})
}
fn by_filename(name: &str) -> Option<usize> {
all_languages().iter().position(|lang| {
lang.filenames
.iter()
.any(|known| known.eq_ignore_ascii_case(name))
})
}
fn by_filename_stem(name: &str) -> Option<usize> {
let (head, variant) = name.split_once('.')?;
if variant.is_empty() {
return None;
}
all_languages().iter().position(|lang| {
lang.filename_prefixes
.iter()
.any(|stem| stem.eq_ignore_ascii_case(head))
})
}
pub fn group_by_language<'a>(paths: &[&'a Path]) -> Vec<(&'static LanguageSupport, Vec<&'a Path>)> {
let mut buckets: Vec<Vec<&'a Path>> = vec![Vec::new(); all_languages().len()];
for path in paths {
if let Some(index) = detect_index(path) {
buckets[index].push(path);
}
}
buckets
.into_iter()
.enumerate()
.filter(|(_, files)| !files.is_empty())
.map(|(index, files)| (all_languages()[index], files))
.collect()
}
pub fn all_languages() -> &'static [&'static LanguageSupport] {
&definitions::ALL_LANGUAGES[..]
}
pub fn source_extensions() -> &'static [&'static str] {
&SOURCE_EXTENSIONS
}
static SOURCE_EXTENSIONS: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
let mut seen = BTreeSet::new();
let mut out = Vec::new();
for lang in all_languages() {
for ext in lang.extensions {
if seen.insert(*ext) {
out.push(*ext);
}
}
}
out
});
pub fn vendored_dirs() -> &'static [&'static str] {
&VENDORED_DIRS
}
static VENDORED_DIRS: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
let mut seen = BTreeSet::new();
let mut out = Vec::new();
for lang in all_languages() {
for dir in lang.vendored_dirs {
if seen.insert(*dir) {
out.push(*dir);
}
}
}
out
});
#[cfg(test)]
mod tests;