fb2_clean/config/
input.rs

1use crate::Result;
2use std::{fs, path::Path};
3
4/// Input directory OR file.
5#[derive(Clone, Debug, PartialEq)]
6pub enum Input {
7    Dir(Box<Path>),
8    File(InputFile),
9}
10
11/// Input file.
12#[derive(Clone, Debug, Hash, Eq, PartialEq)]
13pub struct InputFile {
14    pub ty: InputFileType,
15    pub path: Box<Path>,
16}
17
18/// Input file type.
19#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum InputFileType {
22    Fb2,
23    Fb2Zip,
24}
25
26impl Input {
27    /// Returns iterator of files in the [`Input::Dir`] directory (non-recursive),
28    /// OR single-file iterator from [`Input::File`].
29    pub fn iter(&self) -> Box<dyn Iterator<Item = InputFile>> {
30        match self {
31            Self::Dir(d) => Box::new(
32                fs::read_dir(d)
33                    .ok()
34                    .into_iter()
35                    .flat_map(|rd| rd.filter_map(std::result::Result::ok))
36                    .filter_map(|entry| {
37                        let path = entry.path();
38                        get_input_file_type(&path).map(|ty| InputFile {
39                            ty,
40                            path: path.into(),
41                        })
42                    }),
43            ),
44            Self::File(f) => Box::new(Some(f.clone()).into_iter()),
45        }
46    }
47}
48
49impl Input {
50    pub(crate) fn new(path: impl AsRef<Path>) -> Result<Input> {
51        let path = fs::canonicalize(path)?.into_boxed_path();
52
53        if path.is_dir() {
54            Ok(Self::Dir(path))
55        } else if let Some(ty) = get_input_file_type(&path) {
56            Ok(Self::File(InputFile { ty, path }))
57        } else {
58            Err("File has unsupported extension".into())
59        }
60    }
61}
62
63impl InputFileType {
64    pub(crate) const fn is_fb2(&self) -> bool {
65        matches!(self, Self::Fb2)
66    }
67
68    pub(crate) const fn is_fb2_zip(&self) -> bool {
69        matches!(self, Self::Fb2Zip)
70    }
71
72    pub(crate) const fn as_extension(&self) -> &'static str {
73        if self.is_fb2() { "fb2" } else { "fb2.zip" }
74    }
75}
76
77fn get_input_file_type(f: &Path) -> Option<InputFileType> {
78    let bytes = f.as_os_str().as_encoded_bytes();
79    let len = bytes.len();
80
81    for (ext, ty) in [
82        (".fb2", InputFileType::Fb2),
83        (".fb2.zip", InputFileType::Fb2Zip),
84    ] {
85        let ext = ext.as_bytes();
86        let l = ext.len();
87
88        if l > len {
89            break;
90        }
91        if bytes[len - l..len].eq_ignore_ascii_case(ext) {
92            return Some(ty);
93        }
94    }
95
96    None
97}