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