Skip to main content

glob/
lib.rs

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)]
67doc_comment::doctest!("../README.md");
68
69use std::cmp;
70use std::cmp::Ordering;
71use std::error::Error;
72use std::ffi::OsString;
73use std::fmt;
74use std::fs;
75use std::fs::DirEntry;
76use std::io;
77use std::ops::Deref;
78use std::path::{self, Component, Path, PathBuf};
79use std::str::FromStr;
80
81use CharSpecifier::{CharRange, SingleChar};
82use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch};
83use PatternToken::AnyExcept;
84use 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)]
96pub 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.
161pub fn glob(pattern: &str) -> Result<Paths, PatternError> {
162    glob_with(pattern, MatchOptions::new())
163}
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.
178pub 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    fn check_windows_verbatim(_: &Path) -> bool {
196        false
197    }
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    fn to_scope(p: &Path) -> PathBuf {
206        p.to_path_buf()
207    }
208
209    // make sure that the pattern is valid first, else early return with error
210    let _ = Pattern::new(pattern)?;
211
212    let mut components = Path::new(pattern).components().peekable();
213    loop {
214        match components.peek() {
215            Some(&Component::Prefix(..)) | Some(&Component::RootDir) => {
216                components.next();
217            }
218            _ => break,
219        }
220    }
221    let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>();
222    let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>();
223    let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len();
224    let root = if root_len > 0 {
225        Some(Path::new(&pattern[..root_len]))
226    } else {
227        None
228    };
229
230    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        return Ok(Paths {
235            dir_patterns: Vec::new(),
236            require_dir: false,
237            options,
238            todo: Vec::new(),
239            scope: None,
240        });
241    }
242
243    let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
244    let scope = PathWrapper::from_path(scope);
245
246    let mut dir_patterns = Vec::new();
247    let components =
248        pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator);
249
250    for component in components {
251        dir_patterns.push(Pattern::new(component)?);
252    }
253
254    if root_len == pattern.len() {
255        dir_patterns.push(Pattern {
256            original: "".to_string(),
257            tokens: Vec::new(),
258            is_recursive: false,
259            has_metachars: false,
260        });
261    }
262
263    let last_is_separator = pattern.chars().next_back().map(path::is_separator);
264    let require_dir = last_is_separator == Some(true);
265    let todo = Vec::new();
266
267    Ok(Paths {
268        dir_patterns,
269        require_dir,
270        options,
271        todo,
272        scope: Some(scope),
273    })
274}
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)]
282pub struct GlobError {
283    path: PathBuf,
284    error: io::Error,
285}
286
287impl GlobError {
288    /// The Path that the error corresponds to.
289    pub fn path(&self) -> &Path {
290        &self.path
291    }
292
293    /// The error in question.
294    pub fn error(&self) -> &io::Error {
295        &self.error
296    }
297
298    /// Consumes self, returning the _raw_ underlying `io::Error`
299    #[deprecated(note = "use `.into` instead")]
300    pub fn into_error(self) -> io::Error {
301        self.error
302    }
303}
304
305impl From<GlobError> for io::Error {
306    fn from(value: GlobError) -> Self {
307        value.error
308    }
309}
310
311impl Error for GlobError {
312    #[allow(deprecated)]
313    fn description(&self) -> &str {
314        self.error.description()
315    }
316
317    #[allow(unknown_lints, bare_trait_objects)]
318    fn cause(&self) -> Option<&dyn Error> {
319        Some(&self.error)
320    }
321}
322
323impl fmt::Display for GlobError {
324    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
325        write!(
326            f,
327            "attempting to read `{}` resulted in an error: {}",
328            self.path.display(),
329            self.error
330        )
331    }
332}
333
334#[derive(Debug)]
335struct PathWrapper {
336    path: PathBuf,
337    is_directory: bool,
338}
339
340impl PathWrapper {
341    fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self {
342        let is_directory = e
343            .file_type()
344            .ok()
345            .and_then(|file_type| {
346                // We need to use fs::metadata to resolve the actual path
347                // if it's a symlink.
348                if file_type.is_symlink() {
349                    None
350                } else {
351                    Some(file_type.is_dir())
352                }
353            })
354            .or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok())
355            .unwrap_or(false);
356        Self { path, is_directory }
357    }
358    fn from_path(path: PathBuf) -> Self {
359        let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false);
360        Self { path, is_directory }
361    }
362
363    fn into_path(self) -> PathBuf {
364        self.path
365    }
366}
367
368impl Deref for PathWrapper {
369    type Target = Path;
370
371    fn deref(&self) -> &Self::Target {
372        self.path.deref()
373    }
374}
375
376impl AsRef<Path> for PathWrapper {
377    fn as_ref(&self) -> &Path {
378        self.path.as_ref()
379    }
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.
386pub type GlobResult = Result<PathBuf, GlobError>;
387
388impl Iterator for Paths {
389    type Item = GlobResult;
390
391    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        if let Some(scope) = self.scope.take() {
397            if !self.dir_patterns.is_empty() {
398                // Shouldn't happen, but we're using -1 as a special index.
399                assert!(self.dir_patterns.len() < usize::MAX);
400
401                fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options);
402            }
403        }
404
405        loop {
406            if self.dir_patterns.is_empty() || self.todo.is_empty() {
407                return None;
408            }
409
410            let (path, mut idx) = match self.todo.pop().unwrap() {
411                Ok(pair) => pair,
412                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            if idx == usize::MAX {
418                if self.require_dir && !path.is_directory {
419                    continue;
420                }
421                return Some(Ok(path.into_path()));
422            }
423
424            if self.dir_patterns[idx].is_recursive {
425                let mut next = idx;
426
427                // collapse consecutive recursive patterns
428                while (next + 1) < self.dir_patterns.len()
429                    && self.dir_patterns[next + 1].is_recursive
430                {
431                    next += 1;
432                }
433
434                if path.is_directory {
435                    // the path is a directory, so it's a match
436
437                    // push this directory's contents
438                    fill_todo(
439                        &mut self.todo,
440                        &self.dir_patterns,
441                        next,
442                        &path,
443                        self.options,
444                    );
445
446                    if next == self.dir_patterns.len() - 1 {
447                        // pattern ends in recursive pattern, so return this
448                        // directory as a result
449                        return Some(Ok(path.into_path()));
450                    } else {
451                        // advanced to the next pattern for this path
452                        idx = next + 1;
453                    }
454                } else if next == self.dir_patterns.len() - 1 {
455                    // not a directory and it's the last pattern, meaning no
456                    // match
457                    continue;
458                } else {
459                    // advanced to the next pattern for this path
460                    idx = next + 1;
461                }
462            }
463
464            // not recursive, so match normally
465            if self.dir_patterns[idx].matches_with(
466                {
467                    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                        None => continue,
472                        Some(x) => x,
473                    }
474                },
475                self.options,
476            ) {
477                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                    if !self.require_dir || path.is_directory {
483                        return Some(Ok(path.into_path()));
484                    }
485                } else {
486                    fill_todo(
487                        &mut self.todo,
488                        &self.dir_patterns,
489                        idx + 1,
490                        &path,
491                        self.options,
492                    );
493                }
494            }
495        }
496    }
497}
498
499/// A pattern parsing error.
500#[derive(Debug)]
501#[allow(missing_copy_implementations)]
502pub 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
510impl Error for PatternError {
511    fn description(&self) -> &str {
512        self.msg
513    }
514}
515
516impl fmt::Display for PatternError {
517    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
518        write!(
519            f,
520            "Pattern syntax error near position {}: {}",
521            self.pos, self.msg
522        )
523    }
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)]
556pub 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.
566impl fmt::Display for Pattern {
567    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
568        self.original.fmt(f)
569    }
570}
571
572impl FromStr for Pattern {
573    type Err = PatternError;
574
575    fn from_str(s: &str) -> Result<Self, PatternError> {
576        Self::new(s)
577    }
578}
579
580#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
581enum 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)]
591enum CharSpecifier {
592    SingleChar(char),
593    CharRange(char, char),
594}
595
596#[derive(Copy, Clone, PartialEq)]
597enum MatchResult {
598    Match,
599    SubPatternDoesntMatch,
600    EntirePatternDoesntMatch,
601}
602
603const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`";
604const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \
605                                         component";
606const ERROR_INVALID_RANGE: &str = "invalid range pattern";
607
608impl Pattern {
609    /// This function compiles Unix shell style patterns.
610    ///
611    /// An invalid glob pattern will yield a `PatternError`.
612    pub fn new(pattern: &str) -> Result<Self, PatternError> {
613        let chars = pattern.chars().collect::<Vec<_>>();
614        let mut tokens = Vec::new();
615        let mut is_recursive = false;
616        let mut has_metachars = false;
617        let mut i = 0;
618
619        while i < chars.len() {
620            match chars[i] {
621                '?' => {
622                    has_metachars = true;
623                    tokens.push(AnyChar);
624                    i += 1;
625                }
626                '*' => {
627                    has_metachars = true;
628
629                    let old = i;
630
631                    while i < chars.len() && chars[i] == '*' {
632                        i += 1;
633                    }
634
635                    let count = i - old;
636
637                    match count.cmp(&2) {
638                        Ordering::Greater => {
639                            return Err(PatternError {
640                                pos: old + 2,
641                                msg: ERROR_WILDCARDS,
642                            })
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                            let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) {
649                                // it ends in a '/'
650                                if i < chars.len() && path::is_separator(chars[i]) {
651                                    i += 1;
652                                    true
653                                // or the pattern ends here
654                                // this enables the existing globbing mechanism
655                                } else if i == chars.len() {
656                                    true
657                                // `**` ends in non-separator
658                                } else {
659                                    return Err(PatternError {
660                                        pos: i,
661                                        msg: ERROR_RECURSIVE_WILDCARDS,
662                                    });
663                                }
664                            // `**` begins with non-separator
665                            } else {
666                                return Err(PatternError {
667                                    pos: old - 1,
668                                    msg: ERROR_RECURSIVE_WILDCARDS,
669                                });
670                            };
671
672                            if is_valid {
673                                // collapse consecutive AnyRecursiveSequence to a
674                                // single one
675
676                                let tokens_len = tokens.len();
677
678                                if !(tokens_len > 1
679                                    && tokens[tokens_len - 1] == AnyRecursiveSequence)
680                                {
681                                    is_recursive = true;
682                                    tokens.push(AnyRecursiveSequence);
683                                }
684                            }
685                        }
686                        Ordering::Less => tokens.push(AnySequence),
687                    }
688                }
689                '[' => {
690                    has_metachars = true;
691
692                    if i + 4 <= chars.len() && chars[i + 1] == '!' {
693                        match chars[i + 3..].iter().position(|x| *x == ']') {
694                            None => (),
695                            Some(j) => {
696                                let chars = &chars[i + 2..i + 3 + j];
697                                let cs = parse_char_specifiers(chars);
698                                tokens.push(AnyExcept(cs));
699                                i += j + 4;
700                                continue;
701                            }
702                        }
703                    } else if i + 3 <= chars.len() && chars[i + 1] != '!' {
704                        match chars[i + 2..].iter().position(|x| *x == ']') {
705                            None => (),
706                            Some(j) => {
707                                let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]);
708                                tokens.push(AnyWithin(cs));
709                                i += j + 3;
710                                continue;
711                            }
712                        }
713                    }
714
715                    // if we get here then this is not a valid range pattern
716                    return Err(PatternError {
717                        pos: i,
718                        msg: ERROR_INVALID_RANGE,
719                    });
720                }
721                c => {
722                    tokens.push(Char(c));
723                    i += 1;
724                }
725            }
726        }
727
728        Ok(Self {
729            tokens,
730            original: pattern.to_string(),
731            is_recursive,
732            has_metachars,
733        })
734    }
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    pub fn escape(s: &str) -> String {
740        let mut escaped = String::new();
741        for c in s.chars() {
742            match c {
743                // note that ! does not need escaping because it is only special
744                // inside brackets
745                '?' | '*' | '[' | ']' => {
746                    escaped.push('[');
747                    escaped.push(c);
748                    escaped.push(']');
749                }
750                c => {
751                    escaped.push(c);
752                }
753            }
754        }
755        escaped
756    }
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    pub fn matches(&self, str: &str) -> bool {
771        self.matches_with(str, MatchOptions::new())
772    }
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    pub fn matches_path(&self, path: &Path) -> bool {
777        // FIXME (#9639): This needs to handle non-utf8 paths
778        path.to_str().map_or(false, |s| self.matches(s))
779    }
780
781    /// Return if the given `str` matches this `Pattern` using the specified
782    /// match options.
783    pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
784        self.matches_from(true, str.chars(), 0, options) == Match
785    }
786
787    /// Return if the given `Path`, when converted to a `str`, matches this
788    /// `Pattern` using the specified match options.
789    pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
790        // FIXME (#9639): This needs to handle non-utf8 paths
791        path.to_str()
792            .map_or(false, |s| self.matches_with(s, options))
793    }
794
795    /// Access the original glob pattern.
796    pub fn as_str(&self) -> &str {
797        &self.original
798    }
799
800    fn matches_from(
801        &self,
802        mut follows_separator: bool,
803        mut file: std::str::Chars,
804        i: usize,
805        options: MatchOptions,
806    ) -> MatchResult {
807        for (ti, token) in self.tokens[i..].iter().enumerate() {
808            match *token {
809                AnySequence | AnyRecursiveSequence => {
810                    // ** must be at the start.
811                    debug_assert!(match *token {
812                        AnyRecursiveSequence => follows_separator,
813                        _ => true,
814                    });
815
816                    // Empty match
817                    match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
818                        SubPatternDoesntMatch => (), // keep trying
819                        m => return m,
820                    };
821
822                    while let Some(c) = file.next() {
823                        if follows_separator && options.require_literal_leading_dot && c == '.' {
824                            return SubPatternDoesntMatch;
825                        }
826                        follows_separator = path::is_separator(c);
827                        match *token {
828                            AnyRecursiveSequence if !follows_separator => continue,
829                            AnySequence
830                                if options.require_literal_separator && follows_separator =>
831                            {
832                                return SubPatternDoesntMatch
833                            }
834                            _ => (),
835                        }
836                        match self.matches_from(
837                            follows_separator,
838                            file.clone(),
839                            i + ti + 1,
840                            options,
841                        ) {
842                            SubPatternDoesntMatch => (), // keep trying
843                            m => return m,
844                        }
845                    }
846                }
847                _ => {
848                    let c = match file.next() {
849                        Some(c) => c,
850                        None => return EntirePatternDoesntMatch,
851                    };
852
853                    let is_sep = path::is_separator(c);
854
855                    if !match *token {
856                        AnyChar | AnyWithin(..) | AnyExcept(..)
857                            if (options.require_literal_separator && is_sep)
858                                || (follows_separator
859                                    && options.require_literal_leading_dot
860                                    && c == '.') =>
861                        {
862                            false
863                        }
864                        AnyChar => true,
865                        AnyWithin(ref specifiers) => in_char_specifiers(specifiers, c, options),
866                        AnyExcept(ref specifiers) => !in_char_specifiers(specifiers, c, options),
867                        Char(c2) => chars_eq(c, c2, options.case_sensitive),
868                        AnySequence | AnyRecursiveSequence => unreachable!(),
869                    } {
870                        return SubPatternDoesntMatch;
871                    }
872                    follows_separator = is_sep;
873                }
874            }
875        }
876
877        // Iter is fused.
878        if file.next().is_none() {
879            Match
880        } else {
881            SubPatternDoesntMatch
882        }
883    }
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.
889fn fill_todo(
890    todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>,
891    patterns: &[Pattern],
892    idx: usize,
893    path: &PathWrapper,
894    options: MatchOptions,
895) {
896    let add = |todo: &mut Vec<_>, next_path: PathWrapper| {
897        if idx + 1 == patterns.len() {
898            // We know it's good, so don't make the iterator match this path
899            // against the pattern again. In particular, it can't match
900            // . or .. globs since these never show up as path components.
901            todo.push(Ok((next_path, usize::MAX)));
902        } else {
903            fill_todo(todo, patterns, idx + 1, &next_path, options);
904        }
905    };
906
907    let pattern = &patterns[idx];
908    let is_dir = path.is_directory;
909    let curdir = path.as_ref() == Path::new(".");
910    match (pattern.has_metachars, is_dir) {
911        (false, _) => {
912            debug_assert!(
913                pattern
914                    .tokens
915                    .iter()
916                    .all(|tok| matches!(tok, PatternToken::Char(_))),
917                "broken invariant: pattern has metachars but shouldn't"
918            );
919            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            let special = "." == s || ".." == s;
927            let next_path = if curdir {
928                PathBuf::from(s)
929            } else {
930                path.join(s)
931            };
932            let next_path = PathWrapper::from_path(next_path);
933            if (special && is_dir)
934                || (!special
935                    && (fs::metadata(&next_path).is_ok()
936                        || fs::symlink_metadata(&next_path).is_ok()))
937            {
938                add(todo, next_path);
939            }
940        }
941        (true, true) => {
942            let dirs = fs::read_dir(path).and_then(|d| {
943                d.map(|e| {
944                    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                        let (path, filename) = if curdir {
952                            (PathBuf::from(e.path().file_name().unwrap()), e.file_name())
953                        } else {
954                            (e.path(), e.file_name())
955                        };
956                        (PathWrapper::from_dir_entry(path, e), filename)
957                    })
958                })
959                .collect::<Result<Vec<(PathWrapper, OsString)>, _>>()
960            });
961            match dirs {
962                Ok(mut children) => {
963                    if options.require_literal_leading_dot {
964                        children.retain(|x| !x.1.to_str().unwrap().starts_with('.'));
965                    }
966                    children.sort_by(|p1, p2| p2.1.cmp(&p1.1));
967                    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                    if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
975                        for &special in &[".", ".."] {
976                            if pattern.matches_with(special, options) {
977                                add(todo, PathWrapper::from_path(path.join(special)));
978                            }
979                        }
980                    }
981                }
982                Err(e) => {
983                    todo.push(Err(GlobError {
984                        path: path.to_path_buf(),
985                        error: e,
986                    }));
987                }
988            }
989        }
990        (true, false) => {
991            // not a directory, nothing more to find
992        }
993    }
994}
995
996fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
997    let mut cs = Vec::new();
998    let mut i = 0;
999    while i < s.len() {
1000        if i + 3 <= s.len() && s[i + 1] == '-' {
1001            cs.push(CharRange(s[i], s[i + 2]));
1002            i += 3;
1003        } else {
1004            cs.push(SingleChar(s[i]));
1005            i += 1;
1006        }
1007    }
1008    cs
1009}
1010
1011fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
1012    for &specifier in specifiers.iter() {
1013        match specifier {
1014            SingleChar(sc) => {
1015                if chars_eq(c, sc, options.case_sensitive) {
1016                    return true;
1017                }
1018            }
1019            CharRange(start, end) => {
1020                // FIXME: work with non-ascii chars properly (issue #1347)
1021                if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
1022                    let start = start.to_ascii_lowercase();
1023                    let end = end.to_ascii_lowercase();
1024
1025                    let start_up = start.to_uppercase().next().unwrap();
1026                    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                    if start != start_up && end != end_up {
1031                        let c = c.to_ascii_lowercase();
1032                        if c >= start && c <= end {
1033                            return true;
1034                        }
1035                    }
1036                }
1037
1038                if c >= start && c <= end {
1039                    return true;
1040                }
1041            }
1042        }
1043    }
1044
1045    false
1046}
1047
1048/// A helper function to determine if two chars are (possibly case-insensitively) equal.
1049fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
1050    if cfg!(windows) && path::is_separator(a) && path::is_separator(b) {
1051        true
1052    } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
1053        // FIXME: work with non-ascii chars properly (issue #9084)
1054        a.eq_ignore_ascii_case(&b)
1055    } else {
1056        a == b
1057    }
1058}
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)]
1063pub 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
1083impl 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    pub fn new() -> Self {
1103        Self {
1104            case_sensitive: true,
1105            require_literal_separator: false,
1106            require_literal_leading_dot: false,
1107        }
1108    }
1109}
1110
1111#[cfg(test)]
1112mod 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}