use super::js;
const ALIASES: [(&str, &str); 31] = [
("json", "json"),
("jsonc", "json"),
("yaml", "yaml"),
("yml", "yaml"),
("csv", "csv"),
("tsv", "csv"),
("xml", "xml"),
("log", "log"),
("txt", "plaintext"),
("text", "plaintext"),
("plaintext", "plaintext"),
("javascript", "javascript"),
("js", "javascript"),
("jsx", "javascript"),
("mjs", "javascript"),
("cjs", "javascript"),
("javascriptreact", "javascript"),
("typescript", "typescript"),
("ts", "typescript"),
("tsx", "typescript"),
("mts", "typescript"),
("cts", "typescript"),
("typescriptreact", "typescript"),
("html", "html"),
("toml", "toml"),
("ini", "ini"),
("cfg", "ini"),
("conf", "ini"),
("properties", "properties"),
("markdown", "markdown"),
("md", "markdown"),
];
const EXTENSION_ONLY: [(&str, &str); 2] = [("htm", "html"), ("xhtml", "html")];
pub(crate) const SUPPORTED_FORMATS: [&str; 11] = [
"json",
"yaml",
"csv",
"xml",
"log",
"plaintext",
"javascript",
"typescript",
"html",
"toml",
"markdown",
];
pub(crate) const FALLBACK_FORMAT: &str = "unknown";
fn normalise(value: &str) -> String {
let lowered = js::trim(value).to_lowercase();
lowered.strip_prefix('.').unwrap_or(&lowered).to_string()
}
fn lookup(name: &str) -> Option<&'static str> {
let name = normalise(name);
ALIASES
.iter()
.chain(EXTENSION_ONLY.iter())
.find(|(alias, _)| *alias == name)
.map(|(_, language)| *language)
}
pub(crate) fn resolve_format(format: Option<&str>, filename: Option<&str>) -> &'static str {
if let Some(language) = format.and_then(lookup) {
return language;
}
filename
.and_then(|name| name.rsplit_once('.'))
.and_then(|(_, extension)| lookup(extension))
.unwrap_or(FALLBACK_FORMAT)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_explicit_format_wins() {
assert_eq!(resolve_format(Some("yml"), None), "yaml");
assert_eq!(resolve_format(Some(".TS"), None), "typescript");
}
#[test]
fn a_filename_is_the_fallback() {
assert_eq!(resolve_format(None, Some("app.log")), "log");
assert_eq!(resolve_format(Some("rust"), Some("a.json")), "json");
}
#[test]
fn typescript_and_plaintext_are_their_own_keys() {
assert_eq!(resolve_format(Some("typescript"), None), "typescript");
assert_eq!(resolve_format(Some("plaintext"), None), "plaintext");
assert_eq!(resolve_format(Some("tsx"), None), "typescript");
}
#[test]
fn the_configuration_and_prose_names_resolve_to_themselves() {
assert_eq!(resolve_format(None, Some("pyproject.toml")), "toml");
assert_eq!(resolve_format(None, Some("setup.cfg")), "ini");
assert_eq!(resolve_format(None, Some("app.conf")), "ini");
assert_eq!(resolve_format(None, Some("README.md")), "markdown");
assert_eq!(resolve_format(Some("markdown"), None), "markdown");
}
#[test]
fn an_unknown_name_falls_back_rather_than_refusing() {
assert_eq!(resolve_format(Some("rust"), None), FALLBACK_FORMAT);
assert_eq!(resolve_format(None, Some("main.rs")), FALLBACK_FORMAT);
assert_eq!(resolve_format(None, Some("Makefile")), FALLBACK_FORMAT);
assert_eq!(resolve_format(None, Some("noextension")), FALLBACK_FORMAT);
assert_eq!(resolve_format(None, None), FALLBACK_FORMAT);
}
#[test]
fn the_name_is_trimmed_the_way_javascript_trims() {
assert_eq!(resolve_format(Some("\u{feff}json"), None), "json");
assert_eq!(resolve_format(Some("json\u{feff}"), None), "json");
assert_eq!(resolve_format(Some("\u{85}json"), None), FALLBACK_FORMAT);
assert_eq!(resolve_format(Some(" json\t"), None), "json");
}
#[test]
fn only_one_leading_dot_is_stripped() {
assert_eq!(resolve_format(Some(".json"), None), "json");
assert_eq!(resolve_format(Some("..json"), None), FALLBACK_FORMAT);
}
#[test]
fn every_supported_name_resolves() {
for name in SUPPORTED_FORMATS {
assert_eq!(resolve_format(Some(name), None), name, "{name}");
}
}
#[test]
fn no_advertised_name_is_the_fallback() {
for name in SUPPORTED_FORMATS {
assert_ne!(name, FALLBACK_FORMAT);
}
}
}