Skip to main content

fb2_clean/config/
input.rs

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