Skip to main content

snapbox/data/
format.rs

1/// Describes the structure of [`Data`][crate::Data]
2#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Default)]
3#[non_exhaustive]
4pub enum DataFormat {
5    /// Processing of the [`Data`][crate::Data] failed
6    Error,
7    /// Non-textual, opaque data
8    Binary,
9    #[default]
10    Text,
11    #[cfg(feature = "json")]
12    Json,
13    /// Streamed JSON output according to <https://jsonlines.org/>
14    #[cfg(feature = "json")]
15    JsonLines,
16    /// [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#DOS_and_Windows)
17    /// rendered as [svg](https://docs.rs/anstyle-svg)
18    #[cfg(feature = "term-svg")]
19    TermSvg,
20}
21
22impl DataFormat {
23    /// Assumed file extension for the format
24    pub fn ext(self) -> &'static str {
25        match self {
26            Self::Error => "txt",
27            Self::Binary => "bin",
28            Self::Text => "txt",
29            #[cfg(feature = "json")]
30            Self::Json => "json",
31            #[cfg(feature = "json")]
32            Self::JsonLines => "jsonl",
33            #[cfg(feature = "term-svg")]
34            Self::TermSvg => "term.svg",
35        }
36    }
37}
38
39impl From<&std::path::Path> for DataFormat {
40    fn from(path: &std::path::Path) -> Self {
41        let file_name = path
42            .file_name()
43            .and_then(|e| e.to_str())
44            .unwrap_or_default();
45        let mut ext = file_name.strip_prefix('.').unwrap_or(file_name);
46        while let Some((_, new_ext)) = ext.split_once('.') {
47            ext = new_ext;
48            match ext {
49                #[cfg(feature = "json")]
50                "json" => {
51                    return DataFormat::Json;
52                }
53                #[cfg(feature = "json")]
54                "jsonl" => {
55                    return DataFormat::JsonLines;
56                }
57                #[cfg(feature = "term-svg")]
58                "term.svg" => {
59                    return Self::TermSvg;
60                }
61                _ => {}
62            }
63        }
64        DataFormat::Text
65    }
66}
67
68#[cfg(test)]
69mod test {
70    use super::*;
71
72    #[test]
73    fn combos() {
74        #[cfg(feature = "json")]
75        let json = DataFormat::Json;
76        #[cfg(not(feature = "json"))]
77        let json = DataFormat::Text;
78        #[cfg(feature = "json")]
79        let jsonl = DataFormat::JsonLines;
80        #[cfg(not(feature = "json"))]
81        let jsonl = DataFormat::Text;
82        #[cfg(feature = "term-svg")]
83        let term_svg = DataFormat::TermSvg;
84        #[cfg(not(feature = "term-svg"))]
85        let term_svg = DataFormat::Text;
86        let cases = [
87            ("foo", DataFormat::Text),
88            (".foo", DataFormat::Text),
89            ("foo.txt", DataFormat::Text),
90            (".foo.txt", DataFormat::Text),
91            ("foo.stdout.txt", DataFormat::Text),
92            ("foo.json", json),
93            ("foo.stdout.json", json),
94            (".foo.json", json),
95            ("foo.jsonl", jsonl),
96            ("foo.stdout.jsonl", jsonl),
97            (".foo.jsonl", jsonl),
98            ("foo.term.svg", term_svg),
99            ("foo.stdout.term.svg", term_svg),
100            (".foo.term.svg", term_svg),
101        ];
102        for (input, output) in cases {
103            let input = std::path::Path::new(input);
104            assert_eq!(DataFormat::from(input), output, "for `{}`", input.display());
105        }
106    }
107}