file_matcher/entries/
one.rs

1use crate::utils::is_readable_entry;
2use crate::{EntryName, EntryType, FileMatcherError, Result};
3use std::ffi::OsString;
4use std::fmt::Debug;
5use std::io::Read;
6use std::path::{Path, PathBuf};
7
8pub trait OneEntryNamed: Debug {
9    fn within_path_buf(&self, directory: PathBuf) -> OneEntry;
10    fn entry_name(&self) -> &EntryName;
11    fn entry_type(&self) -> &EntryType;
12    fn name_alias(&self) -> Option<&str>;
13    fn boxed(&self) -> Box<dyn OneEntryNamed>;
14}
15
16#[derive(Debug)]
17pub struct OneEntry {
18    entry_named: Box<dyn OneEntryNamed>,
19    directory: PathBuf,
20}
21
22impl OneEntry {
23    pub fn new(entry_named: Box<dyn OneEntryNamed>, directory: impl Into<PathBuf>) -> Self {
24        Self {
25            entry_named,
26            directory: directory.into(),
27        }
28    }
29
30    pub fn entry(&self) -> &dyn OneEntryNamed {
31        self.entry_named.as_ref()
32    }
33
34    pub fn entry_type(&self) -> &EntryType {
35        self.entry().entry_type()
36    }
37
38    pub fn entry_name(&self) -> &EntryName {
39        self.entry().entry_name()
40    }
41
42    pub fn directory(&self) -> &Path {
43        self.directory.as_path()
44    }
45
46    /// Return true if there exists exactly one entry of the specified type and name,
47    /// false otherwise
48    pub fn exists(&self) -> Result<bool> {
49        match self.find() {
50            Ok(path) => Ok(path.exists()),
51            Err(error) => match &error {
52                FileMatcherError::NotExists(_) => Ok(false),
53                _ => error.into(),
54            },
55        }
56    }
57
58    /// Try to find an exactly one entry of the specified type and name
59    pub fn find(&self) -> Result<PathBuf> {
60        let entry_type = self.entry_named.entry_type();
61        let entry_name = self.entry_named.entry_name();
62
63        self.find_by_type_and_name(entry_type, entry_name)
64    }
65
66    fn find_by_type_and_name(
67        &self,
68        entry_type: &EntryType,
69        entry_name: &EntryName,
70    ) -> Result<PathBuf> {
71        match entry_name {
72            EntryName::Exact(name) => {
73                let entry = self.directory.join(name);
74                if is_readable_entry(entry_type, &entry) {
75                    Ok(entry)
76                } else {
77                    FileMatcherError::NotExists(self.clone()).into()
78                }
79            }
80            EntryName::Any(names) => {
81                let entries = names
82                    .iter()
83                    .map(|each| self.directory.join(each))
84                    .filter(|each| is_readable_entry(entry_type, each.as_path()))
85                    .collect::<Vec<PathBuf>>();
86
87                match entries.len() {
88                    0 => FileMatcherError::NotExists(self.clone()).into(),
89                    1 => Ok(entries.first().unwrap().to_owned()),
90                    _ => FileMatcherError::TooMany(self.clone()).into(),
91                }
92            }
93            EntryName::AnyNamed(entry_names) => {
94                let mut entries: Vec<PathBuf> = vec![];
95
96                for entry_name in entry_names {
97                    (match self.find_by_type_and_name(entry_type, entry_name) {
98                        Ok(entry) => {
99                            entries.push(entry);
100                            Ok(())
101                        }
102                        Err(error) => match &error {
103                            FileMatcherError::NotExists(_) => Ok(()),
104                            _ => error.into(),
105                        },
106                    })?;
107                }
108
109                match entries.len() {
110                    0 => FileMatcherError::NotExists(self.clone()).into(),
111                    1 => Ok(entries.first().unwrap().to_owned()),
112                    _ => FileMatcherError::TooMany(self.clone()).into(),
113                }
114            }
115            #[cfg(feature = "regex")]
116            EntryName::Regex(regex_pattern) => {
117                let entries = crate::finders::regex_finder::find_entries_in_directory_matching(
118                    entry_type,
119                    regex_pattern,
120                    &self.directory,
121                )?;
122                match entries.len() {
123                    0 => FileMatcherError::NotExists(self.clone()).into(),
124                    1 => Ok(entries.first().unwrap().to_owned()),
125                    _ => FileMatcherError::TooMany(self.clone()).into(),
126                }
127            }
128            #[cfg(feature = "wildmatch")]
129            EntryName::Wildmatch(wildmatch_pattern) => {
130                let entries = crate::finders::wildmatch_finder::find_entries_in_directory_matching(
131                    entry_type,
132                    wildmatch_pattern,
133                    &self.directory,
134                )?;
135                match entries.len() {
136                    0 => FileMatcherError::NotExists(self.clone()).into(),
137                    1 => Ok(entries.first().unwrap().to_owned()),
138                    _ => FileMatcherError::TooMany(self.clone()).into(),
139                }
140            }
141        }
142    }
143
144    pub fn as_path_buf(&self) -> Result<PathBuf> {
145        self.find()
146    }
147
148    pub fn as_os_string(&self) -> Result<OsString> {
149        let path = self.find()?;
150        match path.file_name() {
151            None => FileMatcherError::NotReadable(path.clone()).into(),
152            Some(file_name) => Ok(file_name.to_os_string()),
153        }
154    }
155
156    pub fn as_string(&self) -> Result<String> {
157        let file_name = self.as_os_string()?;
158        match file_name.to_str() {
159            None => FileMatcherError::InvalidUnicode(file_name).into(),
160            Some(file_name) => Ok(file_name.to_owned()),
161        }
162    }
163
164    pub fn as_bytes(&self) -> Result<Vec<u8>> {
165        let path = self.find()?;
166        let mut file = std::fs::File::open(path.as_path())?;
167        let mut buffer = Vec::new();
168        file.read_to_end(&mut buffer)?;
169        Ok(buffer)
170    }
171}
172
173impl Clone for OneEntry {
174    fn clone(&self) -> Self {
175        Self::new(self.entry_named.boxed(), self.directory.clone())
176    }
177}
178
179impl From<OneEntry> for PathBuf {
180    fn from(filter: OneEntry) -> Self {
181        PathBuf::from(&filter)
182    }
183}
184
185impl From<&OneEntry> for PathBuf {
186    fn from(filter: &OneEntry) -> Self {
187        filter
188            .find()
189            .expect("Could not find exactly one matching file")
190    }
191}
192
193impl From<&OneEntry> for Result<OsString> {
194    fn from(filter: &OneEntry) -> Self {
195        filter.as_os_string()
196    }
197}
198
199impl From<OneEntry> for Result<OsString> {
200    fn from(filter: OneEntry) -> Self {
201        (&filter).into()
202    }
203}
204
205impl From<&OneEntry> for Result<String> {
206    fn from(filter: &OneEntry) -> Self {
207        filter.as_string()
208    }
209}
210
211impl From<OneEntry> for Result<String> {
212    fn from(filter: OneEntry) -> Self {
213        (&filter).into()
214    }
215}
216
217impl From<&OneEntry> for Result<Vec<u8>> {
218    fn from(filter: &OneEntry) -> Self {
219        filter.as_bytes()
220    }
221}
222
223impl From<OneEntry> for Result<Vec<u8>> {
224    fn from(filter: OneEntry) -> Self {
225        (&filter).into()
226    }
227}