1use 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#[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