weavatrix_scan/
walk_builder.rs1use crate::report::FileIdentity;
2use crate::walker::{WalkEntry, WalkError, WalkOptions, Walker};
3use std::cmp::Ordering;
4use std::ffi::OsStr;
5use std::fs::DirEntry;
6use std::path::PathBuf;
7use std::sync::{Arc, Mutex, PoisonError};
8
9pub(crate) type EntrySorter = Arc<dyn Fn(&DirEntry, &DirEntry) -> Ordering + Send + Sync + 'static>;
10pub(crate) type EntryFilter = Arc<dyn Fn(&WalkEntry) -> bool + Send + Sync + 'static>;
11
12pub struct WalkBuilder {
14 roots: Vec<PathBuf>,
15 options: WalkOptions,
16 sorter: Option<EntrySorter>,
17 filter: Option<EntryFilter>,
18 skip_stdout: Option<FileIdentity>,
19 contents_first: bool,
20}
21
22impl WalkBuilder {
23 #[must_use]
24 pub fn new(root: impl Into<PathBuf>) -> Self {
25 Self {
26 roots: vec![root.into()],
27 options: WalkOptions::default(),
28 sorter: None,
29 filter: None,
30 skip_stdout: None,
31 contents_first: false,
32 }
33 }
34
35 #[must_use]
36 pub fn add_root(mut self, root: impl Into<PathBuf>) -> Self {
37 self.roots.push(root.into());
38 self
39 }
40
41 #[must_use]
42 pub const fn options(mut self, options: WalkOptions) -> Self {
43 self.options = options;
44 self
45 }
46
47 #[must_use]
49 pub fn sort_by_file_name(mut self) -> Self {
50 self.sorter = Some(Arc::new(|left, right| {
51 left.file_name().cmp(&right.file_name())
52 }));
53 self
54 }
55
56 #[must_use]
58 pub fn sort_by<F>(mut self, compare: F) -> Self
59 where
60 F: Fn(&DirEntry, &DirEntry) -> Ordering + Send + Sync + 'static,
61 {
62 self.sorter = Some(Arc::new(compare));
63 self
64 }
65
66 #[must_use]
68 pub fn sort_by_name<F>(mut self, compare: F) -> Self
69 where
70 F: Fn(&OsStr, &OsStr) -> Ordering + Send + Sync + 'static,
71 {
72 self.sorter = Some(Arc::new(move |left, right| {
73 compare(&left.file_name(), &right.file_name())
74 }));
75 self
76 }
77
78 #[must_use]
83 pub fn skip_stdout(mut self, enabled: bool) -> Self {
84 self.skip_stdout = enabled.then(crate::stdout::identity).flatten();
85 self
86 }
87
88 #[must_use]
90 pub const fn contents_first(mut self, enabled: bool) -> Self {
91 self.contents_first = enabled;
92 self
93 }
94
95 #[must_use]
97 pub fn filter_entry<F>(mut self, filter: F) -> Self
98 where
99 F: Fn(&WalkEntry) -> bool + Send + Sync + 'static,
100 {
101 self.filter = Some(Arc::new(filter));
102 self
103 }
104
105 #[must_use]
107 pub fn filter_directories<F>(mut self, filter: F) -> Self
108 where
109 F: Fn(&WalkEntry) -> bool + Send + Sync + 'static,
110 {
111 self.filter = Some(Arc::new(move |entry| !entry.is_dir() || filter(entry)));
112 self
113 }
114
115 #[must_use]
120 pub fn filter_directories_stateful<F>(mut self, filter: F) -> Self
121 where
122 F: FnMut(&WalkEntry) -> bool + Send + 'static,
123 {
124 let filter = Mutex::new(filter);
125 self.filter = Some(Arc::new(move |entry| {
126 if !entry.is_dir() {
127 return true;
128 }
129 let mut filter = filter.lock().unwrap_or_else(PoisonError::into_inner);
130 filter(entry)
131 }));
132 self
133 }
134
135 #[must_use]
136 pub fn build(self) -> MultiWalker {
137 MultiWalker {
138 roots: self.roots.into_iter(),
139 options: self.options,
140 sorter: self.sorter,
141 filter: self.filter,
142 skip_stdout: self.skip_stdout,
143 contents_first: self.contents_first,
144 current: None,
145 }
146 }
147}
148
149pub struct MultiWalker {
151 roots: std::vec::IntoIter<PathBuf>,
152 options: WalkOptions,
153 sorter: Option<EntrySorter>,
154 filter: Option<EntryFilter>,
155 skip_stdout: Option<FileIdentity>,
156 contents_first: bool,
157 current: Option<Walker>,
158}
159
160impl Iterator for MultiWalker {
161 type Item = Result<WalkEntry, WalkError>;
162
163 fn next(&mut self) -> Option<Self::Item> {
164 loop {
165 if let Some(walker) = self.current.as_mut()
166 && let Some(item) = walker.next()
167 {
168 return Some(item);
169 }
170 self.current = None;
171 let root = self.roots.next()?;
172 match Walker::with_behavior(
173 root,
174 self.options,
175 self.sorter.clone(),
176 self.filter.clone(),
177 self.skip_stdout,
178 self.contents_first,
179 ) {
180 Ok(walker) => self.current = Some(walker),
181 Err(error) => return Some(Err(error)),
182 }
183 }
184 }
185}