Coverage Report

Created: 2026-10-28 07:07

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