relative_path_utils/glob/
mod.rs1#[cfg(test)]
2mod tests;
3
4use core::fmt;
5use core::mem;
6
7use alloc::borrow::ToOwned;
8use alloc::boxed::Box;
9use alloc::collections::VecDeque;
10use alloc::vec::Vec;
11
12use std::io;
13
14use relative_path::{RelativePath, RelativePathBuf};
15
16use crate::Root;
17
18type Result<T> = std::result::Result<T, Error>;
19
20#[derive(Debug)]
21pub struct Error {
22 kind: ErrorKind,
23}
24
25impl Error {
26 fn new(kind: ErrorKind) -> Self {
27 Self { kind }
28 }
29}
30
31impl fmt::Display for Error {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match &self.kind {
34 ErrorKind::ReadDir(..) => write!(f, "Error reading directory"),
35 ErrorKind::DirEntry(..) => write!(f, "Error getting directory entry"),
36 }
37 }
38}
39
40impl From<ErrorKind> for Error {
41 #[inline]
42 fn from(kind: ErrorKind) -> Self {
43 Self::new(kind)
44 }
45}
46
47#[derive(Debug)]
48enum ErrorKind {
49 ReadDir(io::Error),
50 DirEntry(io::Error),
51}
52
53impl std::error::Error for Error {
54 #[allow(clippy::match_same_arms)]
55 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
56 match &self.kind {
57 ErrorKind::ReadDir(error) => Some(error),
58 ErrorKind::DirEntry(error) => Some(error),
59 }
60 }
61}
62
63pub struct Glob<'a> {
67 root: &'a Root,
68 components: Vec<Component<'a>>,
69}
70
71impl<'a> Glob<'a> {
72 pub(super) fn new(root: &'a Root, pattern: &'a RelativePath) -> Self {
74 let components = compile_pattern(pattern);
75 Self { root, components }
76 }
77
78 #[must_use]
100 pub fn matcher(&self) -> Matcher<'_> {
101 Matcher {
102 root: self.root,
103 queue: [(RelativePathBuf::new(), self.components.as_ref())]
104 .into_iter()
105 .collect(),
106 }
107 }
108}
109
110impl<'a> Matcher<'a> {
111 fn expand_filesystem<M>(
113 &mut self,
114 current: &RelativePath,
115 rest: &'a [Component<'a>],
116 mut m: M,
117 ) -> Result<()>
118 where
119 M: FnMut(&str) -> bool,
120 {
121 if let Ok(m) = self.root.metadata(current) {
122 if !m.is_dir() {
123 return Ok(());
124 }
125 } else {
126 return Ok(());
127 }
128
129 for e in self.root.read_dir(current).map_err(ErrorKind::ReadDir)? {
130 let e = e.map_err(ErrorKind::DirEntry)?;
131 let c = e.file_name();
132 let c = c.to_string_lossy();
133
134 if !m(c.as_ref()) {
135 continue;
136 }
137
138 let mut new = current.to_owned();
139 new.push(c.as_ref());
140 self.queue.push_back((new, rest));
141 }
142
143 Ok(())
144 }
145
146 fn walk(&mut self, current: &RelativePathBuf, rest: &'a [Component<'a>]) -> Result<()> {
148 self.queue.push_back((current.clone(), rest));
149
150 let mut queue = VecDeque::new();
151 queue.push_back(current.clone());
152
153 while let Some(current) = queue.pop_front() {
154 let Ok(m) = self.root.metadata(¤t) else {
155 continue;
156 };
157
158 if !m.is_dir() {
159 continue;
160 }
161
162 for e in self.root.read_dir(¤t).map_err(ErrorKind::ReadDir)? {
163 let e = e.map_err(ErrorKind::DirEntry)?;
164 let c = e.file_name();
165 let c = c.to_string_lossy();
166 let next = current.join(c.as_ref());
167 self.queue.push_back((next.clone(), rest));
168 queue.push_back(next);
169 }
170 }
171
172 Ok(())
173 }
174}
175
176pub struct Matcher<'a> {
178 root: &'a Root,
179 queue: VecDeque<(RelativePathBuf, &'a [Component<'a>])>,
180}
181
182impl Iterator for Matcher<'_> {
183 type Item = Result<RelativePathBuf>;
184
185 fn next(&mut self) -> Option<Self::Item> {
186 'outer: loop {
187 let (mut path, mut components) = self.queue.pop_front()?;
188
189 while let [first, rest @ ..] = components {
190 match first {
191 Component::ParentDir => {
192 path = path.join(relative_path::Component::ParentDir);
193 }
194 Component::Normal(normal) => {
195 path = path.join(normal);
196 }
197 Component::Fragment(fragment) => {
198 if let Err(e) =
199 self.expand_filesystem(&path, rest, |name| fragment.is_match(name))
200 {
201 return Some(Err(e));
202 }
203
204 continue 'outer;
205 }
206 Component::StarStar => {
207 if let Err(e) = self.walk(&path, rest) {
208 return Some(Err(e));
209 }
210
211 continue 'outer;
212 }
213 }
214
215 components = rest;
216 }
217
218 return Some(Ok(path));
219 }
220 }
221}
222
223#[derive(Clone)]
224enum Component<'a> {
225 ParentDir,
227 Normal(&'a str),
229 Fragment(Fragment<'a>),
231 StarStar,
233}
234
235fn compile_pattern(pattern: &RelativePath) -> Vec<Component<'_>> {
236 let mut output = Vec::new();
237
238 for c in pattern.components() {
239 output.push(match c {
240 relative_path::Component::CurDir => continue,
241 relative_path::Component::ParentDir => Component::ParentDir,
242 relative_path::Component::Normal("**") => Component::StarStar,
243 relative_path::Component::Normal(normal) => {
244 let fragment = Fragment::parse(normal);
245
246 if let Some(normal) = fragment.as_literal() {
247 Component::Normal(normal)
248 } else {
249 Component::Fragment(fragment)
250 }
251 }
252 });
253 }
254
255 output
256}
257
258#[derive(Debug, Clone, Copy)]
259enum Part<'a> {
260 Star,
261 Literal(&'a str),
262}
263
264#[derive(Debug, Clone)]
266pub(crate) struct Fragment<'a> {
267 parts: Box<[Part<'a>]>,
268}
269
270impl<'a> Fragment<'a> {
271 pub(crate) fn parse(string: &'a str) -> Fragment<'a> {
272 let mut literal = true;
273 let mut parts = Vec::new();
274 let mut start = None;
275
276 for (n, c) in string.char_indices() {
277 if c == '*' {
278 if let Some(s) = start.take() {
279 parts.push(Part::Literal(&string[s..n]));
280 }
281
282 if mem::take(&mut literal) {
283 parts.push(Part::Star);
284 }
285 } else {
286 if start.is_none() {
287 start = Some(n);
288 }
289
290 literal = true;
291 }
292 }
293
294 if let Some(s) = start {
295 parts.push(Part::Literal(&string[s..]));
296 }
297
298 Fragment {
299 parts: parts.into(),
300 }
301 }
302
303 pub(crate) fn is_match(&self, string: &str) -> bool {
305 let mut backtrack = VecDeque::new();
306 backtrack.push_back((self.parts.as_ref(), string));
307
308 while let Some((mut parts, mut string)) = backtrack.pop_front() {
309 while let Some(part) = parts.first() {
310 match part {
311 Part::Star => {
312 let Some(Part::Literal(peek)) = parts.get(1) else {
316 return true;
317 };
318
319 let Some(peek) = peek.chars().next() else {
320 return true;
321 };
322
323 while let Some(c) = string.chars().next() {
324 if c == peek {
325 backtrack.push_front((
326 parts,
327 string.get(c.len_utf8()..).unwrap_or_default(),
328 ));
329 break;
330 }
331
332 string = string.get(c.len_utf8()..).unwrap_or_default();
333 }
334 }
335 Part::Literal(literal) => {
336 let Some(remainder) = string.strip_prefix(literal) else {
339 return false;
340 };
341
342 string = remainder;
343 }
344 }
345
346 parts = parts.get(1..).unwrap_or_default();
347 }
348
349 if string.is_empty() {
350 return true;
351 }
352 }
353
354 false
355 }
356
357 fn as_literal(&self) -> Option<&'a str> {
359 if let [Part::Literal(one)] = self.parts.as_ref() {
360 Some(one)
361 } else {
362 None
363 }
364 }
365}