Skip to main content

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