1use std::borrow::Cow;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub enum FormatId {
7 PlainText,
8 Markdown,
9 Log,
10 SourceCode,
11 Json,
12 Yaml,
13 Toml,
14 Xml,
15 Csv,
16 Html,
17 Pdf,
18 Docx,
19 Odt,
20 Rtf,
21 Epub,
22 Xlsx,
23 Pptx,
24 External(&'static str),
26 Binary,
28}
29
30impl FormatId {
31 pub fn name(self) -> &'static str {
32 match self {
33 FormatId::PlainText => "text",
34 FormatId::Markdown => "markdown",
35 FormatId::Log => "log",
36 FormatId::SourceCode => "code",
37 FormatId::Json => "json",
38 FormatId::Yaml => "yaml",
39 FormatId::Toml => "toml",
40 FormatId::Xml => "xml",
41 FormatId::Csv => "csv",
42 FormatId::Html => "html",
43 FormatId::Pdf => "pdf",
44 FormatId::Docx => "docx",
45 FormatId::Odt => "odt",
46 FormatId::Rtf => "rtf",
47 FormatId::Epub => "epub",
48 FormatId::Xlsx => "xlsx",
49 FormatId::Pptx => "pptx",
50 FormatId::External(n) => n,
51 FormatId::Binary => "binary",
52 }
53 }
54
55 pub fn parse(s: &str) -> Option<Self> {
57 let known = [
58 FormatId::PlainText,
59 FormatId::Markdown,
60 FormatId::Log,
61 FormatId::SourceCode,
62 FormatId::Json,
63 FormatId::Yaml,
64 FormatId::Toml,
65 FormatId::Xml,
66 FormatId::Csv,
67 FormatId::Html,
68 FormatId::Pdf,
69 FormatId::Docx,
70 FormatId::Odt,
71 FormatId::Rtf,
72 FormatId::Epub,
73 FormatId::Xlsx,
74 FormatId::Pptx,
75 FormatId::Binary,
76 ];
77 let lower = s.to_ascii_lowercase();
78 let lower = match lower.as_str() {
80 "md" => "markdown".to_string(),
81 "txt" => "text".to_string(),
82 "yml" => "yaml".to_string(),
83 "htm" => "html".to_string(),
84 "rs" | "py" | "js" | "source" => "code".to_string(),
85 other => other.to_string(),
86 };
87 known.into_iter().find(|f| f.name() == lower)
88 }
89
90 pub fn all_names() -> Vec<&'static str> {
91 vec![
92 "text", "markdown", "log", "code", "json", "yaml", "toml", "xml", "csv", "html", "pdf",
93 "docx", "odt", "rtf", "epub", "xlsx", "pptx", "binary",
94 ]
95 }
96}
97
98impl std::fmt::Display for FormatId {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.write_str(self.name())
101 }
102}
103
104pub type Confidence = u8;
109
110pub mod confidence {
111 use super::Confidence;
112 pub const EXPLICIT: Confidence = 100;
114 pub const MAGIC: Confidence = 90;
116 pub const STRUCTURAL: Confidence = 70;
118 pub const EXTENSION: Confidence = 50;
120 pub const HEURISTIC: Confidence = 30;
122 pub const FALLBACK: Confidence = 1;
124}
125
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct Detection {
128 pub format: FormatId,
129 pub confidence: Confidence,
130 pub reason: Cow<'static, str>,
133 pub encoding: Option<&'static str>,
135}
136
137impl Detection {
138 pub fn new(
139 format: FormatId,
140 confidence: Confidence,
141 reason: impl Into<Cow<'static, str>>,
142 ) -> Self {
143 Detection {
144 format,
145 confidence,
146 reason: reason.into(),
147 encoding: None,
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn parse_accepts_aliases() {
158 assert_eq!(FormatId::parse("md"), Some(FormatId::Markdown));
159 assert_eq!(FormatId::parse("MARKDOWN"), Some(FormatId::Markdown));
160 assert_eq!(FormatId::parse("yml"), Some(FormatId::Yaml));
161 assert_eq!(FormatId::parse("txt"), Some(FormatId::PlainText));
162 assert_eq!(FormatId::parse("nonexistent"), None);
163 }
164
165 #[test]
166 fn all_names_covers_every_parseable_format() {
167 for name in FormatId::all_names() {
168 assert!(
169 FormatId::parse(name).is_some(),
170 "'{name}' is advertised in --help but does not parse"
171 );
172 }
173 }
174}