/rust/registry/src/index.crates.io-1949cf8c6b5b557f/glob-0.3.4/src/lib.rs
Line | Count | Source |
1 | | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
2 | | // file at the top-level directory of this distribution and at |
3 | | // http://rust-lang.org/COPYRIGHT. |
4 | | // |
5 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | | // option. This file may not be copied, modified, or distributed |
9 | | // except according to those terms. |
10 | | |
11 | | //! Support for matching file paths against Unix shell style patterns. |
12 | | //! |
13 | | //! The `glob` and `glob_with` functions allow querying the filesystem for all |
14 | | //! files that match a particular pattern (similar to the libc `glob` function). |
15 | | //! The methods on the `Pattern` type provide functionality for checking if |
16 | | //! individual paths match a particular pattern (similar to the libc `fnmatch` |
17 | | //! function). |
18 | | //! |
19 | | //! For consistency across platforms, and for Windows support, this module |
20 | | //! is implemented entirely in Rust rather than deferring to the libc |
21 | | //! `glob`/`fnmatch` functions. |
22 | | //! |
23 | | //! # Examples |
24 | | //! |
25 | | //! To print all jpg files in `/media/` and all of its subdirectories. |
26 | | //! |
27 | | //! ```rust,no_run |
28 | | //! use glob::glob; |
29 | | //! |
30 | | //! for entry in glob("/media/**/*.jpg").expect("Failed to read glob pattern") { |
31 | | //! match entry { |
32 | | //! Ok(path) => println!("{:?}", path.display()), |
33 | | //! Err(e) => println!("{:?}", e), |
34 | | //! } |
35 | | //! } |
36 | | //! ``` |
37 | | //! |
38 | | //! To print all files containing the letter "a", case insensitive, in a `local` |
39 | | //! directory relative to the current working directory. This ignores errors |
40 | | //! instead of printing them. |
41 | | //! |
42 | | //! ```rust,no_run |
43 | | //! use glob::glob_with; |
44 | | //! use glob::MatchOptions; |
45 | | //! |
46 | | //! let options = MatchOptions { |
47 | | //! case_sensitive: false, |
48 | | //! require_literal_separator: false, |
49 | | //! require_literal_leading_dot: false, |
50 | | //! }; |
51 | | //! for entry in glob_with("local/*a*", options).unwrap() { |
52 | | //! if let Ok(path) = entry { |
53 | | //! println!("{:?}", path.display()) |
54 | | //! } |
55 | | //! } |
56 | | //! ``` |
57 | | |
58 | | #![doc( |
59 | | html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", |
60 | | html_favicon_url = "https://www.rust-lang.org/favicon.ico", |
61 | | html_root_url = "https://docs.rs/glob/0.3.1" |
62 | | )] |
63 | | #![deny(missing_docs)] |
64 | | #![allow(clippy::while_let_loop)] |
65 | | |
66 | | #[cfg(test)] |
67 | | doc_comment::doctest!("../README.md"); |
68 | | |
69 | | use std::cmp; |
70 | | use std::cmp::Ordering; |
71 | | use std::error::Error; |
72 | | use std::ffi::OsString; |
73 | | use std::fmt; |
74 | | use std::fs; |
75 | | use std::fs::DirEntry; |
76 | | use std::io; |
77 | | use std::ops::Deref; |
78 | | use std::path::{self, Component, Path, PathBuf}; |
79 | | use std::str::FromStr; |
80 | | |
81 | | use CharSpecifier::{CharRange, SingleChar}; |
82 | | use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch}; |
83 | | use PatternToken::AnyExcept; |
84 | | use PatternToken::{AnyChar, AnyRecursiveSequence, AnySequence, AnyWithin, Char}; |
85 | | |
86 | | /// An iterator that yields `Path`s from the filesystem that match a particular |
87 | | /// pattern. |
88 | | /// |
89 | | /// Note that it yields `GlobResult` in order to report any `IoErrors` that may |
90 | | /// arise during iteration. If a directory matches but is unreadable, |
91 | | /// thereby preventing its contents from being checked for matches, a |
92 | | /// `GlobError` is returned to express this. |
93 | | /// |
94 | | /// See the `glob` function for more details. |
95 | | #[derive(Debug)] |
96 | | pub struct Paths { |
97 | | dir_patterns: Vec<Pattern>, |
98 | | require_dir: bool, |
99 | | options: MatchOptions, |
100 | | todo: Vec<Result<(PathWrapper, usize), GlobError>>, |
101 | | scope: Option<PathWrapper>, |
102 | | } |
103 | | |
104 | | /// Return an iterator that produces all the `Path`s that match the given |
105 | | /// pattern using default match options, which may be absolute or relative to |
106 | | /// the current working directory. |
107 | | /// |
108 | | /// This may return an error if the pattern is invalid. |
109 | | /// |
110 | | /// This method uses the default match options and is equivalent to calling |
111 | | /// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you |
112 | | /// want to use non-default match options. |
113 | | /// |
114 | | /// When iterating, each result is a `GlobResult` which expresses the |
115 | | /// possibility that there was an `IoError` when attempting to read the contents |
116 | | /// of the matched path. In other words, each item returned by the iterator |
117 | | /// will either be an `Ok(Path)` if the path matched, or an `Err(GlobError)` if |
118 | | /// the path (partially) matched _but_ its contents could not be read in order |
119 | | /// to determine if its contents matched. |
120 | | /// |
121 | | /// See the `Paths` documentation for more information. |
122 | | /// |
123 | | /// # Examples |
124 | | /// |
125 | | /// Consider a directory `/media/pictures` containing only the files |
126 | | /// `kittens.jpg`, `puppies.jpg` and `hamsters.gif`: |
127 | | /// |
128 | | /// ```rust,no_run |
129 | | /// use glob::glob; |
130 | | /// |
131 | | /// for entry in glob("/media/pictures/*.jpg").unwrap() { |
132 | | /// match entry { |
133 | | /// Ok(path) => println!("{:?}", path.display()), |
134 | | /// |
135 | | /// // if the path matched but was unreadable, |
136 | | /// // thereby preventing its contents from matching |
137 | | /// Err(e) => println!("{:?}", e), |
138 | | /// } |
139 | | /// } |
140 | | /// ``` |
141 | | /// |
142 | | /// The above code will print: |
143 | | /// |
144 | | /// ```ignore |
145 | | /// /media/pictures/kittens.jpg |
146 | | /// /media/pictures/puppies.jpg |
147 | | /// ``` |
148 | | /// |
149 | | /// If you want to ignore unreadable paths, you can use something like |
150 | | /// `filter_map`: |
151 | | /// |
152 | | /// ```rust |
153 | | /// use glob::glob; |
154 | | /// use std::result::Result; |
155 | | /// |
156 | | /// for path in glob("/media/pictures/*.jpg").unwrap().filter_map(Result::ok) { |
157 | | /// println!("{}", path.display()); |
158 | | /// } |
159 | | /// ``` |
160 | | /// Paths are yielded in alphabetical order. |
161 | 0 | pub fn glob(pattern: &str) -> Result<Paths, PatternError> { |
162 | 0 | glob_with(pattern, MatchOptions::new()) |
163 | 0 | } |
164 | | |
165 | | /// Return an iterator that produces all the `Path`s that match the given |
166 | | /// pattern using the specified match options, which may be absolute or relative |
167 | | /// to the current working directory. |
168 | | /// |
169 | | /// This may return an error if the pattern is invalid. |
170 | | /// |
171 | | /// This function accepts Unix shell style patterns as described by |
172 | | /// `Pattern::new(..)`. The options given are passed through unchanged to |
173 | | /// `Pattern::matches_with(..)` with the exception that |
174 | | /// `require_literal_separator` is always set to `true` regardless of the value |
175 | | /// passed to this function. |
176 | | /// |
177 | | /// Paths are yielded in alphabetical order. |
178 | 0 | pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternError> { |
179 | | #[cfg(windows)] |
180 | | fn check_windows_verbatim(p: &Path) -> bool { |
181 | | match p.components().next() { |
182 | | Some(Component::Prefix(ref p)) => { |
183 | | // Allow VerbatimDisk paths. std canonicalize() generates them, and they work fine |
184 | | p.kind().is_verbatim() |
185 | | && if let std::path::Prefix::VerbatimDisk(_) = p.kind() { |
186 | | false |
187 | | } else { |
188 | | true |
189 | | } |
190 | | } |
191 | | _ => false, |
192 | | } |
193 | | } |
194 | | #[cfg(not(windows))] |
195 | 0 | fn check_windows_verbatim(_: &Path) -> bool { |
196 | 0 | false |
197 | 0 | } |
198 | | |
199 | | #[cfg(windows)] |
200 | | fn to_scope(p: &Path) -> PathBuf { |
201 | | // FIXME handle volume relative paths here |
202 | | p.to_path_buf() |
203 | | } |
204 | | #[cfg(not(windows))] |
205 | 0 | fn to_scope(p: &Path) -> PathBuf { |
206 | 0 | p.to_path_buf() |
207 | 0 | } |
208 | | |
209 | | // make sure that the pattern is valid first, else early return with error |
210 | 0 | let _ = Pattern::new(pattern)?; |
211 | | |
212 | 0 | let mut components = Path::new(pattern).components().peekable(); |
213 | | loop { |
214 | 0 | match components.peek() { |
215 | 0 | Some(&Component::Prefix(..)) | Some(&Component::RootDir) => { |
216 | 0 | components.next(); |
217 | 0 | } |
218 | 0 | _ => break, |
219 | | } |
220 | | } |
221 | 0 | let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>(); |
222 | 0 | let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>(); |
223 | 0 | let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len(); |
224 | 0 | let root = if root_len > 0 { |
225 | 0 | Some(Path::new(&pattern[..root_len])) |
226 | | } else { |
227 | 0 | None |
228 | | }; |
229 | | |
230 | 0 | if root_len > 0 && check_windows_verbatim(root.unwrap()) { |
231 | | // FIXME: How do we want to handle verbatim paths? I'm inclined to |
232 | | // return nothing, since we can't very well find all UNC shares with a |
233 | | // 1-letter server name. |
234 | 0 | return Ok(Paths { |
235 | 0 | dir_patterns: Vec::new(), |
236 | 0 | require_dir: false, |
237 | 0 | options, |
238 | 0 | todo: Vec::new(), |
239 | 0 | scope: None, |
240 | 0 | }); |
241 | 0 | } |
242 | | |
243 | 0 | let scope = root.map_or_else(|| PathBuf::from("."), to_scope); |
244 | 0 | let scope = PathWrapper::from_path(scope); |
245 | | |
246 | 0 | let mut dir_patterns = Vec::new(); |
247 | 0 | let components = |
248 | 0 | pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator); |
249 | | |
250 | 0 | for component in components { |
251 | 0 | dir_patterns.push(Pattern::new(component)?); |
252 | | } |
253 | | |
254 | 0 | if root_len == pattern.len() { |
255 | 0 | dir_patterns.push(Pattern { |
256 | 0 | original: "".to_string(), |
257 | 0 | tokens: Vec::new(), |
258 | 0 | is_recursive: false, |
259 | 0 | has_metachars: false, |
260 | 0 | }); |
261 | 0 | } |
262 | | |
263 | 0 | let last_is_separator = pattern.chars().next_back().map(path::is_separator); |
264 | 0 | let require_dir = last_is_separator == Some(true); |
265 | 0 | let todo = Vec::new(); |
266 | | |
267 | 0 | Ok(Paths { |
268 | 0 | dir_patterns, |
269 | 0 | require_dir, |
270 | 0 | options, |
271 | 0 | todo, |
272 | 0 | scope: Some(scope), |
273 | 0 | }) |
274 | 0 | } |
275 | | |
276 | | /// A glob iteration error. |
277 | | /// |
278 | | /// This is typically returned when a particular path cannot be read |
279 | | /// to determine if its contents match the glob pattern. This is possible |
280 | | /// if the program lacks the appropriate permissions, for example. |
281 | | #[derive(Debug)] |
282 | | pub struct GlobError { |
283 | | path: PathBuf, |
284 | | error: io::Error, |
285 | | } |
286 | | |
287 | | impl GlobError { |
288 | | /// The Path that the error corresponds to. |
289 | 0 | pub fn path(&self) -> &Path { |
290 | 0 | &self.path |
291 | 0 | } |
292 | | |
293 | | /// The error in question. |
294 | 0 | pub fn error(&self) -> &io::Error { |
295 | 0 | &self.error |
296 | 0 | } |
297 | | |
298 | | /// Consumes self, returning the _raw_ underlying `io::Error` |
299 | | #[deprecated(note = "use `.into` instead")] |
300 | 0 | pub fn into_error(self) -> io::Error { |
301 | 0 | self.error |
302 | 0 | } |
303 | | } |
304 | | |
305 | | impl From<GlobError> for io::Error { |
306 | 0 | fn from(value: GlobError) -> Self { |
307 | 0 | value.error |
308 | 0 | } |
309 | | } |
310 | | |
311 | | impl Error for GlobError { |
312 | | #[allow(deprecated)] |
313 | 0 | fn description(&self) -> &str { |
314 | 0 | self.error.description() |
315 | 0 | } |
316 | | |
317 | | #[allow(unknown_lints, bare_trait_objects)] |
318 | 0 | fn cause(&self) -> Option<&dyn Error> { |
319 | 0 | Some(&self.error) |
320 | 0 | } |
321 | | } |
322 | | |
323 | | impl fmt::Display for GlobError { |
324 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
325 | 0 | write!( |
326 | 0 | f, |
327 | 0 | "attempting to read `{}` resulted in an error: {}", |
328 | 0 | self.path.display(), |
329 | | self.error |
330 | | ) |
331 | 0 | } |
332 | | } |
333 | | |
334 | | #[derive(Debug)] |
335 | | struct PathWrapper { |
336 | | path: PathBuf, |
337 | | is_directory: bool, |
338 | | } |
339 | | |
340 | | impl PathWrapper { |
341 | 0 | fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self { |
342 | 0 | let is_directory = e |
343 | 0 | .file_type() |
344 | 0 | .ok() |
345 | 0 | .and_then(|file_type| { |
346 | | // We need to use fs::metadata to resolve the actual path |
347 | | // if it's a symlink. |
348 | 0 | if file_type.is_symlink() { |
349 | 0 | None |
350 | | } else { |
351 | 0 | Some(file_type.is_dir()) |
352 | | } |
353 | 0 | }) |
354 | 0 | .or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok()) |
355 | 0 | .unwrap_or(false); |
356 | 0 | Self { path, is_directory } |
357 | 0 | } |
358 | 0 | fn from_path(path: PathBuf) -> Self { |
359 | 0 | let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false); |
360 | 0 | Self { path, is_directory } |
361 | 0 | } |
362 | | |
363 | 0 | fn into_path(self) -> PathBuf { |
364 | 0 | self.path |
365 | 0 | } |
366 | | } |
367 | | |
368 | | impl Deref for PathWrapper { |
369 | | type Target = Path; |
370 | | |
371 | 0 | fn deref(&self) -> &Self::Target { |
372 | 0 | self.path.deref() |
373 | 0 | } |
374 | | } |
375 | | |
376 | | impl AsRef<Path> for PathWrapper { |
377 | 0 | fn as_ref(&self) -> &Path { |
378 | 0 | self.path.as_ref() |
379 | 0 | } |
380 | | } |
381 | | |
382 | | /// An alias for a glob iteration result. |
383 | | /// |
384 | | /// This represents either a matched path or a glob iteration error, |
385 | | /// such as failing to read a particular directory's contents. |
386 | | pub type GlobResult = Result<PathBuf, GlobError>; |
387 | | |
388 | | impl Iterator for Paths { |
389 | | type Item = GlobResult; |
390 | | |
391 | 0 | fn next(&mut self) -> Option<GlobResult> { |
392 | | // the todo buffer hasn't been initialized yet, so it's done at this |
393 | | // point rather than in glob() so that the errors are unified that is, |
394 | | // failing to fill the buffer is an iteration error construction of the |
395 | | // iterator (i.e. glob()) only fails if it fails to compile the Pattern |
396 | 0 | if let Some(scope) = self.scope.take() { |
397 | 0 | if !self.dir_patterns.is_empty() { |
398 | | // Shouldn't happen, but we're using -1 as a special index. |
399 | 0 | assert!(self.dir_patterns.len() < usize::MAX); |
400 | | |
401 | 0 | fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options); |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | loop { |
406 | 0 | if self.dir_patterns.is_empty() || self.todo.is_empty() { |
407 | 0 | return None; |
408 | 0 | } |
409 | | |
410 | 0 | let (path, mut idx) = match self.todo.pop().unwrap() { |
411 | 0 | Ok(pair) => pair, |
412 | 0 | Err(e) => return Some(Err(e)), |
413 | | }; |
414 | | |
415 | | // idx -1: was already checked by fill_todo, maybe path was '.' or |
416 | | // '..' that we can't match here because of normalization. |
417 | 0 | if idx == usize::MAX { |
418 | 0 | if self.require_dir && !path.is_directory { |
419 | 0 | continue; |
420 | 0 | } |
421 | 0 | return Some(Ok(path.into_path())); |
422 | 0 | } |
423 | | |
424 | 0 | if self.dir_patterns[idx].is_recursive { |
425 | 0 | let mut next = idx; |
426 | | |
427 | | // collapse consecutive recursive patterns |
428 | 0 | while (next + 1) < self.dir_patterns.len() |
429 | 0 | && self.dir_patterns[next + 1].is_recursive |
430 | 0 | { |
431 | 0 | next += 1; |
432 | 0 | } |
433 | | |
434 | 0 | if path.is_directory { |
435 | | // the path is a directory, so it's a match |
436 | | |
437 | | // push this directory's contents |
438 | 0 | fill_todo( |
439 | 0 | &mut self.todo, |
440 | 0 | &self.dir_patterns, |
441 | 0 | next, |
442 | 0 | &path, |
443 | 0 | self.options, |
444 | | ); |
445 | | |
446 | 0 | if next == self.dir_patterns.len() - 1 { |
447 | | // pattern ends in recursive pattern, so return this |
448 | | // directory as a result |
449 | 0 | return Some(Ok(path.into_path())); |
450 | 0 | } else { |
451 | 0 | // advanced to the next pattern for this path |
452 | 0 | idx = next + 1; |
453 | 0 | } |
454 | 0 | } else if next == self.dir_patterns.len() - 1 { |
455 | | // not a directory and it's the last pattern, meaning no |
456 | | // match |
457 | 0 | continue; |
458 | 0 | } else { |
459 | 0 | // advanced to the next pattern for this path |
460 | 0 | idx = next + 1; |
461 | 0 | } |
462 | 0 | } |
463 | | |
464 | | // not recursive, so match normally |
465 | 0 | if self.dir_patterns[idx].matches_with( |
466 | | { |
467 | 0 | match path.file_name().and_then(|s| s.to_str()) { |
468 | | // FIXME (#9639): How do we handle non-utf8 filenames? |
469 | | // Ignore them for now; ideally we'd still match them |
470 | | // against a * |
471 | 0 | None => continue, |
472 | 0 | Some(x) => x, |
473 | | } |
474 | | }, |
475 | 0 | self.options, |
476 | | ) { |
477 | 0 | if idx == self.dir_patterns.len() - 1 { |
478 | | // it is not possible for a pattern to match a directory |
479 | | // *AND* its children so we don't need to check the |
480 | | // children |
481 | | |
482 | 0 | if !self.require_dir || path.is_directory { |
483 | 0 | return Some(Ok(path.into_path())); |
484 | 0 | } |
485 | 0 | } else { |
486 | 0 | fill_todo( |
487 | 0 | &mut self.todo, |
488 | 0 | &self.dir_patterns, |
489 | 0 | idx + 1, |
490 | 0 | &path, |
491 | 0 | self.options, |
492 | 0 | ); |
493 | 0 | } |
494 | 0 | } |
495 | | } |
496 | 0 | } |
497 | | } |
498 | | |
499 | | /// A pattern parsing error. |
500 | | #[derive(Debug)] |
501 | | #[allow(missing_copy_implementations)] |
502 | | pub struct PatternError { |
503 | | /// The approximate character index of where the error occurred. |
504 | | pub pos: usize, |
505 | | |
506 | | /// A message describing the error. |
507 | | pub msg: &'static str, |
508 | | } |
509 | | |
510 | | impl Error for PatternError { |
511 | 0 | fn description(&self) -> &str { |
512 | 0 | self.msg |
513 | 0 | } |
514 | | } |
515 | | |
516 | | impl fmt::Display for PatternError { |
517 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
518 | 0 | write!( |
519 | 0 | f, |
520 | 0 | "Pattern syntax error near position {}: {}", |
521 | | self.pos, self.msg |
522 | | ) |
523 | 0 | } |
524 | | } |
525 | | |
526 | | /// A compiled Unix shell style pattern. |
527 | | /// |
528 | | /// - `?` matches any single character. |
529 | | /// |
530 | | /// - `*` matches any (possibly empty) sequence of characters. |
531 | | /// |
532 | | /// - `**` matches the current directory and arbitrary |
533 | | /// subdirectories. To match files in arbitrary subdirectories, use |
534 | | /// `**/*`. |
535 | | /// |
536 | | /// This sequence **must** form a single path component, so both |
537 | | /// `**a` and `b**` are invalid and will result in an error. A |
538 | | /// sequence of more than two consecutive `*` characters is also |
539 | | /// invalid. |
540 | | /// |
541 | | /// - `[...]` matches any character inside the brackets. Character sequences |
542 | | /// can also specify ranges of characters, as ordered by Unicode, so e.g. |
543 | | /// `[0-9]` specifies any character between 0 and 9 inclusive. An unclosed |
544 | | /// bracket is invalid. |
545 | | /// |
546 | | /// - `[!...]` is the negation of `[...]`, i.e. it matches any characters |
547 | | /// **not** in the brackets. |
548 | | /// |
549 | | /// - The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets |
550 | | /// (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then it |
551 | | /// is interpreted as being part of, rather then ending, the character set, so |
552 | | /// `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively. The `-` |
553 | | /// character can be specified inside a character sequence pattern by placing |
554 | | /// it at the start or the end, e.g. `[abc-]`. |
555 | | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] |
556 | | pub struct Pattern { |
557 | | original: String, |
558 | | tokens: Vec<PatternToken>, |
559 | | is_recursive: bool, |
560 | | /// A bool value that indicates whether the pattern contains any metacharacters. |
561 | | /// We use this information for some fast path optimizations. |
562 | | has_metachars: bool, |
563 | | } |
564 | | |
565 | | /// Show the original glob pattern. |
566 | | impl fmt::Display for Pattern { |
567 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
568 | 0 | self.original.fmt(f) |
569 | 0 | } |
570 | | } |
571 | | |
572 | | impl FromStr for Pattern { |
573 | | type Err = PatternError; |
574 | | |
575 | 0 | fn from_str(s: &str) -> Result<Self, PatternError> { |
576 | 0 | Self::new(s) |
577 | 0 | } |
578 | | } |
579 | | |
580 | | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
581 | | enum PatternToken { |
582 | | Char(char), |
583 | | AnyChar, |
584 | | AnySequence, |
585 | | AnyRecursiveSequence, |
586 | | AnyWithin(Vec<CharSpecifier>), |
587 | | AnyExcept(Vec<CharSpecifier>), |
588 | | } |
589 | | |
590 | | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
591 | | enum CharSpecifier { |
592 | | SingleChar(char), |
593 | | CharRange(char, char), |
594 | | } |
595 | | |
596 | | #[derive(Copy, Clone, PartialEq)] |
597 | | enum MatchResult { |
598 | | Match, |
599 | | SubPatternDoesntMatch, |
600 | | EntirePatternDoesntMatch, |
601 | | } |
602 | | |
603 | | const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`"; |
604 | | const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \ |
605 | | component"; |
606 | | const ERROR_INVALID_RANGE: &str = "invalid range pattern"; |
607 | | |
608 | | impl Pattern { |
609 | | /// This function compiles Unix shell style patterns. |
610 | | /// |
611 | | /// An invalid glob pattern will yield a `PatternError`. |
612 | 0 | pub fn new(pattern: &str) -> Result<Self, PatternError> { |
613 | 0 | let chars = pattern.chars().collect::<Vec<_>>(); |
614 | 0 | let mut tokens = Vec::new(); |
615 | 0 | let mut is_recursive = false; |
616 | 0 | let mut has_metachars = false; |
617 | 0 | let mut i = 0; |
618 | | |
619 | 0 | while i < chars.len() { |
620 | 0 | match chars[i] { |
621 | 0 | '?' => { |
622 | 0 | has_metachars = true; |
623 | 0 | tokens.push(AnyChar); |
624 | 0 | i += 1; |
625 | 0 | } |
626 | | '*' => { |
627 | 0 | has_metachars = true; |
628 | | |
629 | 0 | let old = i; |
630 | | |
631 | 0 | while i < chars.len() && chars[i] == '*' { |
632 | 0 | i += 1; |
633 | 0 | } |
634 | | |
635 | 0 | let count = i - old; |
636 | | |
637 | 0 | match count.cmp(&2) { |
638 | | Ordering::Greater => { |
639 | 0 | return Err(PatternError { |
640 | 0 | pos: old + 2, |
641 | 0 | msg: ERROR_WILDCARDS, |
642 | 0 | }) |
643 | | } |
644 | | Ordering::Equal => { |
645 | | // ** can only be an entire path component |
646 | | // i.e. a/**/b is valid, but a**/b or a/**b is not |
647 | | // invalid matches are treated literally |
648 | 0 | let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) { |
649 | | // it ends in a '/' |
650 | 0 | if i < chars.len() && path::is_separator(chars[i]) { |
651 | 0 | i += 1; |
652 | 0 | true |
653 | | // or the pattern ends here |
654 | | // this enables the existing globbing mechanism |
655 | 0 | } else if i == chars.len() { |
656 | 0 | true |
657 | | // `**` ends in non-separator |
658 | | } else { |
659 | 0 | return Err(PatternError { |
660 | 0 | pos: i, |
661 | 0 | msg: ERROR_RECURSIVE_WILDCARDS, |
662 | 0 | }); |
663 | | } |
664 | | // `**` begins with non-separator |
665 | | } else { |
666 | 0 | return Err(PatternError { |
667 | 0 | pos: old - 1, |
668 | 0 | msg: ERROR_RECURSIVE_WILDCARDS, |
669 | 0 | }); |
670 | | }; |
671 | | |
672 | 0 | if is_valid { |
673 | | // collapse consecutive AnyRecursiveSequence to a |
674 | | // single one |
675 | | |
676 | 0 | let tokens_len = tokens.len(); |
677 | | |
678 | 0 | if !(tokens_len > 1 |
679 | 0 | && tokens[tokens_len - 1] == AnyRecursiveSequence) |
680 | 0 | { |
681 | 0 | is_recursive = true; |
682 | 0 | tokens.push(AnyRecursiveSequence); |
683 | 0 | } |
684 | 0 | } |
685 | | } |
686 | 0 | Ordering::Less => tokens.push(AnySequence), |
687 | | } |
688 | | } |
689 | | '[' => { |
690 | 0 | has_metachars = true; |
691 | | |
692 | 0 | if i + 4 <= chars.len() && chars[i + 1] == '!' { |
693 | 0 | match chars[i + 3..].iter().position(|x| *x == ']') { |
694 | 0 | None => (), |
695 | 0 | Some(j) => { |
696 | 0 | let chars = &chars[i + 2..i + 3 + j]; |
697 | 0 | let cs = parse_char_specifiers(chars); |
698 | 0 | tokens.push(AnyExcept(cs)); |
699 | 0 | i += j + 4; |
700 | 0 | continue; |
701 | | } |
702 | | } |
703 | 0 | } else if i + 3 <= chars.len() && chars[i + 1] != '!' { |
704 | 0 | match chars[i + 2..].iter().position(|x| *x == ']') { |
705 | 0 | None => (), |
706 | 0 | Some(j) => { |
707 | 0 | let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]); |
708 | 0 | tokens.push(AnyWithin(cs)); |
709 | 0 | i += j + 3; |
710 | 0 | continue; |
711 | | } |
712 | | } |
713 | 0 | } |
714 | | |
715 | | // if we get here then this is not a valid range pattern |
716 | 0 | return Err(PatternError { |
717 | 0 | pos: i, |
718 | 0 | msg: ERROR_INVALID_RANGE, |
719 | 0 | }); |
720 | | } |
721 | 0 | c => { |
722 | 0 | tokens.push(Char(c)); |
723 | 0 | i += 1; |
724 | 0 | } |
725 | | } |
726 | | } |
727 | | |
728 | 0 | Ok(Self { |
729 | 0 | tokens, |
730 | 0 | original: pattern.to_string(), |
731 | 0 | is_recursive, |
732 | 0 | has_metachars, |
733 | 0 | }) |
734 | 0 | } |
735 | | |
736 | | /// Escape metacharacters within the given string by surrounding them in |
737 | | /// brackets. The resulting string will, when compiled into a `Pattern`, |
738 | | /// match the input string and nothing else. |
739 | 0 | pub fn escape(s: &str) -> String { |
740 | 0 | let mut escaped = String::new(); |
741 | 0 | for c in s.chars() { |
742 | 0 | match c { |
743 | | // note that ! does not need escaping because it is only special |
744 | | // inside brackets |
745 | 0 | '?' | '*' | '[' | ']' => { |
746 | 0 | escaped.push('['); |
747 | 0 | escaped.push(c); |
748 | 0 | escaped.push(']'); |
749 | 0 | } |
750 | 0 | c => { |
751 | 0 | escaped.push(c); |
752 | 0 | } |
753 | | } |
754 | | } |
755 | 0 | escaped |
756 | 0 | } |
757 | | |
758 | | /// Return if the given `str` matches this `Pattern` using the default |
759 | | /// match options (i.e. `MatchOptions::new()`). |
760 | | /// |
761 | | /// # Examples |
762 | | /// |
763 | | /// ```rust |
764 | | /// use glob::Pattern; |
765 | | /// |
766 | | /// assert!(Pattern::new("c?t").unwrap().matches("cat")); |
767 | | /// assert!(Pattern::new("k[!e]tteh").unwrap().matches("kitteh")); |
768 | | /// assert!(Pattern::new("d*g").unwrap().matches("doog")); |
769 | | /// ``` |
770 | 0 | pub fn matches(&self, str: &str) -> bool { |
771 | 0 | self.matches_with(str, MatchOptions::new()) |
772 | 0 | } |
773 | | |
774 | | /// Return if the given `Path`, when converted to a `str`, matches this |
775 | | /// `Pattern` using the default match options (i.e. `MatchOptions::new()`). |
776 | 0 | pub fn matches_path(&self, path: &Path) -> bool { |
777 | | // FIXME (#9639): This needs to handle non-utf8 paths |
778 | 0 | path.to_str().map_or(false, |s| self.matches(s)) |
779 | 0 | } |
780 | | |
781 | | /// Return if the given `str` matches this `Pattern` using the specified |
782 | | /// match options. |
783 | 0 | pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool { |
784 | 0 | self.matches_from(true, str.chars(), 0, options) == Match |
785 | 0 | } |
786 | | |
787 | | /// Return if the given `Path`, when converted to a `str`, matches this |
788 | | /// `Pattern` using the specified match options. |
789 | 0 | pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool { |
790 | | // FIXME (#9639): This needs to handle non-utf8 paths |
791 | 0 | path.to_str() |
792 | 0 | .map_or(false, |s| self.matches_with(s, options)) |
793 | 0 | } |
794 | | |
795 | | /// Access the original glob pattern. |
796 | 0 | pub fn as_str(&self) -> &str { |
797 | 0 | &self.original |
798 | 0 | } |
799 | | |
800 | 0 | fn matches_from( |
801 | 0 | &self, |
802 | 0 | mut follows_separator: bool, |
803 | 0 | mut file: std::str::Chars, |
804 | 0 | i: usize, |
805 | 0 | options: MatchOptions, |
806 | 0 | ) -> MatchResult { |
807 | 0 | for (ti, token) in self.tokens[i..].iter().enumerate() { |
808 | 0 | match *token { |
809 | | AnySequence | AnyRecursiveSequence => { |
810 | | // ** must be at the start. |
811 | 0 | debug_assert!(match *token { |
812 | 0 | AnyRecursiveSequence => follows_separator, |
813 | 0 | _ => true, |
814 | | }); |
815 | | |
816 | | // Empty match |
817 | 0 | match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) { |
818 | 0 | SubPatternDoesntMatch => (), // keep trying |
819 | 0 | m => return m, |
820 | | }; |
821 | | |
822 | 0 | while let Some(c) = file.next() { |
823 | 0 | if follows_separator && options.require_literal_leading_dot && c == '.' { |
824 | 0 | return SubPatternDoesntMatch; |
825 | 0 | } |
826 | 0 | follows_separator = path::is_separator(c); |
827 | 0 | match *token { |
828 | 0 | AnyRecursiveSequence if !follows_separator => continue, |
829 | | AnySequence |
830 | 0 | if options.require_literal_separator && follows_separator => |
831 | | { |
832 | 0 | return SubPatternDoesntMatch |
833 | | } |
834 | 0 | _ => (), |
835 | | } |
836 | 0 | match self.matches_from( |
837 | 0 | follows_separator, |
838 | 0 | file.clone(), |
839 | 0 | i + ti + 1, |
840 | 0 | options, |
841 | 0 | ) { |
842 | 0 | SubPatternDoesntMatch => (), // keep trying |
843 | 0 | m => return m, |
844 | | } |
845 | | } |
846 | | } |
847 | | _ => { |
848 | 0 | let c = match file.next() { |
849 | 0 | Some(c) => c, |
850 | 0 | None => return EntirePatternDoesntMatch, |
851 | | }; |
852 | | |
853 | 0 | let is_sep = path::is_separator(c); |
854 | | |
855 | 0 | if !match *token { |
856 | | AnyChar | AnyWithin(..) | AnyExcept(..) |
857 | 0 | if (options.require_literal_separator && is_sep) |
858 | 0 | || (follows_separator |
859 | 0 | && options.require_literal_leading_dot |
860 | 0 | && c == '.') => |
861 | | { |
862 | 0 | false |
863 | | } |
864 | 0 | AnyChar => true, |
865 | 0 | AnyWithin(ref specifiers) => in_char_specifiers(specifiers, c, options), |
866 | 0 | AnyExcept(ref specifiers) => !in_char_specifiers(specifiers, c, options), |
867 | 0 | Char(c2) => chars_eq(c, c2, options.case_sensitive), |
868 | 0 | AnySequence | AnyRecursiveSequence => unreachable!(), |
869 | | } { |
870 | 0 | return SubPatternDoesntMatch; |
871 | 0 | } |
872 | 0 | follows_separator = is_sep; |
873 | | } |
874 | | } |
875 | | } |
876 | | |
877 | | // Iter is fused. |
878 | 0 | if file.next().is_none() { |
879 | 0 | Match |
880 | | } else { |
881 | 0 | SubPatternDoesntMatch |
882 | | } |
883 | 0 | } |
884 | | } |
885 | | |
886 | | // Fills `todo` with paths under `path` to be matched by `patterns[idx]`, |
887 | | // special-casing patterns to match `.` and `..`, and avoiding `readdir()` |
888 | | // calls when there are no metacharacters in the pattern. |
889 | 0 | fn fill_todo( |
890 | 0 | todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>, |
891 | 0 | patterns: &[Pattern], |
892 | 0 | idx: usize, |
893 | 0 | path: &PathWrapper, |
894 | 0 | options: MatchOptions, |
895 | 0 | ) { |
896 | 0 | let add = |todo: &mut Vec<_>, next_path: PathWrapper| { |
897 | 0 | if idx + 1 == patterns.len() { |
898 | 0 | // We know it's good, so don't make the iterator match this path |
899 | 0 | // against the pattern again. In particular, it can't match |
900 | 0 | // . or .. globs since these never show up as path components. |
901 | 0 | todo.push(Ok((next_path, usize::MAX))); |
902 | 0 | } else { |
903 | 0 | fill_todo(todo, patterns, idx + 1, &next_path, options); |
904 | 0 | } |
905 | 0 | }; |
906 | | |
907 | 0 | let pattern = &patterns[idx]; |
908 | 0 | let is_dir = path.is_directory; |
909 | 0 | let curdir = path.as_ref() == Path::new("."); |
910 | 0 | match (pattern.has_metachars, is_dir) { |
911 | | (false, _) => { |
912 | 0 | debug_assert!( |
913 | 0 | pattern |
914 | 0 | .tokens |
915 | 0 | .iter() |
916 | 0 | .all(|tok| matches!(tok, PatternToken::Char(_))), |
917 | 0 | "broken invariant: pattern has metachars but shouldn't" |
918 | | ); |
919 | 0 | let s = pattern.as_str(); |
920 | | |
921 | | // This pattern component doesn't have any metacharacters, so we |
922 | | // don't need to read the current directory to know where to |
923 | | // continue. So instead of passing control back to the iterator, |
924 | | // we can just check for that one entry and potentially recurse |
925 | | // right away. |
926 | 0 | let special = "." == s || ".." == s; |
927 | 0 | let next_path = if curdir { |
928 | 0 | PathBuf::from(s) |
929 | | } else { |
930 | 0 | path.join(s) |
931 | | }; |
932 | 0 | let next_path = PathWrapper::from_path(next_path); |
933 | 0 | if (special && is_dir) |
934 | 0 | || (!special |
935 | 0 | && (fs::metadata(&next_path).is_ok() |
936 | 0 | || fs::symlink_metadata(&next_path).is_ok())) |
937 | 0 | { |
938 | 0 | add(todo, next_path); |
939 | 0 | } |
940 | | } |
941 | | (true, true) => { |
942 | 0 | let dirs = fs::read_dir(path).and_then(|d| { |
943 | 0 | d.map(|e| { |
944 | 0 | e.map(|e| { |
945 | | // We sort by filename a few lines below. However, it is actually quite |
946 | | // expensive to extract the filename from PathBuf. Since we already know the |
947 | | // filename from `DirEntry` here, we store it proactively (we still |
948 | | // have to heap allocate it). |
949 | | // Then we use this cached filename for sorting, and further on work only |
950 | | // with the full path. |
951 | 0 | let (path, filename) = if curdir { |
952 | 0 | (PathBuf::from(e.path().file_name().unwrap()), e.file_name()) |
953 | | } else { |
954 | 0 | (e.path(), e.file_name()) |
955 | | }; |
956 | 0 | (PathWrapper::from_dir_entry(path, e), filename) |
957 | 0 | }) |
958 | 0 | }) |
959 | 0 | .collect::<Result<Vec<(PathWrapper, OsString)>, _>>() |
960 | 0 | }); |
961 | 0 | match dirs { |
962 | 0 | Ok(mut children) => { |
963 | 0 | if options.require_literal_leading_dot { |
964 | 0 | children.retain(|x| !x.1.to_str().unwrap().starts_with('.')); |
965 | 0 | } |
966 | 0 | children.sort_by(|p1, p2| p2.1.cmp(&p1.1)); |
967 | 0 | todo.extend(children.into_iter().map(|x| Ok((x.0, idx)))); |
968 | | |
969 | | // Matching the special directory entries . and .. that |
970 | | // refer to the current and parent directory respectively |
971 | | // requires that the pattern has a leading dot, even if the |
972 | | // `MatchOptions` field `require_literal_leading_dot` is not |
973 | | // set. |
974 | 0 | if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') { |
975 | 0 | for &special in &[".", ".."] { |
976 | 0 | if pattern.matches_with(special, options) { |
977 | 0 | add(todo, PathWrapper::from_path(path.join(special))); |
978 | 0 | } |
979 | | } |
980 | 0 | } |
981 | | } |
982 | 0 | Err(e) => { |
983 | 0 | todo.push(Err(GlobError { |
984 | 0 | path: path.to_path_buf(), |
985 | 0 | error: e, |
986 | 0 | })); |
987 | 0 | } |
988 | | } |
989 | | } |
990 | 0 | (true, false) => { |
991 | 0 | // not a directory, nothing more to find |
992 | 0 | } |
993 | | } |
994 | 0 | } |
995 | | |
996 | 0 | fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> { |
997 | 0 | let mut cs = Vec::new(); |
998 | 0 | let mut i = 0; |
999 | 0 | while i < s.len() { |
1000 | 0 | if i + 3 <= s.len() && s[i + 1] == '-' { |
1001 | 0 | cs.push(CharRange(s[i], s[i + 2])); |
1002 | 0 | i += 3; |
1003 | 0 | } else { |
1004 | 0 | cs.push(SingleChar(s[i])); |
1005 | 0 | i += 1; |
1006 | 0 | } |
1007 | | } |
1008 | 0 | cs |
1009 | 0 | } |
1010 | | |
1011 | 0 | fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool { |
1012 | 0 | for &specifier in specifiers.iter() { |
1013 | 0 | match specifier { |
1014 | 0 | SingleChar(sc) => { |
1015 | 0 | if chars_eq(c, sc, options.case_sensitive) { |
1016 | 0 | return true; |
1017 | 0 | } |
1018 | | } |
1019 | 0 | CharRange(start, end) => { |
1020 | | // FIXME: work with non-ascii chars properly (issue #1347) |
1021 | 0 | if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() { |
1022 | 0 | let start = start.to_ascii_lowercase(); |
1023 | 0 | let end = end.to_ascii_lowercase(); |
1024 | | |
1025 | 0 | let start_up = start.to_uppercase().next().unwrap(); |
1026 | 0 | let end_up = end.to_uppercase().next().unwrap(); |
1027 | | |
1028 | | // only allow case insensitive matching when |
1029 | | // both start and end are within a-z or A-Z |
1030 | 0 | if start != start_up && end != end_up { |
1031 | 0 | let c = c.to_ascii_lowercase(); |
1032 | 0 | if c >= start && c <= end { |
1033 | 0 | return true; |
1034 | 0 | } |
1035 | 0 | } |
1036 | 0 | } |
1037 | | |
1038 | 0 | if c >= start && c <= end { |
1039 | 0 | return true; |
1040 | 0 | } |
1041 | | } |
1042 | | } |
1043 | | } |
1044 | | |
1045 | 0 | false |
1046 | 0 | } |
1047 | | |
1048 | | /// A helper function to determine if two chars are (possibly case-insensitively) equal. |
1049 | 0 | fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool { |
1050 | 0 | if cfg!(windows) && path::is_separator(a) && path::is_separator(b) { |
1051 | 0 | true |
1052 | 0 | } else if !case_sensitive && a.is_ascii() && b.is_ascii() { |
1053 | | // FIXME: work with non-ascii chars properly (issue #9084) |
1054 | 0 | a.eq_ignore_ascii_case(&b) |
1055 | | } else { |
1056 | 0 | a == b |
1057 | | } |
1058 | 0 | } |
1059 | | |
1060 | | /// Configuration options to modify the behaviour of `Pattern::matches_with(..)`. |
1061 | | #[allow(missing_copy_implementations)] |
1062 | | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] |
1063 | | pub struct MatchOptions { |
1064 | | /// Whether or not patterns should be matched in a case-sensitive manner. |
1065 | | /// This currently only considers upper/lower case relationships between |
1066 | | /// ASCII characters, but in future this might be extended to work with |
1067 | | /// Unicode. |
1068 | | pub case_sensitive: bool, |
1069 | | |
1070 | | /// Whether or not path-component separator characters (e.g. `/` on |
1071 | | /// Posix) must be matched by a literal `/`, rather than by `*` or `?` or |
1072 | | /// `[...]`. |
1073 | | pub require_literal_separator: bool, |
1074 | | |
1075 | | /// Whether or not paths that contain components that start with a `.` |
1076 | | /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, |
1077 | | /// or `[...]` will not match. This is useful because such files are |
1078 | | /// conventionally considered hidden on Unix systems and it might be |
1079 | | /// desirable to skip them when listing files. |
1080 | | pub require_literal_leading_dot: bool, |
1081 | | } |
1082 | | |
1083 | | impl MatchOptions { |
1084 | | /// Constructs a new `MatchOptions` with default field values. This is used |
1085 | | /// when calling functions that do not take an explicit `MatchOptions` |
1086 | | /// parameter. |
1087 | | /// |
1088 | | /// This function always returns this value: |
1089 | | /// |
1090 | | /// ```rust,ignore |
1091 | | /// MatchOptions { |
1092 | | /// case_sensitive: true, |
1093 | | /// require_literal_separator: false, |
1094 | | /// require_literal_leading_dot: false |
1095 | | /// } |
1096 | | /// ``` |
1097 | | /// |
1098 | | /// # Note |
1099 | | /// The behavior of this method doesn't match `default()`'s. This returns |
1100 | | /// `case_sensitive` as `true` while `default()` does it as `false`. |
1101 | | // FIXME: Consider unity the behavior with `default()` in a next major release. |
1102 | 0 | pub fn new() -> Self { |
1103 | 0 | Self { |
1104 | 0 | case_sensitive: true, |
1105 | 0 | require_literal_separator: false, |
1106 | 0 | require_literal_leading_dot: false, |
1107 | 0 | } |
1108 | 0 | } |
1109 | | } |
1110 | | |
1111 | | #[cfg(test)] |
1112 | | mod test { |
1113 | | use super::{glob, MatchOptions, Pattern}; |
1114 | | use std::path::Path; |
1115 | | |
1116 | | #[test] |
1117 | | fn test_pattern_from_str() { |
1118 | | assert!("a*b".parse::<Pattern>().unwrap().matches("a_b")); |
1119 | | assert!("a/**b".parse::<Pattern>().unwrap_err().pos == 4); |
1120 | | } |
1121 | | |
1122 | | #[test] |
1123 | | fn test_wildcard_errors() { |
1124 | | assert!(Pattern::new("a/**b").unwrap_err().pos == 4); |
1125 | | assert!(Pattern::new("a/bc**").unwrap_err().pos == 3); |
1126 | | assert!(Pattern::new("a/*****").unwrap_err().pos == 4); |
1127 | | assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2); |
1128 | | assert!(Pattern::new("a**b").unwrap_err().pos == 0); |
1129 | | } |
1130 | | |
1131 | | #[test] |
1132 | | fn test_unclosed_bracket_errors() { |
1133 | | assert!(Pattern::new("abc[def").unwrap_err().pos == 3); |
1134 | | assert!(Pattern::new("abc[!def").unwrap_err().pos == 3); |
1135 | | assert!(Pattern::new("abc[").unwrap_err().pos == 3); |
1136 | | assert!(Pattern::new("abc[!").unwrap_err().pos == 3); |
1137 | | assert!(Pattern::new("abc[d").unwrap_err().pos == 3); |
1138 | | assert!(Pattern::new("abc[!d").unwrap_err().pos == 3); |
1139 | | assert!(Pattern::new("abc[]").unwrap_err().pos == 3); |
1140 | | assert!(Pattern::new("abc[!]").unwrap_err().pos == 3); |
1141 | | } |
1142 | | |
1143 | | #[test] |
1144 | | fn test_glob_errors() { |
1145 | | assert!(glob("a/**b").err().unwrap().pos == 4); |
1146 | | assert!(glob("abc[def").err().unwrap().pos == 3); |
1147 | | } |
1148 | | |
1149 | | // this test assumes that there is a /root directory and that |
1150 | | // the user running this test is not root or otherwise doesn't |
1151 | | // have permission to read its contents |
1152 | | #[cfg(all(unix, not(target_os = "macos")))] |
1153 | | #[test] |
1154 | | fn test_iteration_errors() { |
1155 | | use std::io; |
1156 | | let mut iter = glob("/root/*").unwrap(); |
1157 | | |
1158 | | // GlobErrors shouldn't halt iteration |
1159 | | let next = iter.next(); |
1160 | | assert!(next.is_some()); |
1161 | | |
1162 | | let err = next.unwrap(); |
1163 | | assert!(err.is_err()); |
1164 | | |
1165 | | let err = err.err().unwrap(); |
1166 | | assert!(err.path() == Path::new("/root")); |
1167 | | assert!(err.error().kind() == io::ErrorKind::PermissionDenied); |
1168 | | } |
1169 | | |
1170 | | #[test] |
1171 | | fn test_absolute_pattern() { |
1172 | | assert!(glob("/").unwrap().next().is_some()); |
1173 | | assert!(glob("//").unwrap().next().is_some()); |
1174 | | |
1175 | | // assume that the filesystem is not empty! |
1176 | | assert!(glob("/*").unwrap().next().is_some()); |
1177 | | |
1178 | | #[cfg(not(windows))] |
1179 | | fn win() {} |
1180 | | |
1181 | | #[cfg(windows)] |
1182 | | fn win() { |
1183 | | use std::env::current_dir; |
1184 | | use std::path::Component; |
1185 | | |
1186 | | // check windows absolute paths with host/device components |
1187 | | let root_with_device = current_dir() |
1188 | | .ok() |
1189 | | .and_then(|p| match p.components().next().unwrap() { |
1190 | | Component::Prefix(prefix_component) => { |
1191 | | let path = Path::new(prefix_component.as_os_str()).join("*"); |
1192 | | Some(path.to_path_buf()) |
1193 | | } |
1194 | | _ => panic!("no prefix in this path"), |
1195 | | }) |
1196 | | .unwrap(); |
1197 | | // FIXME (#9639): This needs to handle non-utf8 paths |
1198 | | assert!(glob(root_with_device.as_os_str().to_str().unwrap()) |
1199 | | .unwrap() |
1200 | | .next() |
1201 | | .is_some()); |
1202 | | } |
1203 | | win() |
1204 | | } |
1205 | | |
1206 | | #[test] |
1207 | | fn test_wildcards() { |
1208 | | assert!(Pattern::new("a*b").unwrap().matches("a_b")); |
1209 | | assert!(Pattern::new("a*b*c").unwrap().matches("abc")); |
1210 | | assert!(!Pattern::new("a*b*c").unwrap().matches("abcd")); |
1211 | | assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c")); |
1212 | | assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c")); |
1213 | | assert!(Pattern::new("abc*abc*abc") |
1214 | | .unwrap() |
1215 | | .matches("abcabcabcabcabcabcabc")); |
1216 | | assert!(!Pattern::new("abc*abc*abc") |
1217 | | .unwrap() |
1218 | | .matches("abcabcabcabcabcabcabca")); |
1219 | | assert!(Pattern::new("a*a*a*a*a*a*a*a*a") |
1220 | | .unwrap() |
1221 | | .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); |
1222 | | assert!(Pattern::new("a*b[xyz]c*d").unwrap().matches("abxcdbxcddd")); |
1223 | | } |
1224 | | |
1225 | | #[test] |
1226 | | fn test_recursive_wildcards() { |
1227 | | let pat = Pattern::new("some/**/needle.txt").unwrap(); |
1228 | | assert!(pat.matches("some/needle.txt")); |
1229 | | assert!(pat.matches("some/one/needle.txt")); |
1230 | | assert!(pat.matches("some/one/two/needle.txt")); |
1231 | | assert!(pat.matches("some/other/needle.txt")); |
1232 | | assert!(!pat.matches("some/other/notthis.txt")); |
1233 | | |
1234 | | // a single ** should be valid, for globs |
1235 | | // Should accept anything |
1236 | | let pat = Pattern::new("**").unwrap(); |
1237 | | assert!(pat.is_recursive); |
1238 | | assert!(pat.matches("abcde")); |
1239 | | assert!(pat.matches("")); |
1240 | | assert!(pat.matches(".asdf")); |
1241 | | assert!(pat.matches("/x/.asdf")); |
1242 | | |
1243 | | // collapse consecutive wildcards |
1244 | | let pat = Pattern::new("some/**/**/needle.txt").unwrap(); |
1245 | | assert!(pat.matches("some/needle.txt")); |
1246 | | assert!(pat.matches("some/one/needle.txt")); |
1247 | | assert!(pat.matches("some/one/two/needle.txt")); |
1248 | | assert!(pat.matches("some/other/needle.txt")); |
1249 | | assert!(!pat.matches("some/other/notthis.txt")); |
1250 | | |
1251 | | // ** can begin the pattern |
1252 | | let pat = Pattern::new("**/test").unwrap(); |
1253 | | assert!(pat.matches("one/two/test")); |
1254 | | assert!(pat.matches("one/test")); |
1255 | | assert!(pat.matches("test")); |
1256 | | |
1257 | | // /** can begin the pattern |
1258 | | let pat = Pattern::new("/**/test").unwrap(); |
1259 | | assert!(pat.matches("/one/two/test")); |
1260 | | assert!(pat.matches("/one/test")); |
1261 | | assert!(pat.matches("/test")); |
1262 | | assert!(!pat.matches("/one/notthis")); |
1263 | | assert!(!pat.matches("/notthis")); |
1264 | | |
1265 | | // Only start sub-patterns on start of path segment. |
1266 | | let pat = Pattern::new("**/.*").unwrap(); |
1267 | | assert!(pat.matches(".abc")); |
1268 | | assert!(pat.matches("abc/.abc")); |
1269 | | assert!(!pat.matches("ab.c")); |
1270 | | assert!(!pat.matches("abc/ab.c")); |
1271 | | } |
1272 | | |
1273 | | #[test] |
1274 | | fn test_lots_of_files() { |
1275 | | // this is a good test because it touches lots of differently named files |
1276 | | glob("/*/*/*/*").unwrap().skip(10000).next(); |
1277 | | } |
1278 | | |
1279 | | #[test] |
1280 | | fn test_range_pattern() { |
1281 | | let pat = Pattern::new("a[0-9]b").unwrap(); |
1282 | | for i in 0..10 { |
1283 | | assert!(pat.matches(&format!("a{}b", i))); |
1284 | | } |
1285 | | assert!(!pat.matches("a_b")); |
1286 | | |
1287 | | let pat = Pattern::new("a[!0-9]b").unwrap(); |
1288 | | for i in 0..10 { |
1289 | | assert!(!pat.matches(&format!("a{}b", i))); |
1290 | | } |
1291 | | assert!(pat.matches("a_b")); |
1292 | | |
1293 | | let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"]; |
1294 | | for &p in pats.iter() { |
1295 | | let pat = Pattern::new(p).unwrap(); |
1296 | | for c in "abcdefghijklmnopqrstuvwxyz".chars() { |
1297 | | assert!(pat.matches(&c.to_string())); |
1298 | | } |
1299 | | for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() { |
1300 | | let options = MatchOptions { |
1301 | | case_sensitive: false, |
1302 | | ..MatchOptions::new() |
1303 | | }; |
1304 | | assert!(pat.matches_with(&c.to_string(), options)); |
1305 | | } |
1306 | | assert!(pat.matches("1")); |
1307 | | assert!(pat.matches("2")); |
1308 | | assert!(pat.matches("3")); |
1309 | | } |
1310 | | |
1311 | | let pats = ["[abc-]", "[-abc]", "[a-c-]"]; |
1312 | | for &p in pats.iter() { |
1313 | | let pat = Pattern::new(p).unwrap(); |
1314 | | assert!(pat.matches("a")); |
1315 | | assert!(pat.matches("b")); |
1316 | | assert!(pat.matches("c")); |
1317 | | assert!(pat.matches("-")); |
1318 | | assert!(!pat.matches("d")); |
1319 | | } |
1320 | | |
1321 | | let pat = Pattern::new("[2-1]").unwrap(); |
1322 | | assert!(!pat.matches("1")); |
1323 | | assert!(!pat.matches("2")); |
1324 | | |
1325 | | assert!(Pattern::new("[-]").unwrap().matches("-")); |
1326 | | assert!(!Pattern::new("[!-]").unwrap().matches("-")); |
1327 | | } |
1328 | | |
1329 | | #[test] |
1330 | | fn test_pattern_matches() { |
1331 | | let txt_pat = Pattern::new("*hello.txt").unwrap(); |
1332 | | assert!(txt_pat.matches("hello.txt")); |
1333 | | assert!(txt_pat.matches("gareth_says_hello.txt")); |
1334 | | assert!(txt_pat.matches("some/path/to/hello.txt")); |
1335 | | assert!(txt_pat.matches("some\\path\\to\\hello.txt")); |
1336 | | assert!(txt_pat.matches("/an/absolute/path/to/hello.txt")); |
1337 | | assert!(!txt_pat.matches("hello.txt-and-then-some")); |
1338 | | assert!(!txt_pat.matches("goodbye.txt")); |
1339 | | |
1340 | | let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap(); |
1341 | | assert!(dir_pat.matches("some/path/to/hello.txt")); |
1342 | | assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt")); |
1343 | | assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some")); |
1344 | | assert!(!dir_pat.matches("some/other/path/to/hello.txt")); |
1345 | | } |
1346 | | |
1347 | | #[test] |
1348 | | fn test_pattern_escape() { |
1349 | | let s = "_[_]_?_*_!_"; |
1350 | | assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string()); |
1351 | | assert!(Pattern::new(&Pattern::escape(s)).unwrap().matches(s)); |
1352 | | } |
1353 | | |
1354 | | #[test] |
1355 | | fn test_pattern_matches_case_insensitive() { |
1356 | | let pat = Pattern::new("aBcDeFg").unwrap(); |
1357 | | let options = MatchOptions { |
1358 | | case_sensitive: false, |
1359 | | require_literal_separator: false, |
1360 | | require_literal_leading_dot: false, |
1361 | | }; |
1362 | | |
1363 | | assert!(pat.matches_with("aBcDeFg", options)); |
1364 | | assert!(pat.matches_with("abcdefg", options)); |
1365 | | assert!(pat.matches_with("ABCDEFG", options)); |
1366 | | assert!(pat.matches_with("AbCdEfG", options)); |
1367 | | } |
1368 | | |
1369 | | #[test] |
1370 | | fn test_pattern_matches_case_insensitive_range() { |
1371 | | let pat_within = Pattern::new("[a]").unwrap(); |
1372 | | let pat_except = Pattern::new("[!a]").unwrap(); |
1373 | | |
1374 | | let options_case_insensitive = MatchOptions { |
1375 | | case_sensitive: false, |
1376 | | require_literal_separator: false, |
1377 | | require_literal_leading_dot: false, |
1378 | | }; |
1379 | | let options_case_sensitive = MatchOptions { |
1380 | | case_sensitive: true, |
1381 | | require_literal_separator: false, |
1382 | | require_literal_leading_dot: false, |
1383 | | }; |
1384 | | |
1385 | | assert!(pat_within.matches_with("a", options_case_insensitive)); |
1386 | | assert!(pat_within.matches_with("A", options_case_insensitive)); |
1387 | | assert!(!pat_within.matches_with("A", options_case_sensitive)); |
1388 | | |
1389 | | assert!(!pat_except.matches_with("a", options_case_insensitive)); |
1390 | | assert!(!pat_except.matches_with("A", options_case_insensitive)); |
1391 | | assert!(pat_except.matches_with("A", options_case_sensitive)); |
1392 | | } |
1393 | | |
1394 | | #[test] |
1395 | | fn test_pattern_matches_require_literal_separator() { |
1396 | | let options_require_literal = MatchOptions { |
1397 | | case_sensitive: true, |
1398 | | require_literal_separator: true, |
1399 | | require_literal_leading_dot: false, |
1400 | | }; |
1401 | | let options_not_require_literal = MatchOptions { |
1402 | | case_sensitive: true, |
1403 | | require_literal_separator: false, |
1404 | | require_literal_leading_dot: false, |
1405 | | }; |
1406 | | |
1407 | | assert!(Pattern::new("abc/def") |
1408 | | .unwrap() |
1409 | | .matches_with("abc/def", options_require_literal)); |
1410 | | assert!(!Pattern::new("abc?def") |
1411 | | .unwrap() |
1412 | | .matches_with("abc/def", options_require_literal)); |
1413 | | assert!(!Pattern::new("abc*def") |
1414 | | .unwrap() |
1415 | | .matches_with("abc/def", options_require_literal)); |
1416 | | assert!(!Pattern::new("abc[/]def") |
1417 | | .unwrap() |
1418 | | .matches_with("abc/def", options_require_literal)); |
1419 | | |
1420 | | assert!(Pattern::new("abc/def") |
1421 | | .unwrap() |
1422 | | .matches_with("abc/def", options_not_require_literal)); |
1423 | | assert!(Pattern::new("abc?def") |
1424 | | .unwrap() |
1425 | | .matches_with("abc/def", options_not_require_literal)); |
1426 | | assert!(Pattern::new("abc*def") |
1427 | | .unwrap() |
1428 | | .matches_with("abc/def", options_not_require_literal)); |
1429 | | assert!(Pattern::new("abc[/]def") |
1430 | | .unwrap() |
1431 | | .matches_with("abc/def", options_not_require_literal)); |
1432 | | } |
1433 | | |
1434 | | #[test] |
1435 | | fn test_pattern_matches_require_literal_leading_dot() { |
1436 | | let options_require_literal_leading_dot = MatchOptions { |
1437 | | case_sensitive: true, |
1438 | | require_literal_separator: false, |
1439 | | require_literal_leading_dot: true, |
1440 | | }; |
1441 | | let options_not_require_literal_leading_dot = MatchOptions { |
1442 | | case_sensitive: true, |
1443 | | require_literal_separator: false, |
1444 | | require_literal_leading_dot: false, |
1445 | | }; |
1446 | | |
1447 | | let f = |options| { |
1448 | | Pattern::new("*.txt") |
1449 | | .unwrap() |
1450 | | .matches_with(".hello.txt", options) |
1451 | | }; |
1452 | | assert!(f(options_not_require_literal_leading_dot)); |
1453 | | assert!(!f(options_require_literal_leading_dot)); |
1454 | | |
1455 | | let f = |options| { |
1456 | | Pattern::new(".*.*") |
1457 | | .unwrap() |
1458 | | .matches_with(".hello.txt", options) |
1459 | | }; |
1460 | | assert!(f(options_not_require_literal_leading_dot)); |
1461 | | assert!(f(options_require_literal_leading_dot)); |
1462 | | |
1463 | | let f = |options| { |
1464 | | Pattern::new("aaa/bbb/*") |
1465 | | .unwrap() |
1466 | | .matches_with("aaa/bbb/.ccc", options) |
1467 | | }; |
1468 | | assert!(f(options_not_require_literal_leading_dot)); |
1469 | | assert!(!f(options_require_literal_leading_dot)); |
1470 | | |
1471 | | let f = |options| { |
1472 | | Pattern::new("aaa/bbb/*") |
1473 | | .unwrap() |
1474 | | .matches_with("aaa/bbb/c.c.c.", options) |
1475 | | }; |
1476 | | assert!(f(options_not_require_literal_leading_dot)); |
1477 | | assert!(f(options_require_literal_leading_dot)); |
1478 | | |
1479 | | let f = |options| { |
1480 | | Pattern::new("aaa/bbb/.*") |
1481 | | .unwrap() |
1482 | | .matches_with("aaa/bbb/.ccc", options) |
1483 | | }; |
1484 | | assert!(f(options_not_require_literal_leading_dot)); |
1485 | | assert!(f(options_require_literal_leading_dot)); |
1486 | | |
1487 | | let f = |options| { |
1488 | | Pattern::new("aaa/?bbb") |
1489 | | .unwrap() |
1490 | | .matches_with("aaa/.bbb", options) |
1491 | | }; |
1492 | | assert!(f(options_not_require_literal_leading_dot)); |
1493 | | assert!(!f(options_require_literal_leading_dot)); |
1494 | | |
1495 | | let f = |options| { |
1496 | | Pattern::new("aaa/[.]bbb") |
1497 | | .unwrap() |
1498 | | .matches_with("aaa/.bbb", options) |
1499 | | }; |
1500 | | assert!(f(options_not_require_literal_leading_dot)); |
1501 | | assert!(!f(options_require_literal_leading_dot)); |
1502 | | |
1503 | | let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options); |
1504 | | assert!(f(options_not_require_literal_leading_dot)); |
1505 | | assert!(!f(options_require_literal_leading_dot)); |
1506 | | } |
1507 | | |
1508 | | #[test] |
1509 | | fn test_matches_path() { |
1510 | | // on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this |
1511 | | // tests that / and \ are considered equivalent on windows |
1512 | | assert!(Pattern::new("a/b").unwrap().matches_path(Path::new("a/b"))); |
1513 | | } |
1514 | | |
1515 | | #[test] |
1516 | | fn test_path_join() { |
1517 | | let pattern = Path::new("one").join(Path::new("**/*.rs")); |
1518 | | assert!(Pattern::new(pattern.to_str().unwrap()).is_ok()); |
1519 | | } |
1520 | | } |