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