1use std::{ffi::OsStr, fs, path::{Path, PathBuf}};
2use regex::Regex;
3
4pub fn resolve(path: &Path) -> Option<Vec<PathBuf>> {
5 let mut fin: Vec<PathBuf> = Vec::new();
6
7 match path.iter().nth(0) {
8 Some(e) => {
9 let mut t = PathBuf::new();
10 t.push(e);
11 _ = t.canonicalize();
12 fin.push(t)
13 },
14 None => { return Some(fin); }
15 }
16
17 for pe in path.iter().skip(1) {
18 if fin.is_empty() { return Some(fin); }
19
20 fin = get_next_file_layer(fin, pe);
21 }
22
23 Some(fin)
24}
25
26fn get_next_file_layer(current_layer: Vec<PathBuf>, next_element: &OsStr) -> Vec<PathBuf> {
27 let mut new_layer: Vec<PathBuf> = Vec::new();
28
29 for p in ¤t_layer {
30 if p.is_file() { continue; }
31
32 let mut candidates = match next_element.to_str().unwrap().contains("*") {
33 false => {
34 if p.join(next_element).try_exists().expect(format!("Failed to determine if {:?} exists in {:?}", next_element, p).as_str()) {
35 vec![PathBuf::from(next_element)]
36 } else {
37 vec![]
38 }
39 },
40 true => {
41 let re = Regex::new(
42 &format!("^{}$", &next_element.to_str().unwrap().replace(".", "[.]").replace("*", ".*"))
43 ).expect(format!("Failed to create regex from {:?}", next_element).as_str());
44
45 let regex_filter = |x: PathBuf| -> Option<PathBuf> {
46 if re.is_match(x.iter().last().expect(format!("Failed to parse with regex {:?}", x).as_str()).to_str().unwrap()) {
47 return Some(p.join(x))
48 } else {
49 return None
50 };
51 };
52
53 fs::read_dir(p).expect(format!("Failed to read directory: {:?}", p).as_str())
54 .map(|x| PathBuf::from(x.unwrap().file_name()))
55 .filter_map(regex_filter)
56 .collect()
57 }
58 };
59
60 candidates = candidates.into_iter()
61 .filter_map(|x|
62 if x.is_symlink() {
63 Some(x.read_link().expect(format!("Failed to follow symlink from {:?}", x).as_str()))
64 }
65 else {
66 Some(x)
67 }
68 )
69 .map(|x| p.join(x))
70 .collect();
71
72 new_layer.append(&mut candidates);
73 }
74
75 new_layer
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use rand::distributions::{Alphanumeric, DistString};
82 use std::fs;
83
84 fn test_setup() -> PathBuf {
85 let test_dir = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
86
87 let mut tdr = std::env::temp_dir();
88 tdr.push(test_dir);
89 _ = fs::create_dir(&tdr);
90
91 tdr
92 }
93
94 fn validate(mut output: Vec<PathBuf>, mut solution: Vec<PathBuf>, test_dir: Option<PathBuf>) {
95 match test_dir {
96 Some(p) => { _ = fs::remove_dir_all(p); }
97 None => ()
98 }
99
100 output.sort_by_key( |k| k.as_os_str().to_owned());
101 solution.sort_by_key( |k| k.as_os_str().to_owned());
102
103 dbg!(&output);
104 dbg!(&solution);
105
106 assert_eq!(output, solution);
107 }
108
109 #[test]
110 fn ending_asterisk() {
111 let tdr = test_setup();
112 let test_path = tdr.clone().join("*");
113
114 let solution: Vec<PathBuf> = vec![
115 tdr.join("A"),
116 tdr.join("B"),
117 ];
118
119 for p in &solution {
120 _ = fs::create_dir(p);
121 }
122
123 validate(
124 resolve(&test_path).unwrap(),
125 solution,
126 Some(tdr)
127 );
128 }
129
130 #[test]
131 fn dot_path() {
132 let tdr = test_setup();
133 let test_path = tdr.clone().join("*.*").join("*");
134
135 _ = fs::create_dir(tdr.join("A.B"));
136 _ = fs::create_dir(tdr.join("A.B").join("C"));
137
138 let solution: Vec<PathBuf> = vec![
139 tdr.join("A.B").join("C")
140 ];
141
142 validate(
143 resolve(&test_path).unwrap(),
144 solution,
145 Some(tdr)
146 )
147 }
148
149 #[test]
150 fn double_asterisk() {
151 let tdr = test_setup();
152 let test_input = tdr.clone().join("*").join("*");
153
154 _ = fs::create_dir(tdr.join("A"));
155 _ = fs::create_dir(tdr.join("B"));
156
157 let solution: Vec<PathBuf> = vec![
158 tdr.join("A").join("C"),
159 tdr.join("B").join("D"),
160 ];
161
162 for p in &solution {
163 _ = fs::create_dir(p);
164 }
165
166 validate(
167 resolve(&test_input).unwrap(),
168 solution,
169 Some(tdr)
170 );
171 }
172
173 #[test]
174 fn not_all_items() {
175 let tdr = test_setup();
176 let test_input = tdr.clone().join("X*");
177
178 let mut paths: Vec<PathBuf> = vec![
179 tdr.join("XA"),
180 tdr.join("X")
181 ];
182
183 let solution = paths.clone();
184
185 paths.push(tdr.join("YB"));
186 paths.push(tdr.join("YX"));
187 for p in paths {
188 _ = fs::create_dir(p);
189 }
190
191 validate(
192 resolve(&test_input).unwrap(),
193 solution,
194 Some(tdr)
195 );
196 }
197
198 #[test]
199 fn double_compound() {
200 let tdr = test_setup();
201 let test_input = tdr.clone().join("X*").join("Y").join("*Z");
202
203 let first_layer: Vec<PathBuf> = vec![
204 tdr.join("X"),
205 tdr.join("XA"),
206 tdr.join("YB"),
207 tdr.join("YX"),
208 ];
209 for p in &first_layer { _ = fs::create_dir(p); }
210
211 let second_layer: Vec<PathBuf> = vec![
212 tdr.join("X").join("Y"),
213 tdr.join("X").join("TY"),
214 tdr.join("XA").join("Y"),
215 tdr.join("YB").join("Y")
216 ];
217 for p in &second_layer { _ = fs::create_dir(p); }
218
219 let mut third_layer: Vec<PathBuf> = vec![
220 tdr.join("X").join("Y").join("Z"),
221 tdr.join("XA").join("Y").join("Z"),
222 tdr.join("XA").join("Y").join("TZ"),
223 tdr.join("XA").join("Y").join("ZAZ"),
224 tdr.join("XA").join("Y").join("ZZZ"),
225 ];
226
227 let solution = third_layer.clone();
228
229 third_layer.push(tdr.join("X").join("Y").join("z"));
230 third_layer.push(tdr.join("X").join("TY").join("Z"));
231 third_layer.push(tdr.join("XA").join("Y").join("ZA"));
232
233 for p in &third_layer { _ = fs::create_dir(p); }
234
235 validate(
236 resolve(&test_input).unwrap(),
237 solution,
238 Some(tdr)
239 );
240 }
241
242 #[test]
243 fn files() {
244 let tdr = test_setup();
245 let test_path = tdr.clone().join("A").join("*.jp*g");
246
247 _ = fs::create_dir(tdr.join("A"));
248 _ = fs::File::create(tdr.join("A").join("a.jpg"));
249 _ = fs::File::create(tdr.join("A").join("b.jpeg"));
250 _ = fs::File::create(tdr.join("A").join("c.jg"));
251
252 let solution: Vec<PathBuf> = vec![
253 tdr.join("A").join("a.jpg"),
254 tdr.join("A").join("b.jpeg")
255 ];
256
257 validate(
258 resolve(&test_path).unwrap(),
259 solution,
260 Some(tdr)
261 );
262 }
263
264 #[cfg(target_family = "unix")]
265 #[test]
266 fn symlinks_unix() {
267 let tdr = test_setup();
268 let test_path = tdr.clone().join("A").join("*").join("*");
269
270 _ = fs::create_dir(tdr.join("A"));
271 _ = fs::create_dir(tdr.join("B"));
272 _ = fs::create_dir(tdr.join("B").join("C"));
273
274 _ = std::os::unix::fs::symlink(
275 tdr.join("B"),
276 tdr.join("A").join("X")
277 );
278
279 let solution: Vec<PathBuf> = vec![tdr.join("B").join("C")];
280
281 validate(
282 resolve(&test_path).unwrap(),
283 solution,
284 Some(tdr)
285 );
286 }
287
288 #[test]
289 fn doc_example() {
290 let tdr = test_setup();
291
292 _ = fs::create_dir(tdr.join("blogs"));
293
294 _ = fs::create_dir(tdr.join("blogs").join("blog_1"));
295 _ = fs::create_dir(tdr.join("blogs").join("blog_1").join("assets"));
296 _ = fs::File::create(tdr.join("blogs").join("blog_1").join("post.txt"));
297 _ = fs::File::create(tdr.join("blogs").join("blog_1").join("assets").join("logo.jpeg"));
298
299 _ = fs::create_dir(tdr.join("blogs").join("blog_2"));
300 _ = fs::create_dir(tdr.join("blogs").join("blog_2").join("assets"));
301 _ = fs::File::create(tdr.join("blogs").join("blog_2").join("post.txt"));
302 _ = fs::File::create(tdr.join("blogs").join("blog_2").join("assets").join("research_notes.txt"));
303
304 _ = fs::create_dir(tdr.join("videos"));
305
306 _ = fs::create_dir(tdr.join("videos").join("video_1"));
307 _ = fs::create_dir(tdr.join("videos").join("video_1").join("assets"));
308 _ = fs::File::create(tdr.join("videos").join("video_1").join("script.txt"));
309 _ = fs::File::create(tdr.join("videos").join("video_1").join("assets").join("logo.jpeg"));
310
311 _ = fs::create_dir(tdr.join("videos").join("video_2"));
312 _ = fs::create_dir(tdr.join("videos").join("video_2").join("assets"));
313 _ = fs::File::create(tdr.join("videos").join("video_2").join("script.txt"));
314 _ = fs::File::create(tdr.join("videos").join("video_2").join("assets").join("sound_effect.wav"));
315
316 _ = fs::create_dir(tdr.join("videos").join("video_3"));
317 _ = fs::create_dir(tdr.join("videos").join("video_3").join("assets"));
318 _ = fs::File::create(tdr.join("videos").join("video_3").join("script.txt"));
319 _ = fs::File::create(tdr.join("videos").join("video_3").join("assets").join("new_logo.png"));
320
321
322 let mut test_path = tdr.clone().join("*").join("*").join("*.txt");
323 let mut solution: Vec<PathBuf> = vec![
324 tdr.join("blogs").join("blog_1").join("post.txt"),
325 tdr.join("blogs").join("blog_2").join("post.txt"),
326 tdr.join("videos").join("video_1").join("script.txt"),
327 tdr.join("videos").join("video_2").join("script.txt"),
328 tdr.join("videos").join("video_3").join("script.txt")
329 ];
330
331 validate(
332 resolve(&test_path).unwrap(),
333 solution,
334 None
335 );
336
337 test_path = tdr.clone().join("*").join("*").join("assets").join("*logo*");
338 solution = vec![
339 tdr.join("blogs").join("blog_1").join("assets").join("logo.jpeg"),
340 tdr.join("videos").join("video_1").join("assets").join("logo.jpeg"),
341 tdr.join("videos").join("video_3").join("assets").join("new_logo.png")
342 ];
343
344 validate(
345 resolve(&test_path).unwrap(),
346 solution,
347 None
348 );
349
350 test_path = tdr.clone().join("*").join("*_1").join("assets").join("*logo*");
351 solution = vec![
352 tdr.join("blogs").join("blog_1").join("assets").join("logo.jpeg"),
353 tdr.join("videos").join("video_1").join("assets").join("logo.jpeg")
354 ];
355
356 validate(
357 resolve(&test_path).unwrap(),
358 solution,
359 None
360 );
361
362 _ = fs::create_dir(tdr.join("presentations"));
363 _ = fs::create_dir(tdr.join("presentations").join("presentation_1"));
364 _ = fs::create_dir(tdr.join("presentations").join("presentation_1").join("assets"));
365 _ = fs::File::create(tdr.join("presentations").join("presentation_1").join("assets").join("logo.jpeg"));
366
367 solution = vec![
368 tdr.join("blogs").join("blog_1").join("assets").join("logo.jpeg"),
369 tdr.join("videos").join("video_1").join("assets").join("logo.jpeg"),
370 tdr.join("presentations").join("presentation_1").join("assets").join("logo.jpeg")
371 ];
372
373 validate(
374 resolve(&test_path).unwrap(),
375 solution,
376 Some(tdr)
377 );
378 }
379}