1use crate::cli::file::FileKind;
2use crate::config::Config;
3use crate::error::{MyError, MyResult};
4use crate::fs::entry::Entry;
5use crate::fs::file::File;
6use crate::fs::flags::FileFlags;
7use crate::fs::system::System;
8use crate::fs::total::Total;
9use crate::git::cache::GitCache;
10use crate::regex;
11use crate::zip::wrapper::ZipKind;
12use chrono::{DateTime, TimeZone, Utc};
13use glob::{MatchOptions, Pattern};
14use multimap::MultiMap;
15use path_clean::PathClean;
16use std::cell::RefCell;
17use std::collections::{BTreeMap, BTreeSet};
18use std::ffi::OsStr;
19#[cfg(windows)]
20use std::path::MAIN_SEPARATOR_STR;
21use std::path::{Component, Path, PathBuf};
22use std::rc::Rc;
23use std::time::SystemTime;
24
25#[allow(dead_code)]
26pub struct Finder<'a, S: System> {
27 config: &'a Config,
28 system: &'a S,
29 current: PathBuf,
30 options: MatchOptions,
31 start_time: Option<DateTime<Utc>>,
32 git_cache: Option<Rc<GitCache>>,
33 git_bash: bool,
34}
35
36impl<'a, S: System> Finder<'a, S> {
38 pub fn new<Tz: TimeZone>(
39 config: &'a Config,
40 system: &'a S,
41 zone: &Tz,
42 current: PathBuf,
43 git_bash: bool,
44 ) -> Self {
45 let options = Self::match_options(config);
46 let start_time = config.start_time(zone);
47 let git_cache = config.filter_git().map(GitCache::new).map(Rc::new);
48 Self {
49 config,
50 system,
51 current,
52 options,
53 start_time,
54 git_cache,
55 git_bash,
56 }
57 }
58
59 #[cfg(windows)]
60 fn match_options(config: &Config) -> MatchOptions {
61 let mut options = MatchOptions::new();
62 options.case_sensitive = config.case_sensitive().unwrap_or(false);
63 options
64 }
65
66 #[cfg(not(windows))]
67 fn match_options(config: &Config) -> MatchOptions {
68 let mut options = MatchOptions::new();
69 options.case_sensitive = config.case_sensitive().unwrap_or(true);
70 options
71 }
72
73 pub fn find_files(&self) -> MyResult<Vec<File>> {
74 let files = RefCell::new(BTreeSet::new());
75 let tasks = self.group_tasks()?;
76 for ((abs_root, rel_root), patterns) in tasks.iter_all() {
77 self.find_entries(&files, abs_root, rel_root, patterns)?;
78 self.find_parents(&files, abs_root, rel_root)?;
79 }
80 let files = files.into_inner().into_iter().collect();
81 Ok(files)
82 }
83
84 pub fn create_total(&self, files: &Vec<File>) -> Total {
85 Total::from_files(self.start_time, self.config, files)
86 }
87
88 fn group_tasks(&self) -> MyResult<MultiMap<(PathBuf, PathBuf), Pattern>> {
89 let mut tasks = MultiMap::new();
90 for pattern in self.config.patterns() {
91 if let Some((abs_root, rel_root, filename)) = self.parse_pattern(pattern) {
92 let pattern = Pattern::new(&filename).map_err(|e| (e, &filename))?;
93 tasks.insert((abs_root, rel_root), pattern);
94 }
95 }
96 Ok(tasks)
97 }
98
99 #[cfg(windows)]
100 fn parse_pattern(&self, pattern: &str) -> Option<(PathBuf, PathBuf, String)> {
101 if self.git_bash {
102 let drive_regex = regex!(r#"^/([A-Za-z])/(.+)$"#);
103 if let Some(captures) = drive_regex.captures(pattern) {
104 let drive = captures.get(1).unwrap().as_str().to_uppercase();
105 let path = captures.get(2).unwrap().as_str().replace("/", MAIN_SEPARATOR_STR);
106 let pattern = format!("{}:{}{}", drive, MAIN_SEPARATOR_STR, path);
107 self.split_pattern(&pattern)
108 } else {
109 let pattern = pattern.replace("/", MAIN_SEPARATOR_STR);
110 self.split_pattern(&pattern)
111 }
112 } else {
113 self.split_pattern(pattern)
114 }
115 }
116
117 #[cfg(not(windows))]
118 fn parse_pattern(&self, pattern: &str) -> Option<(PathBuf, PathBuf, String)> {
119 self.split_pattern(pattern)
120 }
121
122 fn split_pattern(&self, pattern: &str) -> Option<(PathBuf, PathBuf, String)> {
123 let rel_root = PathBuf::from(pattern);
124 let abs_root = self.current.join(&rel_root).clean();
125 if requires_wildcard(&rel_root, self.config.zip_expand()) {
126 let name = String::from("*");
127 return Some((abs_root, rel_root, name));
128 }
129 if let Some(mut name) = find_name(&abs_root) {
130 if let Some(abs_root) = find_parent(&abs_root) {
131 if let Some(rel_root) = find_parent(&rel_root) {
132 if name.starts_with(".") {
133 name = format!("*{name}");
134 }
135 return Some((abs_root, rel_root, name));
136 }
137 }
138 }
139 None
140 }
141
142 fn find_entries(
143 &self,
144 files: &RefCell<BTreeSet<File>>,
145 abs_root: &Path,
146 rel_root: &Path,
147 patterns: &Vec<Pattern>,
148 ) -> MyResult<()> {
149 let rel_depth = count_components(rel_root);
150 let git_cache = self.git_cache.as_ref().map(Rc::clone);
151 self.system.walk_entries(abs_root, rel_root, git_cache, &|entry| {
152 match entry {
153 Ok(entry) => {
154 self.insert_file(
155 files,
156 entry,
157 abs_root,
158 rel_root,
159 rel_depth,
160 patterns,
161 );
162 }
163 Err(error) => if !self.config.quiet() {
164 error.print_error();
165 }
166 }
167 })
168 }
169
170 fn insert_file(
171 &self,
172 files: &RefCell<BTreeSet<File>>,
173 entry: &dyn Entry,
174 abs_root: &Path,
175 rel_root: &Path,
176 rel_depth: usize,
177 patterns: &Vec<Pattern>,
178 ) {
179 match self.create_file(entry, abs_root, rel_root, rel_depth, patterns) {
180 Ok(file) => if let Some(file) = file {
181 if !self.config.sort_name() || self.config.show_indent() || (file.file_type != FileKind::Dir) {
182 files.borrow_mut().insert(file);
183 }
184 }
185 Err(error) => if !self.config.quiet() {
186 error.print_error();
187 }
188 }
189 }
190
191 fn create_file(
192 &self,
193 entry: &dyn Entry,
194 abs_root: &Path,
195 rel_root: &Path,
196 rel_depth: usize,
197 patterns: &Vec<Pattern>,
198 ) -> MyResult<Option<File>> {
199 if let Some(name) = entry.file_name().to_str() {
200 if !patterns.iter().any(|p| p.matches_with(name, self.options)) {
201 return Ok(None);
202 }
203 if let Some(depth) = self.config.min_depth() {
204 if entry.file_depth() < depth {
205 return Ok(None);
206 }
207 }
208 let file_type = FileKind::from_entry(self.system, entry);
209 if let Some(filter_types) = self.config.filter_types() {
210 if !filter_types.contains(&file_type) {
211 return Ok(None);
212 }
213 }
214 if let FileKind::Link(_) = file_type {
215 let link_path = self.system.read_link(entry)?;
216 if let Some(link_path) = link_path {
217 return if let Some(link_entry) = self.follow_link(entry.file_path(), &link_path) {
218 entry.copy_metadata(link_entry.as_ref().as_ref());
219 let link_path = link_entry.file_path().to_path_buf();
220 let link_type = FileKind::from_entry(self.system, link_entry.as_ref().as_ref());
221 self.create_inner(
222 entry,
223 abs_root,
224 rel_root,
225 rel_depth,
226 file_type,
227 Some((link_path, link_type)),
228 )
229 } else {
230 entry.reset_metadata();
231 self.create_inner(
232 entry,
233 abs_root,
234 rel_root,
235 rel_depth,
236 FileKind::Link(false),
237 Some((link_path, FileKind::Link(false))),
238 )
239 }
240 }
241 }
242 return self.create_inner(
243 entry,
244 abs_root,
245 rel_root,
246 rel_depth,
247 file_type,
248 None,
249 );
250 }
251 Ok(None)
252 }
253
254 fn follow_link(&self, file_path: &Path, link_path: &Path) -> Option<Rc<Box<dyn Entry>>> {
255 if let Some(file_parent) = file_path.parent() {
256 let link_path = file_parent.join(link_path);
257 return self.system.get_entry(&link_path).ok();
258 }
259 None
260 }
261
262 fn create_inner(
263 &self,
264 entry: &dyn Entry,
265 abs_root: &Path,
266 rel_root: &Path,
267 rel_depth: usize,
268 file_type: FileKind,
269 link_data: Option<(PathBuf, FileKind)>,
270 ) -> MyResult<Option<File>> {
271 let file_time = DateTime::<Utc>::from(entry.file_time());
272 if let Some(start_time) = self.start_time {
273 if file_time < start_time {
274 return Ok(None);
275 }
276 }
277 let abs_path = entry.file_path();
278 let git_flags = if let Some(git_cache) = &self.git_cache {
279 if entry.file_flags() != FileFlags::File {
280 return Ok(None);
281 }
282 let flags = git_cache.test_allowed(abs_path)?;
283 if flags.is_none() {
284 return Ok(None);
285 }
286 flags
287 } else {
288 None
289 };
290 if let Some(rel_path) = create_relative(abs_root, rel_root, abs_path) {
291 if let Some(abs_dir) = select_parent(abs_path, file_type) {
292 if let Some(rel_dir) = select_parent_from_owned(rel_path, file_type) {
293 let file_depth = entry.file_depth() + rel_depth;
294 let inner_depth = entry.inner_depth();
295 let file_name = select_name(abs_path, file_type).unwrap_or_default();
296 let file_ext = find_extension(abs_path, file_type);
297 let file_size = select_size(entry, &link_data, file_type);
298 let mut file = File::new(abs_dir, rel_dir, file_depth, inner_depth, file_name, file_ext, file_type)
299 .with_mode(entry.file_mode())
300 .with_size(file_size)
301 .with_time(file_time)
302 .with_git(git_flags);
303 #[cfg(unix)]
304 if self.config.show_owner() {
305 let user = self.system.find_user(entry.owner_uid());
306 let group = self.system.find_group(entry.owner_gid());
307 file = file.with_owner(user, group);
308 }
309 if self.config.show_crc() {
310 if let FileKind::File(_) | FileKind::Link(_) = file_type {
311 let file_crc = self.system.read_crc(entry);
312 file = file.with_crc(file_crc);
313 }
314 }
315 if self.config.show_sig() {
316 if let FileKind::File(_) | FileKind::Link(_) = file_type {
317 let file_sig = self.system.read_sig(entry);
318 file = file.with_sig(file_sig);
319 }
320 }
321 #[cfg(windows)]
322 if self.config.win_ver() {
323 if let Some(file_ver) = self.system.read_version(entry) {
324 file = file.with_version(file_ver);
325 }
326 }
327 if let Some((link_path, link_type)) = link_data {
328 file = file.with_link(link_path, link_type);
329 }
330 return Ok(Some(file));
331 }
332 }
333 }
334 Ok(None)
335 }
336
337 fn find_parents(
338 &self,
339 files: &RefCell<BTreeSet<File>>,
340 abs_root: &Path,
341 rel_root: &Path,
342 ) -> MyResult<()> {
343 if self.config.show_indent() {
344 let parents = find_parents(files)?;
345 for (abs_path, file_depth) in parents.into_iter() {
346 self.insert_parent(files, abs_root, rel_root, abs_path, file_depth);
347 }
348 }
349 Ok(())
350 }
351
352 #[allow(unused_mut)]
353 fn insert_parent(
354 &self,
355 files: &RefCell<BTreeSet<File>>,
356 abs_root: &Path,
357 rel_root: &Path,
358 abs_path: PathBuf,
359 file_depth: usize,
360 ) {
361 if let Some(rel_path) = create_relative(abs_root, rel_root, &abs_path) {
362 if let Some(abs_dir) = select_parent(&abs_path, FileKind::Dir) {
363 if let Some(rel_dir) = select_parent_from_owned(rel_path, FileKind::Dir) {
364 let sys_entry = self.system.get_entry(&abs_path).ok();
365 let inner_depth = sys_entry.as_ref().and_then(|e| e.inner_depth());
366 let file_mode = sys_entry.as_ref().map(|e| e.file_mode()).unwrap_or_default();
367 let file_time = sys_entry.as_ref().map(|e| e.file_time()).unwrap_or(SystemTime::UNIX_EPOCH);
368 let file_name = String::from("");
369 let file_ext = String::from("");
370 let mut file = File::new(abs_dir, rel_dir, file_depth, inner_depth, file_name, file_ext, FileKind::Dir)
371 .with_mode(file_mode)
372 .with_time(DateTime::<Utc>::from(file_time));
373 #[cfg(unix)]
374 if self.config.show_owner() {
375 let uid = sys_entry.as_ref().map(|e| e.owner_uid()).unwrap_or_default();
376 let gid = sys_entry.as_ref().map(|e| e.owner_gid()).unwrap_or_default();
377 let user = self.system.find_user(uid);
378 let group = self.system.find_group(gid);
379 file = file.with_owner(user, group);
380 }
381 files.borrow_mut().insert(file);
382 }
383 }
384 }
385 }
386}
387
388fn requires_wildcard(root: &Path, zip_expand: bool) -> bool {
389 let wildcard_regex = regex!(r"(^\.+$|[\\/]\.*$)");
390 if let Some(root) = root.to_str() {
391 if wildcard_regex.is_match(root) {
392 return true;
393 }
394 }
395 ZipKind::from_path(root, zip_expand).is_some()
396}
397
398pub fn count_components(path: &Path) -> usize {
399 path
400 .components()
401 .filter(|c| matches!(c, Component::Normal(_)))
402 .count()
403}
404
405fn find_parents(files: &RefCell<BTreeSet<File>>) -> MyResult<BTreeMap<PathBuf, usize>> {
406 let mut parents = BTreeMap::new();
407 for file in files.borrow().iter() {
408 let file_depth = file.file_depth + file.file_type.dir_offset();
409 find_ancestors(&mut parents, &file.abs_dir, file_depth)?;
410 }
411 Ok(parents)
412}
413
414fn find_ancestors(
415 parents: &mut BTreeMap<PathBuf, usize>,
416 abs_path: &Path,
417 file_depth: usize,
418) -> MyResult<()> {
419 if let Some(file_depth) = file_depth.checked_sub(1) {
420 if file_depth > 0 {
421 if let Some(old_depth) = parents.insert(PathBuf::from(abs_path), file_depth) {
422 if old_depth != file_depth {
423 let error = format!("Inconsistent depth: {}", abs_path.display());
424 return Err(MyError::Text(error));
425 }
426 } else {
427 if let Some(abs_path) = abs_path.parent() {
428 find_ancestors(parents, abs_path, file_depth)?;
429 }
430 }
431 }
432 }
433 Ok(())
434}
435
436fn create_relative(
437 abs_root: &Path,
438 rel_root: &Path,
439 abs_path: &Path,
440) -> Option<PathBuf> {
441 let mut abs_root = PathBuf::from(abs_root);
442 let mut rel_path = PathBuf::new();
443 loop {
444 if let Ok(path) = abs_path.strip_prefix(&abs_root) {
445 rel_path.push(path);
446 return Some(rel_root.join(rel_path).clean());
447 }
448 if !abs_root.pop() {
449 return None;
450 }
451 rel_path.push("..");
452 }
453}
454
455fn select_parent_from_owned(path: PathBuf, file_type: FileKind) -> Option<PathBuf> {
456 if file_type == FileKind::Dir {
457 Some(path)
458 } else {
459 find_parent(&path)
460 }
461}
462
463fn select_parent(path: &Path, file_type: FileKind) -> Option<PathBuf> {
464 if file_type == FileKind::Dir {
465 Some(PathBuf::from(path))
466 } else {
467 find_parent(path)
468 }
469}
470
471fn select_name(path: &Path, file_type: FileKind) -> Option<String> {
472 if file_type == FileKind::Dir {
473 Some(String::from(""))
474 } else {
475 find_name(path)
476 }
477}
478
479fn find_parent(path: &Path) -> Option<PathBuf> {
480 path.parent().map(PathBuf::from)
481}
482
483fn find_name(path: &Path) -> Option<String> {
484 path.file_name().and_then(OsStr::to_str).map(String::from)
485}
486
487fn find_extension(path: &Path, file_type: FileKind) -> String {
488 match file_type {
489 FileKind::File(_) | FileKind::Link(_) => path.extension()
490 .and_then(OsStr::to_str)
491 .map(str::to_ascii_lowercase)
492 .map(|ext| format!(".{ext}"))
493 .unwrap_or_default(),
494 _ => String::default(),
495 }
496}
497
498fn select_size(
499 entry: &dyn Entry,
500 link_data: &Option<(PathBuf, FileKind)>,
501 file_type: FileKind,
502) -> u64 {
503 if file_type == FileKind::Dir {
504 return 0;
505 }
506 if let Some((_, link_type)) = link_data {
507 if *link_type == FileKind::Dir {
508 return 0;
509 }
510 }
511 entry.file_size()
512}
513
514#[cfg(test)]
515mod tests {
516 use crate::cli::file::{ExecKind, FileKind};
517 use crate::cli::recent::RecentKind;
518 use crate::config::Config;
519 use crate::finder::{create_relative, requires_wildcard, Finder};
520 use crate::fs::file::File;
521 use crate::fs::system::tests::MockSystem;
522 use chrono::{DateTime, TimeZone, Utc};
523 use pretty_assertions::assert_eq;
524 use std::path::PathBuf;
525
526 #[test]
527 fn test_dir_requires_wildcard() {
528 assert_eq!(true, test_wildcard(".", false));
529 assert_eq!(true, test_wildcard("..", false));
530 assert_eq!(true, test_wildcard("/", false));
531 assert_eq!(true, test_wildcard("/path/to/dir/", false));
532 assert_eq!(true, test_wildcard("/path/to/dir/.", false));
533 assert_eq!(true, test_wildcard("/path/to/dir/..", false));
534 assert_eq!(true, test_wildcard(r"\", false));
535 assert_eq!(true, test_wildcard(r"\path\to\dir\", false));
536 assert_eq!(true, test_wildcard(r"\path\to\dir\.", false));
537 assert_eq!(true, test_wildcard(r"\path\to\dir\..", false));
538 }
539
540 #[test]
541 fn test_file_requires_wildcard() {
542 assert_eq!(false, test_wildcard("lower", false));
543 assert_eq!(false, test_wildcard("lower.zip", false));
544 assert_eq!(false, test_wildcard("lower.7z", false));
545 assert_eq!(false, test_wildcard("lower.tar", false));
546 assert_eq!(false, test_wildcard("UPPER", false));
547 assert_eq!(false, test_wildcard("UPPER.ZIP", false));
548 assert_eq!(false, test_wildcard("UPPER.7Z", false));
549 assert_eq!(false, test_wildcard("UPPER.TAR", false));
550 assert_eq!(false, test_wildcard("/path/to/dir/lower", false));
551 assert_eq!(false, test_wildcard("/path/to/dir/lower.zip", false));
552 assert_eq!(false, test_wildcard("/path/to/dir/lower.7z", false));
553 assert_eq!(false, test_wildcard("/path/to/dir/lower.tar", false));
554 assert_eq!(false, test_wildcard("/path/to/dir/UPPER", false));
555 assert_eq!(false, test_wildcard("/path/to/dir/UPPER.ZIP", false));
556 assert_eq!(false, test_wildcard("/path/to/dir/UPPER.7Z", false));
557 assert_eq!(false, test_wildcard("/path/to/dir/UPPER.TAR", false));
558 assert_eq!(false, test_wildcard(r"\path\to\dir\lower", false));
559 assert_eq!(false, test_wildcard(r"\path\to\dir\lower.zip", false));
560 assert_eq!(false, test_wildcard(r"\path\to\dir\lower.7z", false));
561 assert_eq!(false, test_wildcard(r"\path\to\dir\lower.tar", false));
562 assert_eq!(false, test_wildcard(r"\path\to\dir\UPPER", false));
563 assert_eq!(false, test_wildcard(r"\path\to\dir\UPPER.ZIP", false));
564 assert_eq!(false, test_wildcard(r"\path\to\dir\UPPER.7Z", false));
565 assert_eq!(false, test_wildcard(r"\path\to\dir\UPPER.TAR", false));
566 }
567
568 #[test]
569 fn test_archive_requires_wildcard() {
570 assert_eq!(false, test_wildcard("lower", true));
571 assert_eq!(true, test_wildcard("lower.zip", true));
572 assert_eq!(true, test_wildcard("lower.7z", true));
573 assert_eq!(true, test_wildcard("lower.tar", true));
574 assert_eq!(false, test_wildcard("UPPER", true));
575 assert_eq!(true, test_wildcard("UPPER.ZIP", true));
576 assert_eq!(true, test_wildcard("UPPER.7Z", true));
577 assert_eq!(true, test_wildcard("UPPER.TAR", true));
578 assert_eq!(false, test_wildcard("/path/to/dir/lower", true));
579 assert_eq!(true, test_wildcard("/path/to/dir/lower.zip", true));
580 assert_eq!(true, test_wildcard("/path/to/dir/lower.7z", true));
581 assert_eq!(true, test_wildcard("/path/to/dir/lower.tar", true));
582 assert_eq!(false, test_wildcard("/path/to/dir/UPPER", true));
583 assert_eq!(true, test_wildcard("/path/to/dir/UPPER.ZIP", true));
584 assert_eq!(true, test_wildcard("/path/to/dir/UPPER.7Z", true));
585 assert_eq!(true, test_wildcard("/path/to/dir/UPPER.TAR", true));
586 assert_eq!(false, test_wildcard(r"\path\to\dir\lower", true));
587 assert_eq!(true, test_wildcard(r"\path\to\dir\lower.zip", true));
588 assert_eq!(true, test_wildcard(r"\path\to\dir\lower.7z", true));
589 assert_eq!(true, test_wildcard(r"\path\to\dir\lower.tar", true));
590 assert_eq!(false, test_wildcard(r"\path\to\dir\UPPER", true));
591 assert_eq!(true, test_wildcard(r"\path\to\dir\UPPER.ZIP", true));
592 assert_eq!(true, test_wildcard(r"\path\to\dir\UPPER.7Z", true));
593 assert_eq!(true, test_wildcard(r"\path\to\dir\UPPER.TAR", true));
594 }
595
596 fn test_wildcard(root: &str, zip_expand: bool) -> bool {
597 let root = PathBuf::from(root);
598 requires_wildcard(&root, zip_expand)
599 }
600
601 #[test]
602 #[cfg(all(windows, any()))]
603 fn test_counts_components() {
604 use crate::finder::count_components;
605 assert_eq!(0, count_components(&PathBuf::from(r"")));
606 assert_eq!(0, count_components(&PathBuf::from(r"..")));
607 assert_eq!(1, count_components(&PathBuf::from(r"..\dir")));
608 assert_eq!(2, count_components(&PathBuf::from(r"..\dir\subdir")));
609 assert_eq!(0, count_components(&PathBuf::from(r".")));
610 assert_eq!(1, count_components(&PathBuf::from(r".\dir")));
611 assert_eq!(2, count_components(&PathBuf::from(r".\dir\subdir")));
612 assert_eq!(1, count_components(&PathBuf::from(r"dir")));
613 assert_eq!(2, count_components(&PathBuf::from(r"dir\subdir")));
614 assert_eq!(1, count_components(&PathBuf::from(r"\dir")));
615 assert_eq!(2, count_components(&PathBuf::from(r"\dir\subdir")));
616 assert_eq!(1, count_components(&PathBuf::from(r"D:\dir")));
617 assert_eq!(2, count_components(&PathBuf::from(r"D:\dir\subdir")));
618 assert_eq!(1, count_components(&PathBuf::from(r"\\unc\dir")));
619 assert_eq!(2, count_components(&PathBuf::from(r"\\unc\dir\subdir")));
620 }
621
622 #[test]
623 #[cfg(not(windows))]
624 fn test_counts_components() {
625 use crate::finder::count_components;
626 assert_eq!(0, count_components(&PathBuf::from("")));
627 assert_eq!(0, count_components(&PathBuf::from("..")));
628 assert_eq!(1, count_components(&PathBuf::from("../dir")));
629 assert_eq!(2, count_components(&PathBuf::from("../dir/subdir")));
630 assert_eq!(0, count_components(&PathBuf::from(".")));
631 assert_eq!(1, count_components(&PathBuf::from("./dir")));
632 assert_eq!(2, count_components(&PathBuf::from("./dir/subdir")));
633 assert_eq!(1, count_components(&PathBuf::from("dir")));
634 assert_eq!(2, count_components(&PathBuf::from("dir/subdir")));
635 assert_eq!(1, count_components(&PathBuf::from("/dir")));
636 assert_eq!(2, count_components(&PathBuf::from("/dir/subdir")));
637 }
638
639 #[test]
640 fn test_creates_relative_paths() {
641 assert_eq!(Some(PathBuf::from("..")), test_relative("/root"));
642 assert_eq!(Some(PathBuf::from("../dir")), test_relative("/root/dir"));
643 assert_eq!(Some(PathBuf::from("../dir/subdir")), test_relative("/root/dir/subdir"));
644 assert_eq!(Some(PathBuf::from("../dir2")), test_relative("/root/dir2"));
645 assert_eq!(Some(PathBuf::from("../dir2/subdir")), test_relative("/root/dir2/subdir"));
646 assert_eq!(Some(PathBuf::from("../..")), test_relative("/"));
647 assert_eq!(Some(PathBuf::from("../../root2/dir")), test_relative("/root2/dir"));
648 assert_eq!(Some(PathBuf::from("../../root2/dir/subdir")), test_relative("/root2/dir/subdir"));
649 }
650
651 fn test_relative(abs_path: &str) -> Option<PathBuf> {
652 let abs_root = PathBuf::from("/root/dir");
653 let rel_root = PathBuf::from("../dir");
654 let abs_path = PathBuf::from(abs_path);
655 create_relative(&abs_root, &rel_root, &abs_path)
656 }
657
658 #[test]
659 fn test_parses_file_attributes_no_indent_in_root_directory() {
660 let config = Config::default()
661 .with_patterns(vec!["*"])
662 .with_recurse_all(true);
663 let system = create_system(&config, create_entries);
664 let finder = create_finder(&config, &system);
665 let files = find_files(&finder);
666 assert_eq!(10, files.len());
667 assert_data(files.get(0), 1, FileKind::File(ExecKind::User), 0o744, 100, 2023, 1, 1);
668 assert_data(files.get(1), 1, FileKind::Dir, 0o755, 0, 2023, 2, 2);
669 assert_data(files.get(2), 2, FileKind::Link(true), 0o755, 0, 2023, 3, 3);
670 assert_data(files.get(3), 2, FileKind::Link(true), 0o644, 500, 2023, 5, 5);
671 assert_data(files.get(4), 2, FileKind::Link(false), 0o644, 0, 1970, 1, 1);
672 assert_data(files.get(5), 2, FileKind::Dir, 0o755, 0, 2023, 3, 3);
673 assert_data(files.get(6), 3, FileKind::File(ExecKind::None), 0o644, 400, 2023, 4, 4);
674 assert_data(files.get(7), 3, FileKind::File(ExecKind::None), 0o644, 500, 2023, 5, 5);
675 assert_data(files.get(8), 3, FileKind::File(ExecKind::None), 0o644, 600, 2023, 6, 6);
676 assert_data(files.get(9), 3, FileKind::File(ExecKind::None), 0o644, 700, 2023, 7, 7);
677 assert_path(files.get(0), "/root", "", "archive.sh", ".sh");
678 assert_path(files.get(1), "/root/dir", "dir", "", "");
679 assert_path(files.get(2), "/root/dir", "dir", "link1", "");
680 assert_path(files.get(3), "/root/dir", "dir", "link2", "");
681 assert_path(files.get(4), "/root/dir", "dir", "link3", "");
682 assert_path(files.get(5), "/root/dir/subdir", "dir/subdir", "", "");
683 assert_path(files.get(6), "/root/dir/subdir", "dir/subdir", "alpha.csv", ".csv");
684 assert_path(files.get(7), "/root/dir/subdir", "dir/subdir", "alpha.txt", ".txt");
685 assert_path(files.get(8), "/root/dir/subdir", "dir/subdir", "beta.csv", ".csv");
686 assert_path(files.get(9), "/root/dir/subdir", "dir/subdir", "beta.txt", ".txt");
687 assert_link(files.get(0), None);
688 assert_link(files.get(1), None);
689 assert_link(files.get(2), Some(("/root/dir/subdir", FileKind::Dir)));
690 assert_link(files.get(3), Some(("/root/dir/subdir/alpha.txt", FileKind::File(ExecKind::None))));
691 assert_link(files.get(4), Some(("/etc/missing.txt", FileKind::Link(false))));
692 assert_link(files.get(5), None);
693 assert_link(files.get(6), None);
694 assert_link(files.get(7), None);
695 assert_link(files.get(8), None);
696 assert_link(files.get(9), None);
697 }
698
699 #[test]
700 fn test_parses_file_attributes_no_indent_in_branch_directory() {
701 let config = Config::default()
702 .with_patterns(vec!["dir/*"])
703 .with_recurse_all(true);
704 let system = create_system(&config, create_entries);
705 let finder = create_finder(&config, &system);
706 let files = find_files(&finder);
707 assert_eq!(8, files.len());
708 assert_data(files.get(0), 2, FileKind::Link(true), 0o755, 0, 2023, 3, 3);
709 assert_data(files.get(1), 2, FileKind::Link(true), 0o644, 500, 2023, 5, 5);
710 assert_data(files.get(2), 2, FileKind::Link(false), 0o644, 0, 1970, 1, 1);
711 assert_data(files.get(3), 2, FileKind::Dir, 0o755, 0, 2023, 3, 3);
712 assert_data(files.get(4), 3, FileKind::File(ExecKind::None), 0o644, 400, 2023, 4, 4);
713 assert_data(files.get(5), 3, FileKind::File(ExecKind::None), 0o644, 500, 2023, 5, 5);
714 assert_data(files.get(6), 3, FileKind::File(ExecKind::None), 0o644, 600, 2023, 6, 6);
715 assert_data(files.get(7), 3, FileKind::File(ExecKind::None), 0o644, 700, 2023, 7, 7);
716 assert_path(files.get(0), "/root/dir", "dir", "link1", "");
717 assert_path(files.get(1), "/root/dir", "dir", "link2", "");
718 assert_path(files.get(2), "/root/dir", "dir", "link3", "");
719 assert_path(files.get(3), "/root/dir/subdir", "dir/subdir", "", "");
720 assert_path(files.get(4), "/root/dir/subdir", "dir/subdir", "alpha.csv", ".csv");
721 assert_path(files.get(5), "/root/dir/subdir", "dir/subdir", "alpha.txt", ".txt");
722 assert_path(files.get(6), "/root/dir/subdir", "dir/subdir", "beta.csv", ".csv");
723 assert_path(files.get(7), "/root/dir/subdir", "dir/subdir", "beta.txt", ".txt");
724 assert_link(files.get(0), Some(("/root/dir/subdir", FileKind::Dir)));
725 assert_link(files.get(1), Some(("/root/dir/subdir/alpha.txt", FileKind::File(ExecKind::None))));
726 assert_link(files.get(2), Some(("/etc/missing.txt", FileKind::Link(false))));
727 assert_link(files.get(3), None);
728 assert_link(files.get(4), None);
729 assert_link(files.get(5), None);
730 assert_link(files.get(6), None);
731 assert_link(files.get(7), None);
732 }
733
734 #[test]
735 fn test_parses_file_attributes_with_indent_in_root_directory() {
736 let config = Config::default()
737 .with_patterns(vec!["*"])
738 .with_recurse_all(true)
739 .with_show_indent(true)
740 .with_filter_types(vec![
741 FileKind::File(ExecKind::None),
742 FileKind::File(ExecKind::User),
743 FileKind::File(ExecKind::Other),
744 FileKind::Link(false),
745 FileKind::Link(true),
746 ]);
747 let system = create_system(&config, create_entries);
748 let finder = create_finder(&config, &system);
749 let files = find_files(&finder);
750 assert_eq!(10, files.len());
751 assert_data(files.get(0), 1, FileKind::File(ExecKind::User), 0o744, 100, 2023, 1, 1);
752 assert_data(files.get(1), 1, FileKind::Dir, 0o755, 0, 2023, 2, 2);
753 assert_data(files.get(2), 2, FileKind::Link(true), 0o755, 0, 2023, 3, 3);
754 assert_data(files.get(3), 2, FileKind::Link(true), 0o644, 500, 2023, 5, 5);
755 assert_data(files.get(4), 2, FileKind::Link(false), 0o644, 0, 1970, 1, 1);
756 assert_data(files.get(5), 2, FileKind::Dir, 0o755, 0, 2023, 3, 3);
757 assert_data(files.get(6), 3, FileKind::File(ExecKind::None), 0o644, 400, 2023, 4, 4);
758 assert_data(files.get(7), 3, FileKind::File(ExecKind::None), 0o644, 500, 2023, 5, 5);
759 assert_data(files.get(8), 3, FileKind::File(ExecKind::None), 0o644, 600, 2023, 6, 6);
760 assert_data(files.get(9), 3, FileKind::File(ExecKind::None), 0o644, 700, 2023, 7, 7);
761 assert_path(files.get(0), "/root", "", "archive.sh", ".sh");
762 assert_path(files.get(1), "/root/dir", "dir", "", "");
763 assert_path(files.get(2), "/root/dir", "dir", "link1", "");
764 assert_path(files.get(3), "/root/dir", "dir", "link2", "");
765 assert_path(files.get(4), "/root/dir", "dir", "link3", "");
766 assert_path(files.get(5), "/root/dir/subdir", "dir/subdir", "", "");
767 assert_path(files.get(6), "/root/dir/subdir", "dir/subdir", "alpha.csv", ".csv");
768 assert_path(files.get(7), "/root/dir/subdir", "dir/subdir", "alpha.txt", ".txt");
769 assert_path(files.get(8), "/root/dir/subdir", "dir/subdir", "beta.csv", ".csv");
770 assert_path(files.get(9), "/root/dir/subdir", "dir/subdir", "beta.txt", ".txt");
771 assert_link(files.get(0), None);
772 assert_link(files.get(1), None);
773 assert_link(files.get(2), Some(("/root/dir/subdir", FileKind::Dir)));
774 assert_link(files.get(3), Some(("/root/dir/subdir/alpha.txt", FileKind::File(ExecKind::None))));
775 assert_link(files.get(4), Some(("/etc/missing.txt", FileKind::Link(false))));
776 assert_link(files.get(5), None);
777 assert_link(files.get(6), None);
778 assert_link(files.get(7), None);
779 assert_link(files.get(8), None);
780 assert_link(files.get(9), None);
781 }
782
783 #[test]
784 fn test_finds_multiple_patterns_in_same_directory() {
785 let expected = vec![
786 "/root/dir/subdir/alpha.csv",
787 "/root/dir/subdir/alpha.txt",
788 "/root/dir/subdir/beta.txt",
789 ];
790 let config = Config::default()
791 .with_patterns(vec!["dir/subdir/alpha.*", "dir/subdir/*.txt"])
792 .with_recurse_all(true);
793 let system = create_system(&config, create_entries);
794 let finder = create_finder(&config, &system);
795 let files = find_files(&finder);
796 let paths = convert_paths(files);
797 assert_eq!(expected, paths);
798 }
799
800 #[test]
801 fn test_finds_multiple_patterns_in_diff_directories() {
802 let expected = vec![
803 "/root/dir/subdir/alpha.csv",
804 "/root/dir/subdir/alpha.txt",
805 "/root/dir/subdir/beta.txt",
806 ];
807 let config = Config::default()
808 .with_patterns(vec!["dir/alpha.*", "dir/subdir/*.txt"])
809 .with_recurse_all(true);
810 let system = create_system(&config, create_entries);
811 let finder = create_finder(&config, &system);
812 let files = find_files(&finder);
813 let paths = convert_paths(files);
814 assert_eq!(expected, paths);
815 }
816
817 #[test]
818 fn test_finds_files_if_recurse_no_indent_in_root_directory() {
819 let expected = vec![
820 "/root/dir/subdir/alpha.txt",
821 "/root/dir/subdir/beta.txt",
822 ];
823 let config = Config::default()
824 .with_patterns(vec!["*.txt"])
825 .with_recurse_all(true);
826 let system = create_system(&config, create_entries);
827 let finder = create_finder(&config, &system);
828 let files = find_files(&finder);
829 let paths = convert_paths(files);
830 assert_eq!(expected, paths);
831 }
832
833 #[test]
834 fn test_finds_parents_if_recurse_with_indent_in_root_directory() {
835 let expected = vec![
836 "/root/dir/",
837 "/root/dir/subdir/",
838 "/root/dir/subdir/alpha.txt",
839 "/root/dir/subdir/beta.txt",
840 ];
841 let config = Config::default()
842 .with_patterns(vec!["*.txt"])
843 .with_recurse_all(true)
844 .with_show_indent(true);
845 let system = create_system(&config, create_entries);
846 let finder = create_finder(&config, &system);
847 let files = find_files(&finder);
848 let paths = convert_paths(files);
849 assert_eq!(expected, paths);
850 }
851
852 #[test]
853 fn test_finds_parents_if_recurse_with_indent_in_branch_directory() {
854 let expected = vec![
855 "/root/dir/",
856 "/root/dir/subdir/",
857 "/root/dir/subdir/alpha.txt",
858 "/root/dir/subdir/beta.txt",
859 ];
860 let config = Config::default()
861 .with_patterns(vec!["dir/*.txt"])
862 .with_recurse_all(true)
863 .with_show_indent(true);
864 let system = create_system(&config, create_entries);
865 let finder = create_finder(&config, &system);
866 let files = find_files(&finder);
867 let paths = convert_paths(files);
868 assert_eq!(expected, paths);
869 }
870
871 #[test]
872 fn test_finds_parents_if_recurse_with_indent_in_leaf_directory() {
873 let expected = vec![
874 "/root/dir/",
875 "/root/dir/subdir/",
876 "/root/dir/subdir/alpha.txt",
877 "/root/dir/subdir/beta.txt",
878 ];
879 let config = Config::default()
880 .with_patterns(vec!["dir/subdir/*.txt"])
881 .with_recurse_all(true)
882 .with_show_indent(true);
883 let system = create_system(&config, create_entries);
884 let finder = create_finder(&config, &system);
885 let files = find_files(&finder);
886 let paths = convert_paths(files);
887 assert_eq!(expected, paths);
888 }
889
890 #[test]
891 fn test_hides_directories_if_order_by_name() {
892 let expected = vec![
893 "/root/archive.sh",
894 "/root/dir/link1",
895 "/root/dir/link2",
896 "/root/dir/link3",
897 "/root/dir/subdir/alpha.csv",
898 "/root/dir/subdir/alpha.txt",
899 "/root/dir/subdir/beta.csv",
900 "/root/dir/subdir/beta.txt",
901 ];
902 let config = Config::default()
903 .with_patterns(vec!["*"])
904 .with_recurse_all(true)
905 .with_sort_name(true);
906 let system = create_system(&config, create_entries);
907 let finder = create_finder(&config, &system);
908 let files = find_files(&finder);
909 let paths = convert_paths(files);
910 assert_eq!(expected, paths);
911 }
912
913 #[test]
914 fn test_finds_files_with_bare_filename() {
915 let expected = vec![
916 "/root/dir/subdir/beta.csv",
917 ];
918 let config = Config::default()
919 .with_patterns(vec!["beta.csv"])
920 .with_recurse_all(true);
921 let system = create_system(&config, create_entries);
922 let finder = create_finder(&config, &system);
923 let files = find_files(&finder);
924 let paths = convert_paths(files);
925 assert_eq!(expected, paths);
926 }
927
928 #[test]
929 fn test_finds_files_with_bare_extension() {
930 let expected = vec![
931 "/root/dir/subdir/alpha.csv",
932 "/root/dir/subdir/beta.csv",
933 ];
934 let config = Config::default()
935 .with_patterns(vec![".csv"])
936 .with_recurse_all(true);
937 let system = create_system(&config, create_entries);
938 let finder = create_finder(&config, &system);
939 let files = find_files(&finder);
940 let paths = convert_paths(files);
941 assert_eq!(expected, paths);
942 }
943
944 #[test]
945 fn test_filters_files_by_minimum_depth() {
946 let expected = vec![
947 "/root/dir/link1",
948 "/root/dir/link2",
949 "/root/dir/link3",
950 "/root/dir/subdir/",
951 "/root/dir/subdir/alpha.csv",
952 "/root/dir/subdir/alpha.txt",
953 "/root/dir/subdir/beta.csv",
954 "/root/dir/subdir/beta.txt",
955 ];
956 let config = Config::default()
957 .with_patterns(vec!["*"])
958 .with_min_depth(2);
959 let system = create_system(&config, create_entries);
960 let finder = create_finder(&config, &system);
961 let files = find_files(&finder);
962 let paths = convert_paths(files);
963 assert_eq!(expected, paths);
964 }
965
966 #[test]
967 fn test_filters_files_by_maximum_depth() {
968 let expected = vec![
969 "/root/archive.sh",
970 "/root/dir/",
971 "/root/dir/link1",
972 "/root/dir/link2",
973 "/root/dir/link3",
974 "/root/dir/subdir/",
975 ];
976 let config = Config::default()
977 .with_patterns(vec!["*"])
978 .with_max_depth(2);
979 let system = create_system(&config, create_entries);
980 let finder = create_finder(&config, &system);
981 let files = find_files(&finder);
982 let paths = convert_paths(files);
983 assert_eq!(expected, paths);
984 }
985
986 #[test]
987 fn test_filters_files_by_file_type() {
988 let expected = vec![
989 "/root/archive.sh",
990 "/root/dir/subdir/alpha.csv",
991 "/root/dir/subdir/alpha.txt",
992 "/root/dir/subdir/beta.csv",
993 "/root/dir/subdir/beta.txt",
994 ];
995 let config = Config::default()
996 .with_patterns(vec!["*"])
997 .with_recurse_all(true)
998 .with_filter_types(vec![
999 FileKind::File(ExecKind::None),
1000 FileKind::File(ExecKind::User),
1001 FileKind::File(ExecKind::Other),
1002 ]);
1003 let system = create_system(&config, create_entries);
1004 let finder = create_finder(&config, &system);
1005 let files = find_files(&finder);
1006 let paths = convert_paths(files);
1007 assert_eq!(expected, paths);
1008 }
1009
1010 #[test]
1011 fn test_filters_files_by_recent_time() {
1012 let expected = vec![
1013 "/root/dir/link2",
1014 "/root/dir/subdir/alpha.txt",
1015 "/root/dir/subdir/beta.csv",
1016 "/root/dir/subdir/beta.txt",
1017 ];
1018 let config = Config::default()
1019 .with_patterns(vec!["*"])
1020 .with_recurse_all(true)
1021 .with_curr_time(2024, 1, 1, 0, 0, 0)
1022 .with_filter_recent(RecentKind::Month(8));
1023 let system = create_system(&config, create_entries);
1024 let finder = create_finder(&config, &system);
1025 let files = find_files(&finder);
1026 let paths = convert_paths(files);
1027 assert_eq!(expected, paths);
1028 }
1029
1030 #[test]
1031 fn test_calculates_total_from_files() {
1032 let config = Config::default()
1033 .with_patterns(vec!["*"])
1034 .with_recurse_all(true);
1035 let system = create_system(&config, create_entries);
1036 let finder = create_finder(&config, &system);
1037 let files = find_files(&finder);
1038 let total = finder.create_total(&files);
1039 assert_eq!(700, total.max_size);
1040 assert_eq!(2800, total.total_size);
1041 #[cfg(unix)]
1042 assert_eq!(0, total.user_width);
1043 #[cfg(unix)]
1044 assert_eq!(0, total.group_width);
1045 #[cfg(windows)]
1046 assert_eq!(0, total.ver_width);
1047 assert_eq!(4, total.ext_width);
1048 assert_eq!(8, total.num_files);
1049 assert_eq!(2, total.num_dirs);
1050 }
1051
1052 #[test]
1053 #[cfg(unix)]
1054 fn test_calculates_total_from_files_with_no_owners() {
1055 let config = Config::default()
1056 .with_show_owner(true);
1057 let system = create_system(&config, create_entries);
1058 let finder = create_finder(&config, &system);
1059 let files = find_files(&finder);
1060 let total = finder.create_total(&files);
1061 assert_eq!(1, total.user_width);
1062 assert_eq!(1, total.group_width);
1063 }
1064
1065 #[test]
1066 #[cfg(unix)]
1067 fn test_calculates_total_from_files_with_some_owners() {
1068 let config = Config::default()
1069 .with_patterns(vec!["*"])
1070 .with_recurse_all(true)
1071 .with_show_owner(true);
1072 let system = create_system(&config, create_entries);
1073 let finder = create_finder(&config, &system);
1074 let files = find_files(&finder);
1075 let total = finder.create_total(&files);
1076 assert_eq!(5, total.user_width);
1077 assert_eq!(6, total.group_width);
1078 }
1079
1080 fn create_entries(system: &mut MockSystem) {
1081 system.insert_entry(1, 'f', 0o744, 0, 0, 100, 2023, 1, 1, "archive.sh", None);
1082 system.insert_entry(1, 'd', 0o755, 1000, 500, 4096, 2023, 2, 2, "dir", None);
1083 system.insert_entry(2, 'l', 0o644, 1000, 500, 99, 2023, 12, 31, "dir/link1", Some("subdir"));
1084 system.insert_entry(2, 'l', 0o644, 1000, 500, 99, 2023, 12, 31, "dir/link2", Some("subdir/alpha.txt"));
1085 system.insert_entry(2, 'l', 0o644, 1000, 500, 99, 2023, 12, 31, "dir/link3", Some("/etc/missing.txt"));
1086 system.insert_entry(2, 'd', 0o755, 1500, 500, 4096, 2023, 3, 3, "dir/subdir", None);
1087 system.insert_entry(3, 'f', 0o644, 1500, 500, 400, 2023, 4, 4, "dir/subdir/alpha.csv", None);
1088 system.insert_entry(3, 'f', 0o644, 1500, 500, 500, 2023, 5, 5, "dir/subdir/alpha.txt", None);
1089 system.insert_entry(3, 'f', 0o644, 1500, 500, 600, 2023, 6, 6, "dir/subdir/beta.csv", None);
1090 system.insert_entry(3, 'f', 0o644, 1500, 500, 700, 2023, 7, 7, "dir/subdir/beta.txt", None);
1091 }
1092
1093 #[test]
1094 fn test_performs_case_sensitive_search() {
1095 let expected = vec![
1096 "/root/A1.txt",
1097 "/root/A2.txt",
1098 ];
1099 let config = Config::default()
1100 .with_patterns(vec!["A*"])
1101 .with_recurse_all(true)
1102 .with_case_sensitive(true);
1103 let system = create_system(&config, create_cases);
1104 let finder = create_finder(&config, &system);
1105 let files = find_files(&finder);
1106 let paths = convert_paths(files);
1107 assert_eq!(expected, paths);
1108 }
1109
1110 #[test]
1111 fn test_performs_case_insensitive_search() {
1112 let expected = vec![
1113 "/root/A1.txt",
1114 "/root/A2.txt",
1115 "/root/a1.txt",
1116 "/root/a2.txt",
1117 ];
1118 let config = Config::default()
1119 .with_patterns(vec!["A*"])
1120 .with_recurse_all(true)
1121 .with_case_sensitive(false);
1122 let system = create_system(&config, create_cases);
1123 let finder = create_finder(&config, &system);
1124 let files = find_files(&finder);
1125 let paths = convert_paths(files);
1126 assert_eq!(expected, paths);
1127 }
1128
1129 fn create_cases(system: &mut MockSystem) {
1130 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "A1.txt", None);
1131 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "A2.txt", None);
1132 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "B3.txt", None);
1133 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "B4.txt", None);
1134 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "a1.txt", None);
1135 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "a2.txt", None);
1136 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "b3.txt", None);
1137 system.insert_entry(1, 'f', 0o000, 0, 0, 0, 1970, 1, 1, "b4.txt", None);
1138 }
1139
1140 #[cfg(unix)]
1141 fn create_system<F>(config: &Config, mut setter: F) -> MockSystem<'_> where
1142 F: FnMut(&mut MockSystem),
1143 {
1144 use std::collections::BTreeMap;
1145 let current = PathBuf::from("/root");
1146 let user_names = BTreeMap::from([
1147 (0, String::from("root")),
1148 (1000, String::from("alice")),
1149 (1500, String::from("bob")),
1150 ]);
1151 let group_names = BTreeMap::from([
1152 (0, String::from("root")),
1153 (500, String::from("public")),
1154 ]);
1155 let mut system = MockSystem::new(config, current, user_names, group_names);
1156 setter(&mut system);
1157 system
1158 }
1159
1160 #[cfg(not(unix))]
1161 fn create_system<F>(config: &Config, mut setter: F) -> MockSystem<'_> where
1162 F: FnMut(&mut MockSystem),
1163 {
1164 let current = PathBuf::from("/root");
1165 let mut system = MockSystem::new(config, current);
1166 setter(&mut system);
1167 system
1168 }
1169
1170 fn create_finder<'a>(
1171 config: &'a Config,
1172 system: &'a MockSystem,
1173 ) -> Finder<'a, MockSystem<'a>> {
1174 let current = PathBuf::from("/root");
1175 Finder::new(config, system, &Utc, current, false)
1176 }
1177
1178 fn find_files(finder: &Finder<MockSystem>) -> Vec<File> {
1179 let mut files = finder.find_files().unwrap();
1180 files.sort_by_key(File::get_path);
1181 files
1182 }
1183
1184 fn assert_data(
1185 file: Option<&File>,
1186 file_depth: usize,
1187 file_type: FileKind,
1188 file_mode: u32,
1189 file_size: u64,
1190 time_year: i32,
1191 time_month: u32,
1192 time_day: u32,
1193 ) {
1194 let file = file.unwrap();
1195 let file_time = create_time(time_year, time_month, time_day);
1196 assert_eq!(file.file_depth, file_depth, "file depth");
1197 assert_eq!(file.file_type, file_type, "file type");
1198 assert_eq!(file.file_mode, file_mode, "file mode");
1199 assert_eq!(file.file_size, file_size, "file size");
1200 assert_eq!(file.file_time, file_time, "file time");
1201 }
1202
1203 fn assert_path(
1204 file: Option<&File>,
1205 abs_dir: &str,
1206 rel_dir: &str,
1207 file_name: &str,
1208 file_ext: &str,
1209 ) {
1210 let file = file.unwrap();
1211 assert_eq!(file.abs_dir, PathBuf::from(abs_dir), "absolute directory");
1212 assert_eq!(file.rel_dir, PathBuf::from(rel_dir), "relative directory");
1213 assert_eq!(file.file_name, file_name, "file name");
1214 assert_eq!(file.file_ext, file_ext, "file extension");
1215 }
1216
1217 fn assert_link(
1218 file: Option<&File>,
1219 link_data: Option<(&str, FileKind)>,
1220 ) {
1221 let file = file.unwrap();
1222 let link_data = link_data.map(|(p, f)| (PathBuf::from(p), f));
1223 assert_eq!(file.link_data, link_data, "link data");
1224 }
1225
1226 fn create_time(year: i32, month: u32, day: u32) -> DateTime<Utc> {
1227 Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
1228 }
1229
1230 fn convert_paths(files: Vec<File>) -> Vec<String> {
1231 files.into_iter().flat_map(convert_path).collect()
1232 }
1233
1234 #[cfg(windows)]
1235 fn convert_path(file: File) -> Option<String> {
1236 use std::path::MAIN_SEPARATOR_STR;
1237 let path = file.abs_dir.join(file.file_name);
1238 path.to_str().map(|path| path.replace(MAIN_SEPARATOR_STR, "/"))
1239 }
1240
1241 #[cfg(not(windows))]
1242 fn convert_path(file: File) -> Option<String> {
1243 let path = file.abs_dir.join(file.file_name);
1244 path.to_str().map(str::to_string)
1245 }
1246}