fb2_clean/config/
input.rs

1use crate::Result;
2use either::Either;
3use std::{fs, iter, path::Path};
4
5/// Input directory OR file.
6#[derive(Clone, Debug, PartialEq)]
7pub enum Input {
8    Dir(Box<Path>),
9    File(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 single-file iterator from [`Input::File`].
30    #[deprecated(since = "0.2.2", note = "Use new_iter")]
31    pub fn iter(&self) -> Box<dyn Iterator<Item = InputFile>> {
32        match self {
33            Self::Dir(d) => Box::new(
34                fs::read_dir(d)
35                    .ok()
36                    .into_iter()
37                    .flat_map(|rd| rd.filter_map(std::result::Result::ok))
38                    .filter_map(|entry| {
39                        let path = entry.path();
40                        get_input_file_type(&path).map(|ty| InputFile {
41                            ty,
42                            path: path.into(),
43                        })
44                    }),
45            ),
46            Self::File(f) => Box::new(Some(f.clone()).into_iter()),
47        }
48    }
49
50    /// Returns iterator of files in the [`Input::Dir`] directory (non-recursive),
51    /// OR single-file iterator from [`Input::File`].
52    pub fn new_iter(&self) -> impl Iterator<Item = InputFile> + use<> {
53        match self {
54            Self::Dir(d) => Either::Left(
55                fs::read_dir(d)
56                    .ok()
57                    .into_iter()
58                    .flat_map(|rd| rd.filter_map(std::result::Result::ok))
59                    .filter_map(|entry| {
60                        let path = entry.path();
61                        get_input_file_type(&path).map(|ty| InputFile {
62                            ty,
63                            path: path.into(),
64                        })
65                    }),
66            ),
67            Self::File(f) => Either::Right(iter::once(f.clone())),
68        }
69    }
70}
71
72impl Input {
73    pub(crate) fn new(path: impl AsRef<Path>) -> Result<Input> {
74        let path = fs::canonicalize(path)?.into_boxed_path();
75
76        if path.is_dir() {
77            Ok(Self::Dir(path))
78        } else if let Some(ty) = get_input_file_type(&path) {
79            Ok(Self::File(InputFile { ty, path }))
80        } else {
81            Err("File has unsupported extension".into())
82        }
83    }
84}
85
86impl InputFileType {
87    pub(crate) const fn is_fb2(&self) -> bool {
88        matches!(self, Self::Fb2)
89    }
90
91    pub(crate) const fn is_fb2_zip(&self) -> bool {
92        matches!(self, Self::Fb2Zip)
93    }
94
95    pub(crate) const fn as_extension(&self) -> &'static str {
96        if self.is_fb2() { "fb2" } else { "fb2.zip" }
97    }
98}
99
100fn get_input_file_type(f: &Path) -> Option<InputFileType> {
101    let bytes = f.as_os_str().as_encoded_bytes();
102    let len = bytes.len();
103
104    for (ext, ty) in [
105        (".fb2", InputFileType::Fb2),
106        (".fb2.zip", InputFileType::Fb2Zip),
107    ] {
108        let ext = ext.as_bytes();
109        let l = ext.len();
110
111        if l > len {
112            break;
113        }
114        if bytes[len - l..len].eq_ignore_ascii_case(ext) {
115            return Some(ty);
116        }
117    }
118
119    None
120}