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