collectfiles/
lib.rs

1// Copyright 2021 Hwakyeom Kim(=just-do-halee)
2
3//! `collectfiles`
4//! ## Example
5//! ```ignore
6//! use collectfiles::*;
7//!
8//! let vec = CollectFiles("/Users/hwakyeom/programs/")
9//!         .with_depth(1)
10//!         .with_target_regex(".md$")
11//!         .with_hook(|path| path.with_extension("mutated"))
12//!         .with_unwrap_or_else(|e| {
13//!             if e.kind() == io::ErrorKind::NotFound {
14//!                 PathBuf::from("/Users/other/")
15//!             } else {
16//!                panic!("{:?}", e)
17//!             }
18//!         })
19//!         .collect();
20//!
21//! println!("{:#?}", vec);
22//! ```
23
24use std::{
25    fs, io,
26    path::{Path, PathBuf},
27};
28
29use rayon::prelude::*;
30use regex::Regex;
31
32pub trait CollectFilesPrelude {
33    fn as_root_dir(&self) -> &Path;
34    fn as_target_regex(&self) -> Option<&str>;
35    fn as_hook(&self) -> Option<fn(PathBuf) -> PathBuf>;
36    fn as_depth(&self) -> Option<usize>;
37    fn with_hook(self, hook_fn: fn(PathBuf) -> PathBuf) -> CollectFilesConfigured;
38    fn with_depth(self, level: usize) -> CollectFilesConfigured;
39    fn with_target_regex(self, regex: &str) -> CollectFilesConfigured;
40    fn with_unwrap_or_else(self, f: fn(io::Error) -> PathBuf) -> CollectFilesConfigured;
41    fn collect(&self) -> Vec<PathBuf>;
42}
43use private::*;
44pub mod private {
45    use super::*;
46    #[derive(Debug, Default, Clone)]
47    pub struct CollectFilesConfigured {
48        root_dir: PathBuf,
49        depth: Option<usize>,
50        hook_fn: Option<fn(PathBuf) -> PathBuf>,
51        target_regex: Option<Regex>,
52        unwrap_or_else: Option<fn(io::Error) -> PathBuf>,
53    }
54    impl CollectFilesConfigured {
55        pub fn new(root_dir: PathBuf) -> Self {
56            Self {
57                root_dir,
58                ..Default::default()
59            }
60        }
61    }
62
63    impl CollectFilesPrelude for CollectFilesConfigured {
64        #[inline]
65        fn as_target_regex(&self) -> Option<&str> {
66            if let Some(regex) = &self.target_regex {
67                Some(regex.as_str())
68            } else {
69                None
70            }
71        }
72        #[inline]
73        fn as_root_dir(&self) -> &Path {
74            self.root_dir.as_ref()
75        }
76        #[inline]
77        fn as_hook(&self) -> Option<fn(PathBuf) -> PathBuf> {
78            self.hook_fn
79        }
80        #[inline]
81        fn as_depth(&self) -> Option<usize> {
82            self.depth
83        }
84        #[inline]
85        fn with_hook(mut self, hook_fn: fn(PathBuf) -> PathBuf) -> Self {
86            self.hook_fn = Some(hook_fn);
87            self
88        }
89        #[inline]
90        fn with_depth(mut self, level: usize) -> Self {
91            self.depth = Some(level);
92            self
93        }
94        #[inline]
95        fn with_target_regex(mut self, regex: &str) -> CollectFilesConfigured {
96            self.target_regex = Some(
97                Regex::new(regex).unwrap_or_else(|_| panic!("* Regular Expression: {}", regex)),
98            );
99            self
100        }
101        #[inline]
102        fn with_unwrap_or_else(mut self, f: fn(io::Error) -> PathBuf) -> CollectFilesConfigured {
103            self.unwrap_or_else = Some(f);
104            self
105        }
106        #[inline]
107        fn collect(&self) -> Vec<PathBuf> {
108            collect_files(
109                self.root_dir.clone(),
110                self.depth,
111                self.hook_fn,
112                self.target_regex.clone(),
113                self.unwrap_or_else,
114            )
115        }
116    }
117}
118
119/// CollectFiles(`entry_dir`)
120///
121/// ## Example
122/// ```ignore
123/// use collectfiles::*;
124///
125/// let vec = CollectFiles("/Users/hwakyeom/programs/")
126///         .with_depth(1)
127///         .with_target_regex(".md$")
128///         .with_hook(|path| path.with_extension("mutated"))
129///         .with_unwrap_or_else(|e| {
130///             if e.kind() == io::ErrorKind::NotFound {
131///                 PathBuf::from("/Users/other/")
132///             } else {
133///                panic!("{:?}", e)
134///             }
135///         })
136///         .collect();
137///
138/// println!("{:#?}", vec);
139/// ```
140#[derive(Debug)]
141pub struct CollectFiles<T>(pub T)
142where
143    T: AsRef<Path> + Clone + Send + Sync;
144
145impl<T> CollectFiles<T>
146where
147    T: AsRef<Path> + Clone + Send + Sync,
148{
149    fn clone(&self) -> CollectFilesConfigured {
150        CollectFilesConfigured::new(self.0.as_ref().to_path_buf())
151    }
152}
153
154impl<T> CollectFilesPrelude for CollectFiles<T>
155where
156    T: AsRef<Path> + Clone + Send + Sync,
157{
158    #[inline]
159    fn as_target_regex(&self) -> Option<&str> {
160        None
161    }
162    #[inline]
163    fn as_root_dir(&self) -> &Path {
164        self.0.as_ref()
165    }
166    #[inline]
167    fn as_hook(&self) -> Option<fn(PathBuf) -> PathBuf> {
168        None
169    }
170    #[inline]
171    fn as_depth(&self) -> Option<usize> {
172        None
173    }
174    #[inline]
175    fn with_hook(self, hook_fn: fn(PathBuf) -> PathBuf) -> CollectFilesConfigured {
176        self.clone().with_hook(hook_fn)
177    }
178    #[inline]
179    fn with_depth(self, level: usize) -> CollectFilesConfigured {
180        self.clone().with_depth(level)
181    }
182    #[inline]
183    fn with_target_regex(self, regex: &str) -> CollectFilesConfigured {
184        self.clone().with_target_regex(regex)
185    }
186    #[inline]
187    fn with_unwrap_or_else(self, f: fn(io::Error) -> PathBuf) -> CollectFilesConfigured {
188        self.clone().with_unwrap_or_else(f)
189    }
190    #[inline]
191    fn collect(&self) -> Vec<PathBuf> {
192        collect_files(self.0.as_ref().to_path_buf(), None, None, None, None)
193    }
194}
195#[inline]
196fn collect_files(
197    dir_path: PathBuf,
198    depth: Option<usize>,
199    hook_fn: Option<fn(PathBuf) -> PathBuf>,
200    target_regex: Option<Regex>,
201    unwrap_or_else: Option<fn(io::Error) -> PathBuf>,
202) -> Vec<PathBuf> {
203    let paths = if let Some(f) = unwrap_or_else {
204        fs::read_dir(dir_path)
205            .unwrap_or_else(|e| fs::read_dir(f(e)).unwrap())
206            .par_bridge()
207    } else {
208        fs::read_dir(dir_path).unwrap().par_bridge()
209    };
210
211    paths
212        .flat_map(|p| {
213            let path = if let Some(f) = unwrap_or_else {
214                match p {
215                    Ok(v) => v.path(),
216                    Err(e) => f(e),
217                }
218            } else {
219                p.unwrap().path()
220            };
221            if path.is_dir() {
222                match depth {
223                    Some(dep) if dep > 0 => collect_files(
224                        path,
225                        Some(dep - 1),
226                        hook_fn,
227                        target_regex.clone(),
228                        unwrap_or_else,
229                    ),
230                    Some(_) => vec![PathBuf::default()],
231                    None => {
232                        collect_files(path, depth, hook_fn, target_regex.clone(), unwrap_or_else)
233                    }
234                }
235            } else {
236                match &target_regex {
237                    Some(r)
238                        if r.is_match(path.to_str().unwrap_or_else(|| {
239                            panic!("* not a valid unicode extension: {}", path.display())
240                        })) =>
241                    {
242                        if let Some(hook) = hook_fn {
243                            vec![hook(path)]
244                        } else {
245                            vec![path]
246                        }
247                    }
248                    Some(_) => vec![PathBuf::default()],
249                    None => vec![path],
250                }
251            }
252        })
253        .filter(|p| p.as_os_str() != "")
254        .collect()
255}
256
257// #[cfg(test)]
258// mod tests {
259//     use super::*;
260//     #[test]
261//     fn it_works() {
262//         let c = CollectFiles("/Users/hwakyeom/programs2/")
263//             .with_depth(1)
264//             .with_target_regex(".md$")
265//             .with_hook(|path| path.with_extension("mutated"))
266//             .with_unwrap_or_else(|e| {
267//                 if e.kind() == io::ErrorKind::NotFound {
268//                     PathBuf::from("/Users/hwakyeom/programs/")
269//                 } else {
270//                     panic!("{:?}", e)
271//                 }
272//             });
273//         println!("{:#?}", c.collect());
274//         println!(
275//             "entry: {:?}\ndepth: {:?}\nhook: {:?}\n regex: {:?}",
276//             c.as_root_dir(),
277//             c.as_depth(),
278//             c.as_hook(),
279//             c.as_target_regex()
280//         )
281//     }
282// }