use std::borrow::Cow;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FormatId {
PlainText,
Markdown,
Log,
SourceCode,
Json,
Yaml,
Toml,
Xml,
Csv,
Html,
Pdf,
Docx,
Odt,
Rtf,
Epub,
Xlsx,
Pptx,
External(&'static str),
Binary,
}
impl FormatId {
pub fn name(self) -> &'static str {
match self {
FormatId::PlainText => "text",
FormatId::Markdown => "markdown",
FormatId::Log => "log",
FormatId::SourceCode => "code",
FormatId::Json => "json",
FormatId::Yaml => "yaml",
FormatId::Toml => "toml",
FormatId::Xml => "xml",
FormatId::Csv => "csv",
FormatId::Html => "html",
FormatId::Pdf => "pdf",
FormatId::Docx => "docx",
FormatId::Odt => "odt",
FormatId::Rtf => "rtf",
FormatId::Epub => "epub",
FormatId::Xlsx => "xlsx",
FormatId::Pptx => "pptx",
FormatId::External(n) => n,
FormatId::Binary => "binary",
}
}
pub fn parse(s: &str) -> Option<Self> {
let known = [
FormatId::PlainText,
FormatId::Markdown,
FormatId::Log,
FormatId::SourceCode,
FormatId::Json,
FormatId::Yaml,
FormatId::Toml,
FormatId::Xml,
FormatId::Csv,
FormatId::Html,
FormatId::Pdf,
FormatId::Docx,
FormatId::Odt,
FormatId::Rtf,
FormatId::Epub,
FormatId::Xlsx,
FormatId::Pptx,
FormatId::Binary,
];
let lower = s.to_ascii_lowercase();
let lower = match lower.as_str() {
"md" => "markdown".to_string(),
"txt" => "text".to_string(),
"yml" => "yaml".to_string(),
"htm" => "html".to_string(),
"rs" | "py" | "js" | "source" => "code".to_string(),
other => other.to_string(),
};
known.into_iter().find(|f| f.name() == lower)
}
pub fn all_names() -> Vec<&'static str> {
vec![
"text", "markdown", "log", "code", "json", "yaml", "toml", "xml", "csv", "html", "pdf",
"docx", "odt", "rtf", "epub", "xlsx", "pptx", "binary",
]
}
}
impl std::fmt::Display for FormatId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
pub type Confidence = u8;
pub mod confidence {
use super::Confidence;
pub const EXPLICIT: Confidence = 100;
pub const MAGIC: Confidence = 90;
pub const STRUCTURAL: Confidence = 70;
pub const EXTENSION: Confidence = 50;
pub const HEURISTIC: Confidence = 30;
pub const FALLBACK: Confidence = 1;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Detection {
pub format: FormatId,
pub confidence: Confidence,
pub reason: Cow<'static, str>,
pub encoding: Option<&'static str>,
}
impl Detection {
pub fn new(
format: FormatId,
confidence: Confidence,
reason: impl Into<Cow<'static, str>>,
) -> Self {
Detection {
format,
confidence,
reason: reason.into(),
encoding: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_accepts_aliases() {
assert_eq!(FormatId::parse("md"), Some(FormatId::Markdown));
assert_eq!(FormatId::parse("MARKDOWN"), Some(FormatId::Markdown));
assert_eq!(FormatId::parse("yml"), Some(FormatId::Yaml));
assert_eq!(FormatId::parse("txt"), Some(FormatId::PlainText));
assert_eq!(FormatId::parse("nonexistent"), None);
}
#[test]
fn all_names_covers_every_parseable_format() {
for name in FormatId::all_names() {
assert!(
FormatId::parse(name).is_some(),
"'{name}' is advertised in --help but does not parse"
);
}
}
}