1use direntryfilter::{DirEntryFilter, DirectoryOnlyFilter, FileOnlyFilter, IgnoreDirEntry};
2use moar_options::*;
3#[cfg(feature = "pathfilter")]
4use pathfilter::{IgnorePath, PathFilter};
5use std::{
6 fs::{self, DirEntry},
7 path::PathBuf,
8};
9
10pub struct PathWalker {
11 directories: Vec<PathBuf>,
12 items: Vec<DirEntry>,
13 follow_symlinks: bool,
14 max_depth: Option<u64>,
15 current_depth: u64,
16 direntry_filters: Vec<DirEntryFilter>,
17 #[cfg(feature = "pathfilter")]
18 path_filters: Vec<PathFilter>,
19}
20
21impl PathWalker {
22 pub fn new<T: Into<PathBuf>>(path: T) -> Self {
23 Self {
24 directories: vec![path.into()],
25 items: Vec::new(),
26 follow_symlinks: false,
27 max_depth: None,
28 current_depth: 0,
29 direntry_filters: Vec::new(),
30 #[cfg(feature = "pathfilter")]
31 path_filters: Vec::new(),
32 }
33 }
34
35 pub fn follow_symlinks(mut self) -> Self {
36 self.follow_symlinks = true;
37 self
38 }
39
40 pub fn with_max_depth<T: Into<Option<u64>>>(mut self, max_depth: T) -> Self {
41 self.max_depth = max_depth.into();
42 self
43 }
44}
45
46impl Default for PathWalker {
47 fn default() -> Self {
48 Self::new(".")
49 }
50}
51
52#[cfg(feature = "pathfilter")]
53impl PathWalker {
54 pub fn with_filter(mut self, filter: PathFilter) -> Self {
55 self.path_filters.push(filter);
56 self
57 }
58
59 pub fn with_filters<T: AsRef<[PathFilter]>>(mut self, filters: T) -> Self {
60 self.path_filters.extend_from_slice(filters.as_ref());
61 self
62 }
63}
64
65impl PathWalker {
66 pub fn files_only(mut self) -> Self {
67 self.direntry_filters.push(FileOnlyFilter::default().into());
68 self
69 }
70
71 pub fn directories_only(mut self) -> Self {
72 self.direntry_filters
73 .push(DirectoryOnlyFilter::default().into());
74 self
75 }
76}
77
78impl PathWalker {
79 fn handle_entry(&mut self, entry: DirEntry) {
80 let entry_path = entry.path();
81
82 #[cfg(feature = "pathfilter")]
83 if self.path_filters.ignore(&entry_path) {
84 return;
85 }
86
87 if let Ok(file_type) = entry.file_type() {
88 if self.follow_symlinks || !file_type.is_symlink() {
89 if file_type.is_dir()
90 && self
91 .max_depth
92 .owned_is_none_or(|max_depth| self.current_depth < max_depth)
93 {
94 self.directories.push(entry_path);
95 self.current_depth += 1;
96 }
97
98 if self.direntry_filters.ignore(&entry) {
99 return;
100 }
101
102 self.items.push(entry);
103 }
104 };
105 }
106}
107
108impl Iterator for PathWalker {
109 type Item = DirEntry;
110
111 fn next(&mut self) -> Option<Self::Item> {
112 match self.items.pop() {
113 Some(entry) => Some(entry),
114 None => {
115 while self.items.is_empty() && !self.directories.is_empty() {
116 self.directories
117 .pop()
118 .map(fs::read_dir)
119 .into_iter()
120 .flatten()
121 .flatten()
122 .flatten()
123 .for_each(|entry| self.handle_entry(entry))
124 }
125
126 self.items.pop()
127 }
128 }
129 }
130}