Skip to main content

globset/
lib.rs

1/*!
2The globset crate provides cross platform single glob and glob set matching.
3
4Glob set matching is the process of matching one or more glob patterns against
5a single candidate path simultaneously, and returning all of the globs that
6matched. For example, given this set of globs:
7
8* `*.rs`
9* `src/lib.rs`
10* `src/**/foo.rs`
11
12and a path `src/bar/baz/foo.rs`, then the set would report the first and third
13globs as matching.
14
15# Example: one glob
16
17This example shows how to match a single glob against a single file path.
18
19```
20use globset::Glob;
21
22let glob = Glob::new("*.rs")?.compile_matcher();
23
24assert!(glob.is_match("foo.rs"));
25assert!(glob.is_match("foo/bar.rs"));
26assert!(!glob.is_match("Cargo.toml"));
27# Ok::<(), Box<dyn std::error::Error>>(())
28```
29
30# Example: configuring a glob matcher
31
32This example shows how to use a `GlobBuilder` to configure aspects of match
33semantics. In this example, we prevent wildcards from matching path separators.
34
35```
36use globset::GlobBuilder;
37
38let glob = GlobBuilder::new("*.rs")
39    .literal_separator(true).build()?.compile_matcher();
40
41assert!(glob.is_match("foo.rs"));
42assert!(!glob.is_match("foo/bar.rs")); // no longer matches
43assert!(!glob.is_match("Cargo.toml"));
44# Ok::<(), Box<dyn std::error::Error>>(())
45```
46
47# Example: match multiple globs at once
48
49This example shows how to match multiple glob patterns at once.
50
51```
52use globset::{Glob, GlobSetBuilder};
53
54let mut builder = GlobSetBuilder::new();
55// A GlobBuilder can be used to configure each glob's match semantics
56// independently.
57builder.add(Glob::new("*.rs")?);
58builder.add(Glob::new("src/lib.rs")?);
59builder.add(Glob::new("src/**/foo.rs")?);
60let set = builder.build()?;
61
62assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
63# Ok::<(), Box<dyn std::error::Error>>(())
64```
65
66# Syntax
67
68Standard Unix-style glob syntax is supported:
69
70* `?` matches any single character. (If the `literal_separator` option is
71  enabled, then `?` can never match a path separator.)
72* `*` matches zero or more characters. (If the `literal_separator` option is
73  enabled, then `*` can never match a path separator.)
74* `**` recursively matches directories but are only legal in three situations.
75  First, if the glob starts with <code>\*\*&#x2F;</code>, then it matches
76  all directories. For example, <code>\*\*&#x2F;foo</code> matches `foo`
77  and `bar/foo` but not `foo/bar`. Secondly, if the glob ends with
78  <code>&#x2F;\*\*</code>, then it matches all sub-entries. For example,
79  <code>foo&#x2F;\*\*</code> matches `foo/a` and `foo/a/b`, but not `foo`.
80  Thirdly, if the glob contains <code>&#x2F;\*\*&#x2F;</code> anywhere within
81  the pattern, then it matches zero or more directories. Using `**` anywhere
82  else is illegal (N.B. the glob `**` is allowed and means "match everything").
83* `{a,b}` matches `a` or `b` where `a` and `b` are arbitrary glob patterns.
84  (N.B. Nesting `{...}` is not currently allowed.)
85* `[ab]` matches `a` or `b` where `a` and `b` are characters. Use
86  `[!ab]` to match any character except for `a` and `b`.
87* Metacharacters such as `*` and `?` can be escaped with character class
88  notation. e.g., `[*]` matches `*`.
89* When backslash escapes are enabled, a backslash (`\`) will escape all meta
90  characters in a glob. If it precedes a non-meta character, then the slash is
91  ignored. A `\\` will match a literal `\\`. Note that this mode is only
92  enabled on Unix platforms by default, but can be enabled on any platform
93  via the `backslash_escape` setting on `Glob`.
94
95A `GlobBuilder` can be used to prevent wildcards from matching path separators,
96or to enable case insensitive matching.
97
98# Crate Features
99
100This crate includes optional features that can be enabled if necessary.
101These features are not required but may be useful depending on the use case.
102
103The following features are available:
104
105* **arbitrary** -
106  Enabling this feature introduces a public dependency on the
107  [`arbitrary`](https://crates.io/crates/arbitrary)
108  crate. Namely, it implements the `Arbitrary` trait from that crate for the
109  [`Glob`] type. This feature is disabled by default.
110*/
111
112#![deny(missing_docs)]
113
114use std::{
115    borrow::Cow,
116    panic::{RefUnwindSafe, UnwindSafe},
117    path::Path,
118    sync::Arc,
119};
120
121use {
122    aho_corasick::AhoCorasick,
123    bstr::{B, ByteSlice, ByteVec},
124    regex_automata::{
125        PatternSet,
126        meta::Regex,
127        util::pool::{Pool, PoolGuard},
128    },
129};
130
131use crate::{
132    glob::MatchStrategy,
133    pathutil::{file_name, file_name_ext, normalize_path},
134};
135
136pub use crate::glob::{Glob, GlobBuilder, GlobMatcher};
137
138mod fnv;
139mod glob;
140mod pathutil;
141
142#[cfg(feature = "serde1")]
143mod serde_impl;
144
145#[cfg(feature = "log")]
146macro_rules! debug {
147    ($($token:tt)*) => (::log::debug!($($token)*);)
148}
149
150#[cfg(not(feature = "log"))]
151macro_rules! debug {
152    ($($token:tt)*) => {};
153}
154
155/// Represents an error that can occur when parsing a glob pattern.
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct Error {
158    /// The original glob provided by the caller.
159    glob: Option<String>,
160    /// The kind of error.
161    kind: ErrorKind,
162}
163
164/// The kind of error that can occur when parsing a glob pattern.
165#[derive(Clone, Debug, Eq, PartialEq)]
166#[non_exhaustive]
167pub enum ErrorKind {
168    /// **DEPRECATED**.
169    ///
170    /// This error used to occur for consistency with git's glob specification,
171    /// but the specification now accepts all uses of `**`. When `**` does not
172    /// appear adjacent to a path separator or at the beginning/end of a glob,
173    /// it is now treated as two consecutive `*` patterns. As such, this error
174    /// is no longer used.
175    InvalidRecursive,
176    /// Occurs when a character class (e.g., `[abc]`) is not closed.
177    UnclosedClass,
178    /// Occurs when a range in a character (e.g., `[a-z]`) is invalid. For
179    /// example, if the range starts with a lexicographically larger character
180    /// than it ends with.
181    InvalidRange(char, char),
182    /// Occurs when a `}` is found without a matching `{`.
183    UnopenedAlternates,
184    /// Occurs when a `{` is found without a matching `}`.
185    UnclosedAlternates,
186    /// **DEPRECATED**.
187    ///
188    /// This error used to occur when an alternating group was nested inside
189    /// another alternating group, e.g., `{{a,b},{c,d}}`. However, this is now
190    /// supported and as such this error cannot occur.
191    NestedAlternates,
192    /// Occurs when an unescaped '\' is found at the end of a glob.
193    DanglingEscape,
194    /// An error associated with parsing or compiling a regex.
195    Regex(String),
196}
197
198impl std::error::Error for Error {
199    fn description(&self) -> &str {
200        self.kind.description()
201    }
202}
203
204impl Error {
205    /// Return the glob that caused this error, if one exists.
206    pub fn glob(&self) -> Option<&str> {
207        self.glob.as_ref().map(|s| &**s)
208    }
209
210    /// Return the kind of this error.
211    pub fn kind(&self) -> &ErrorKind {
212        &self.kind
213    }
214}
215
216impl ErrorKind {
217    fn description(&self) -> &str {
218        match *self {
219            ErrorKind::InvalidRecursive => {
220                "invalid use of **; must be one path component"
221            }
222            ErrorKind::UnclosedClass => {
223                "unclosed character class; missing ']'"
224            }
225            ErrorKind::InvalidRange(_, _) => "invalid character range",
226            ErrorKind::UnopenedAlternates => {
227                "unopened alternate group; missing '{' \
228                (maybe escape '}' with '[}]'?)"
229            }
230            ErrorKind::UnclosedAlternates => {
231                "unclosed alternate group; missing '}' \
232                (maybe escape '{' with '[{]'?)"
233            }
234            ErrorKind::NestedAlternates => {
235                "nested alternate groups are not allowed"
236            }
237            ErrorKind::DanglingEscape => "dangling '\\'",
238            ErrorKind::Regex(ref err) => err,
239        }
240    }
241}
242
243impl std::fmt::Display for Error {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self.glob {
246            None => self.kind.fmt(f),
247            Some(ref glob) => {
248                write!(f, "error parsing glob '{}': {}", glob, self.kind)
249            }
250        }
251    }
252}
253
254impl std::fmt::Display for ErrorKind {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match *self {
257            ErrorKind::InvalidRecursive
258            | ErrorKind::UnclosedClass
259            | ErrorKind::UnopenedAlternates
260            | ErrorKind::UnclosedAlternates
261            | ErrorKind::NestedAlternates
262            | ErrorKind::DanglingEscape
263            | ErrorKind::Regex(_) => write!(f, "{}", self.description()),
264            ErrorKind::InvalidRange(s, e) => {
265                write!(f, "invalid range; '{}' > '{}'", s, e)
266            }
267        }
268    }
269}
270
271fn new_regex(pat: &str) -> Result<Regex, Error> {
272    let syntax = regex_automata::util::syntax::Config::new()
273        .utf8(false)
274        .dot_matches_new_line(true);
275    let config = Regex::config()
276        .utf8_empty(false)
277        .nfa_size_limit(Some(10 * (1 << 20)))
278        .hybrid_cache_capacity(10 * (1 << 20));
279    Regex::builder().syntax(syntax).configure(config).build(pat).map_err(
280        |err| Error {
281            glob: Some(pat.to_string()),
282            kind: ErrorKind::Regex(err.to_string()),
283        },
284    )
285}
286
287fn new_regex_set(pats: Vec<String>) -> Result<Regex, Error> {
288    let syntax = regex_automata::util::syntax::Config::new()
289        .utf8(false)
290        .dot_matches_new_line(true);
291    let config = Regex::config()
292        .match_kind(regex_automata::MatchKind::All)
293        .utf8_empty(false)
294        .nfa_size_limit(Some(10 * (1 << 20)))
295        .hybrid_cache_capacity(10 * (1 << 20));
296    Regex::builder()
297        .syntax(syntax)
298        .configure(config)
299        .build_many(&pats)
300        .map_err(|err| Error {
301            glob: None,
302            kind: ErrorKind::Regex(err.to_string()),
303        })
304}
305
306/// GlobSet represents a group of globs that can be matched together in a
307/// single pass.
308#[derive(Clone, Debug)]
309pub struct GlobSet {
310    len: usize,
311    strats: Vec<GlobSetMatchStrategy>,
312}
313
314impl GlobSet {
315    /// Create a new [`GlobSetBuilder`]. A `GlobSetBuilder` can be used to add
316    /// new patterns. Once all patterns have been added, `build` should be
317    /// called to produce a `GlobSet`, which can then be used for matching.
318    #[inline]
319    pub fn builder() -> GlobSetBuilder {
320        GlobSetBuilder::new()
321    }
322
323    /// Create an empty `GlobSet`. An empty set matches nothing.
324    #[inline]
325    pub const fn empty() -> GlobSet {
326        GlobSet { len: 0, strats: vec![] }
327    }
328
329    /// Returns true if this set is empty, and therefore matches nothing.
330    #[inline]
331    pub fn is_empty(&self) -> bool {
332        self.len == 0
333    }
334
335    /// Returns the number of globs in this set.
336    #[inline]
337    pub fn len(&self) -> usize {
338        self.len
339    }
340
341    /// Returns true if any glob in this set matches the path given.
342    pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
343        self.is_match_candidate(&Candidate::new(path.as_ref()))
344    }
345
346    /// Returns true if any glob in this set matches the path given.
347    ///
348    /// This takes a Candidate as input, which can be used to amortize the
349    /// cost of preparing a path for matching.
350    pub fn is_match_candidate(&self, path: &Candidate<'_>) -> bool {
351        if self.is_empty() {
352            return false;
353        }
354        for strat in &self.strats {
355            if strat.is_match(path) {
356                return true;
357            }
358        }
359        false
360    }
361
362    /// Returns true if all globs in this set match the path given.
363    ///
364    /// This will return true if the set of globs is empty, as in that case all
365    /// `0` of the globs will match.
366    ///
367    /// ```
368    /// use globset::{Glob, GlobSetBuilder};
369    ///
370    /// let mut builder = GlobSetBuilder::new();
371    /// builder.add(Glob::new("src/*").unwrap());
372    /// builder.add(Glob::new("**/*.rs").unwrap());
373    /// let set = builder.build().unwrap();
374    ///
375    /// assert!(set.matches_all("src/foo.rs"));
376    /// assert!(!set.matches_all("src/bar.c"));
377    /// assert!(!set.matches_all("test.rs"));
378    /// ```
379    pub fn matches_all<P: AsRef<Path>>(&self, path: P) -> bool {
380        self.matches_all_candidate(&Candidate::new(path.as_ref()))
381    }
382
383    /// Returns true if all globs in this set match the path given.
384    ///
385    /// This takes a Candidate as input, which can be used to amortize the cost
386    /// of preparing a path for matching.
387    ///
388    /// This will return true if the set of globs is empty, as in that case all
389    /// `0` of the globs will match.
390    pub fn matches_all_candidate(&self, path: &Candidate<'_>) -> bool {
391        for strat in &self.strats {
392            if !strat.matches_all(path) {
393                return false;
394            }
395        }
396        true
397    }
398
399    /// Returns the sequence number of every glob pattern that matches the
400    /// given path.
401    pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
402        self.matches_candidate(&Candidate::new(path.as_ref()))
403    }
404
405    /// Returns the sequence number of every glob pattern that matches the
406    /// given path.
407    ///
408    /// This takes a Candidate as input, which can be used to amortize the
409    /// cost of preparing a path for matching.
410    pub fn matches_candidate(&self, path: &Candidate<'_>) -> Vec<usize> {
411        let mut into = vec![];
412        if self.is_empty() {
413            return into;
414        }
415        self.matches_candidate_into(path, &mut into);
416        into
417    }
418
419    /// Adds the sequence number of every glob pattern that matches the given
420    /// path to the vec given.
421    ///
422    /// `into` is cleared before matching begins, and contains the set of
423    /// sequence numbers (in ascending order) after matching ends. If no globs
424    /// were matched, then `into` will be empty.
425    pub fn matches_into<P: AsRef<Path>>(
426        &self,
427        path: P,
428        into: &mut Vec<usize>,
429    ) {
430        self.matches_candidate_into(&Candidate::new(path.as_ref()), into);
431    }
432
433    /// Adds the sequence number of every glob pattern that matches the given
434    /// path to the vec given.
435    ///
436    /// `into` is cleared before matching begins, and contains the set of
437    /// sequence numbers (in ascending order) after matching ends. If no globs
438    /// were matched, then `into` will be empty.
439    ///
440    /// This takes a Candidate as input, which can be used to amortize the
441    /// cost of preparing a path for matching.
442    pub fn matches_candidate_into(
443        &self,
444        path: &Candidate<'_>,
445        into: &mut Vec<usize>,
446    ) {
447        into.clear();
448        if self.is_empty() {
449            return;
450        }
451        for strat in &self.strats {
452            strat.matches_into(path, into);
453        }
454        into.sort();
455        into.dedup();
456    }
457
458    /// Builds a new matcher from a collection of Glob patterns.
459    ///
460    /// Once a matcher is built, no new patterns can be added to it.
461    pub fn new<I, G>(globs: I) -> Result<GlobSet, Error>
462    where
463        I: IntoIterator<Item = G>,
464        G: AsRef<Glob>,
465    {
466        let mut it = globs.into_iter().peekable();
467        if it.peek().is_none() {
468            return Ok(GlobSet::empty());
469        }
470
471        let mut len = 0;
472        let mut lits = LiteralStrategy::new();
473        let mut base_lits = BasenameLiteralStrategy::new();
474        let mut exts = ExtensionStrategy::new();
475        let mut prefixes = MultiStrategyBuilder::new();
476        let mut suffixes = MultiStrategyBuilder::new();
477        let mut required_exts = RequiredExtensionStrategyBuilder::new();
478        let mut regexes = MultiStrategyBuilder::new();
479        for (i, p) in it.enumerate() {
480            len += 1;
481
482            let p = p.as_ref();
483            match MatchStrategy::new(p) {
484                MatchStrategy::Literal(lit) => {
485                    lits.add(i, lit);
486                }
487                MatchStrategy::BasenameLiteral(lit) => {
488                    base_lits.add(i, lit);
489                }
490                MatchStrategy::Extension(ext) => {
491                    exts.add(i, ext);
492                }
493                MatchStrategy::Prefix(prefix) => {
494                    prefixes.add(i, prefix);
495                }
496                MatchStrategy::Suffix { suffix, component } => {
497                    if component {
498                        lits.add(i, suffix[1..].to_string());
499                    }
500                    suffixes.add(i, suffix);
501                }
502                MatchStrategy::RequiredExtension(ext) => {
503                    required_exts.add(i, ext, p.regex().to_owned());
504                }
505                MatchStrategy::Regex => {
506                    debug!(
507                        "glob `{:?}` converted to regex: `{:?}`",
508                        p,
509                        p.regex()
510                    );
511                    regexes.add(i, p.regex().to_owned());
512                }
513            }
514        }
515        debug!(
516            "built glob set; {} literals, {} basenames, {} extensions, \
517                {} prefixes, {} suffixes, {} required extensions, {} regexes",
518            lits.0.len(),
519            base_lits.0.len(),
520            exts.0.len(),
521            prefixes.literals.len(),
522            suffixes.literals.len(),
523            required_exts.0.len(),
524            regexes.literals.len()
525        );
526        let mut strats = Vec::with_capacity(7);
527        // Only add strategies that are populated
528        if !exts.0.is_empty() {
529            strats.push(GlobSetMatchStrategy::Extension(exts));
530        }
531        if !base_lits.0.is_empty() {
532            strats.push(GlobSetMatchStrategy::BasenameLiteral(base_lits));
533        }
534        if !lits.0.is_empty() {
535            strats.push(GlobSetMatchStrategy::Literal(lits));
536        }
537        if !suffixes.is_empty() {
538            strats.push(GlobSetMatchStrategy::Suffix(suffixes.suffix()));
539        }
540        if !prefixes.is_empty() {
541            strats.push(GlobSetMatchStrategy::Prefix(prefixes.prefix()));
542        }
543        if !required_exts.0.is_empty() {
544            strats.push(GlobSetMatchStrategy::RequiredExtension(
545                required_exts.build()?,
546            ));
547        }
548        if !regexes.is_empty() {
549            strats.push(GlobSetMatchStrategy::Regex(regexes.regex_set()?));
550        }
551
552        Ok(GlobSet { len, strats })
553    }
554}
555
556impl Default for GlobSet {
557    /// Create a default empty GlobSet.
558    fn default() -> Self {
559        GlobSet::empty()
560    }
561}
562
563/// GlobSetBuilder builds a group of patterns that can be used to
564/// simultaneously match a file path.
565#[derive(Clone, Debug)]
566pub struct GlobSetBuilder {
567    pats: Vec<Glob>,
568}
569
570impl GlobSetBuilder {
571    /// Create a new `GlobSetBuilder`. A `GlobSetBuilder` can be used to add new
572    /// patterns. Once all patterns have been added, `build` should be called
573    /// to produce a [`GlobSet`], which can then be used for matching.
574    pub fn new() -> GlobSetBuilder {
575        GlobSetBuilder { pats: vec![] }
576    }
577
578    /// Builds a new matcher from all of the glob patterns added so far.
579    ///
580    /// Once a matcher is built, no new patterns can be added to it.
581    pub fn build(&self) -> Result<GlobSet, Error> {
582        GlobSet::new(self.pats.iter())
583    }
584
585    /// Add a new pattern to this set.
586    pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
587        self.pats.push(pat);
588        self
589    }
590}
591
592/// A candidate path for matching.
593///
594/// All glob matching in this crate operates on `Candidate` values.
595/// Constructing candidates has a very small cost associated with it, so
596/// callers may find it beneficial to amortize that cost when matching a single
597/// path against multiple globs or sets of globs.
598#[derive(Clone)]
599pub struct Candidate<'a> {
600    path: Cow<'a, [u8]>,
601    basename: Cow<'a, [u8]>,
602    ext: Cow<'a, [u8]>,
603}
604
605impl<'a> std::fmt::Debug for Candidate<'a> {
606    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
607        f.debug_struct("Candidate")
608            .field("path", &self.path.as_bstr())
609            .field("basename", &self.basename.as_bstr())
610            .field("ext", &self.ext.as_bstr())
611            .finish()
612    }
613}
614
615impl<'a> Candidate<'a> {
616    /// Create a new candidate for matching from the given path.
617    pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
618        Self::from_cow(Vec::from_path_lossy(path.as_ref()))
619    }
620
621    /// Create a new candidate for matching from the given path as a sequence
622    /// of bytes.
623    ///
624    /// Generally speaking, this routine expects the bytes to be
625    /// _conventionally_ UTF-8. It is legal for the byte sequence to contain
626    /// invalid UTF-8. However, if the bytes are in some other encoding that
627    /// isn't ASCII compatible (for example, UTF-16), then the results of
628    /// matching are unspecified.
629    pub fn from_bytes<P: AsRef<[u8]> + ?Sized>(path: &'a P) -> Candidate<'a> {
630        Self::from_cow(Cow::Borrowed(path.as_ref()))
631    }
632
633    fn from_cow(path: Cow<'a, [u8]>) -> Candidate<'a> {
634        let path = normalize_path(path);
635        let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
636        let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
637        Candidate { path, basename, ext }
638    }
639
640    fn path_prefix(&self, max: usize) -> &[u8] {
641        if self.path.len() <= max { &*self.path } else { &self.path[..max] }
642    }
643
644    fn path_suffix(&self, max: usize) -> &[u8] {
645        if self.path.len() <= max {
646            &*self.path
647        } else {
648            &self.path[self.path.len() - max..]
649        }
650    }
651}
652
653#[derive(Clone, Debug)]
654enum GlobSetMatchStrategy {
655    Literal(LiteralStrategy),
656    BasenameLiteral(BasenameLiteralStrategy),
657    Extension(ExtensionStrategy),
658    Prefix(PrefixStrategy),
659    Suffix(SuffixStrategy),
660    RequiredExtension(RequiredExtensionStrategy),
661    Regex(RegexSetStrategy),
662}
663
664impl GlobSetMatchStrategy {
665    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
666        use self::GlobSetMatchStrategy::*;
667        match *self {
668            Literal(ref s) => s.is_match(candidate),
669            BasenameLiteral(ref s) => s.is_match(candidate),
670            Extension(ref s) => s.is_match(candidate),
671            Prefix(ref s) => s.is_match(candidate),
672            Suffix(ref s) => s.is_match(candidate),
673            RequiredExtension(ref s) => s.is_match(candidate),
674            Regex(ref s) => s.is_match(candidate),
675        }
676    }
677
678    fn matches_into(
679        &self,
680        candidate: &Candidate<'_>,
681        matches: &mut Vec<usize>,
682    ) {
683        use self::GlobSetMatchStrategy::*;
684        match *self {
685            Literal(ref s) => s.matches_into(candidate, matches),
686            BasenameLiteral(ref s) => s.matches_into(candidate, matches),
687            Extension(ref s) => s.matches_into(candidate, matches),
688            Prefix(ref s) => s.matches_into(candidate, matches),
689            Suffix(ref s) => s.matches_into(candidate, matches),
690            RequiredExtension(ref s) => s.matches_into(candidate, matches),
691            Regex(ref s) => s.matches_into(candidate, matches),
692        }
693    }
694
695    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
696        use self::GlobSetMatchStrategy::*;
697        match *self {
698            Literal(ref s) => s.matches_all(candidate),
699            BasenameLiteral(ref s) => s.matches_all(candidate),
700            Extension(ref s) => s.matches_all(candidate),
701            Prefix(ref s) => s.matches_all(candidate),
702            Suffix(ref s) => s.matches_all(candidate),
703            RequiredExtension(ref s) => s.matches_all(candidate),
704            Regex(ref s) => s.matches_all(candidate),
705        }
706    }
707}
708
709#[derive(Clone, Debug)]
710struct LiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
711
712impl LiteralStrategy {
713    fn new() -> LiteralStrategy {
714        LiteralStrategy(fnv::HashMap::default())
715    }
716
717    fn add(&mut self, global_index: usize, lit: String) {
718        self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
719    }
720
721    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
722        self.0.contains_key(candidate.path.as_bytes())
723    }
724
725    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
726        self.0.len() == 1 && self.is_match(candidate)
727    }
728
729    #[inline(never)]
730    fn matches_into(
731        &self,
732        candidate: &Candidate<'_>,
733        matches: &mut Vec<usize>,
734    ) {
735        if let Some(hits) = self.0.get(candidate.path.as_bytes()) {
736            matches.extend(hits);
737        }
738    }
739}
740
741#[derive(Clone, Debug)]
742struct BasenameLiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
743
744impl BasenameLiteralStrategy {
745    fn new() -> BasenameLiteralStrategy {
746        BasenameLiteralStrategy(fnv::HashMap::default())
747    }
748
749    fn add(&mut self, global_index: usize, lit: String) {
750        self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
751    }
752
753    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
754        if candidate.basename.is_empty() {
755            return false;
756        }
757        self.0.contains_key(candidate.basename.as_bytes())
758    }
759
760    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
761        self.0.len() == 1 && self.is_match(candidate)
762    }
763
764    #[inline(never)]
765    fn matches_into(
766        &self,
767        candidate: &Candidate<'_>,
768        matches: &mut Vec<usize>,
769    ) {
770        if candidate.basename.is_empty() {
771            return;
772        }
773        if let Some(hits) = self.0.get(candidate.basename.as_bytes()) {
774            matches.extend(hits);
775        }
776    }
777}
778
779#[derive(Clone, Debug)]
780struct ExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
781
782impl ExtensionStrategy {
783    fn new() -> ExtensionStrategy {
784        ExtensionStrategy(fnv::HashMap::default())
785    }
786
787    fn add(&mut self, global_index: usize, ext: String) {
788        self.0.entry(ext.into_bytes()).or_insert(vec![]).push(global_index);
789    }
790
791    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
792        if candidate.ext.is_empty() {
793            return false;
794        }
795        self.0.contains_key(candidate.ext.as_bytes())
796    }
797
798    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
799        self.0.len() == 1 && self.is_match(candidate)
800    }
801
802    #[inline(never)]
803    fn matches_into(
804        &self,
805        candidate: &Candidate<'_>,
806        matches: &mut Vec<usize>,
807    ) {
808        if candidate.ext.is_empty() {
809            return;
810        }
811        if let Some(hits) = self.0.get(candidate.ext.as_bytes()) {
812            matches.extend(hits);
813        }
814    }
815}
816
817#[derive(Clone, Debug)]
818struct PrefixStrategy {
819    matcher: AhoCorasick,
820    map: Vec<usize>,
821    longest: usize,
822}
823
824impl PrefixStrategy {
825    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
826        let path = candidate.path_prefix(self.longest);
827        for m in self.matcher.find_overlapping_iter(path) {
828            if m.start() == 0 {
829                return true;
830            }
831        }
832        false
833    }
834
835    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
836        let path = candidate.path_prefix(self.longest);
837        let mut count = 0;
838        // If all the prefixes match, we should get exactly one match for each
839        // prefix at the beginning.
840        for m in self.matcher.find_overlapping_iter(path) {
841            if m.start() == 0 {
842                count += 1;
843            }
844        }
845        count == self.map.len()
846    }
847
848    fn matches_into(
849        &self,
850        candidate: &Candidate<'_>,
851        matches: &mut Vec<usize>,
852    ) {
853        let path = candidate.path_prefix(self.longest);
854        for m in self.matcher.find_overlapping_iter(path) {
855            if m.start() == 0 {
856                matches.push(self.map[m.pattern()]);
857            }
858        }
859    }
860}
861
862#[derive(Clone, Debug)]
863struct SuffixStrategy {
864    matcher: AhoCorasick,
865    map: Vec<usize>,
866    longest: usize,
867}
868
869impl SuffixStrategy {
870    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
871        let path = candidate.path_suffix(self.longest);
872        for m in self.matcher.find_overlapping_iter(path) {
873            if m.end() == path.len() {
874                return true;
875            }
876        }
877        false
878    }
879
880    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
881        let path = candidate.path_suffix(self.longest);
882        let mut count = 0;
883        // If all the suffixes match, we should get exactly one match
884        // for each suffix at the end.
885        for m in self.matcher.find_overlapping_iter(path) {
886            if m.end() == path.len() {
887                count += 1;
888            }
889        }
890        count == self.map.len()
891    }
892
893    fn matches_into(
894        &self,
895        candidate: &Candidate<'_>,
896        matches: &mut Vec<usize>,
897    ) {
898        let path = candidate.path_suffix(self.longest);
899        for m in self.matcher.find_overlapping_iter(path) {
900            if m.end() == path.len() {
901                matches.push(self.map[m.pattern()]);
902            }
903        }
904    }
905}
906
907#[derive(Clone, Debug)]
908struct RequiredExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<(usize, Regex)>>);
909
910impl RequiredExtensionStrategy {
911    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
912        if candidate.ext.is_empty() {
913            return false;
914        }
915        match self.0.get(candidate.ext.as_bytes()) {
916            None => false,
917            Some(regexes) => {
918                for &(_, ref re) in regexes {
919                    if re.is_match(candidate.path.as_bytes()) {
920                        return true;
921                    }
922                }
923                false
924            }
925        }
926    }
927
928    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
929        if candidate.ext.is_empty() {
930            return false;
931        }
932        if let Some(regexes) = self.0.get(candidate.ext.as_bytes()) {
933            for &(_, ref re) in regexes {
934                if !re.is_match(candidate.path.as_bytes()) {
935                    return false;
936                }
937            }
938            true
939        } else {
940            false
941        }
942    }
943
944    #[inline(never)]
945    fn matches_into(
946        &self,
947        candidate: &Candidate<'_>,
948        matches: &mut Vec<usize>,
949    ) {
950        if candidate.ext.is_empty() {
951            return;
952        }
953        if let Some(regexes) = self.0.get(candidate.ext.as_bytes()) {
954            for &(global_index, ref re) in regexes {
955                if re.is_match(candidate.path.as_bytes()) {
956                    matches.push(global_index);
957                }
958            }
959        }
960    }
961}
962
963#[derive(Clone, Debug)]
964struct RegexSetStrategy {
965    matcher: Regex,
966    map: Vec<usize>,
967    // We use a pool of PatternSets to hopefully allocating a fresh one on each
968    // call.
969    //
970    // TODO: In the next semver breaking release, we should drop this pool and
971    // expose an opaque type that wraps PatternSet. Then callers can provide
972    // it to `matches_into` directly. Callers might still want to use a pool
973    // or similar to amortize allocation, but that matches the status quo and
974    // absolves us of needing to do it here.
975    patset: Arc<Pool<PatternSet, PatternSetPoolFn>>,
976}
977
978type PatternSetPoolFn =
979    Box<dyn Fn() -> PatternSet + Send + Sync + UnwindSafe + RefUnwindSafe>;
980
981impl RegexSetStrategy {
982    fn is_match(&self, candidate: &Candidate<'_>) -> bool {
983        self.matcher.is_match(candidate.path.as_bytes())
984    }
985
986    fn find_matches(
987        &self,
988        candidate: &Candidate<'_>,
989    ) -> PoolGuard<'_, PatternSet, PatternSetPoolFn> {
990        let input = regex_automata::Input::new(candidate.path.as_bytes());
991        let mut patset = self.patset.get();
992        patset.clear();
993        self.matcher.which_overlapping_matches(&input, &mut patset);
994        patset
995    }
996
997    fn matches_all(&self, candidate: &Candidate<'_>) -> bool {
998        let patset = self.find_matches(candidate);
999        patset.is_full()
1000    }
1001
1002    fn matches_into(
1003        &self,
1004        candidate: &Candidate<'_>,
1005        matches: &mut Vec<usize>,
1006    ) {
1007        let patset = self.find_matches(candidate);
1008        for i in patset.iter() {
1009            matches.push(self.map[i]);
1010        }
1011        PoolGuard::put(patset);
1012    }
1013}
1014
1015#[derive(Clone, Debug)]
1016struct MultiStrategyBuilder {
1017    literals: Vec<String>,
1018    map: Vec<usize>,
1019    longest: usize,
1020}
1021
1022impl MultiStrategyBuilder {
1023    fn new() -> MultiStrategyBuilder {
1024        MultiStrategyBuilder { literals: vec![], map: vec![], longest: 0 }
1025    }
1026
1027    fn add(&mut self, global_index: usize, literal: String) {
1028        if literal.len() > self.longest {
1029            self.longest = literal.len();
1030        }
1031        self.map.push(global_index);
1032        self.literals.push(literal);
1033    }
1034
1035    fn prefix(self) -> PrefixStrategy {
1036        PrefixStrategy {
1037            matcher: AhoCorasick::new(&self.literals).unwrap(),
1038            map: self.map,
1039            longest: self.longest,
1040        }
1041    }
1042
1043    fn suffix(self) -> SuffixStrategy {
1044        SuffixStrategy {
1045            matcher: AhoCorasick::new(&self.literals).unwrap(),
1046            map: self.map,
1047            longest: self.longest,
1048        }
1049    }
1050
1051    fn regex_set(self) -> Result<RegexSetStrategy, Error> {
1052        let matcher = new_regex_set(self.literals)?;
1053        let pattern_len = matcher.pattern_len();
1054        let create: PatternSetPoolFn =
1055            Box::new(move || PatternSet::new(pattern_len));
1056        Ok(RegexSetStrategy {
1057            matcher,
1058            map: self.map,
1059            patset: Arc::new(Pool::new(create)),
1060        })
1061    }
1062
1063    fn is_empty(&self) -> bool {
1064        self.literals.is_empty()
1065    }
1066}
1067
1068#[derive(Clone, Debug)]
1069struct RequiredExtensionStrategyBuilder(
1070    fnv::HashMap<Vec<u8>, Vec<(usize, String)>>,
1071);
1072
1073impl RequiredExtensionStrategyBuilder {
1074    fn new() -> RequiredExtensionStrategyBuilder {
1075        RequiredExtensionStrategyBuilder(fnv::HashMap::default())
1076    }
1077
1078    fn add(&mut self, global_index: usize, ext: String, regex: String) {
1079        self.0
1080            .entry(ext.into_bytes())
1081            .or_insert(vec![])
1082            .push((global_index, regex));
1083    }
1084
1085    fn build(self) -> Result<RequiredExtensionStrategy, Error> {
1086        let mut exts = fnv::HashMap::default();
1087        for (ext, regexes) in self.0.into_iter() {
1088            exts.insert(ext.clone(), vec![]);
1089            for (global_index, regex) in regexes {
1090                let compiled = new_regex(&regex)?;
1091                exts.get_mut(&ext).unwrap().push((global_index, compiled));
1092            }
1093        }
1094        Ok(RequiredExtensionStrategy(exts))
1095    }
1096}
1097
1098/// Escape meta-characters within the given glob pattern.
1099///
1100/// The escaping works by surrounding meta-characters with brackets. For
1101/// example, `*` becomes `[*]`.
1102///
1103/// # Example
1104///
1105/// ```
1106/// use globset::escape;
1107///
1108/// assert_eq!(escape("foo*bar"), "foo[*]bar");
1109/// assert_eq!(escape("foo?bar"), "foo[?]bar");
1110/// assert_eq!(escape("foo[bar"), "foo[[]bar");
1111/// assert_eq!(escape("foo]bar"), "foo[]]bar");
1112/// assert_eq!(escape("foo{bar"), "foo[{]bar");
1113/// assert_eq!(escape("foo}bar"), "foo[}]bar");
1114/// ```
1115pub fn escape(s: &str) -> String {
1116    let mut escaped = String::with_capacity(s.len());
1117    for c in s.chars() {
1118        match c {
1119            // note that ! does not need escaping because it is only special
1120            // inside brackets
1121            '?' | '*' | '[' | ']' | '{' | '}' => {
1122                escaped.push('[');
1123                escaped.push(c);
1124                escaped.push(']');
1125            }
1126            c => {
1127                escaped.push(c);
1128            }
1129        }
1130    }
1131    escaped
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use crate::glob::{Glob, GlobBuilder};
1137
1138    use super::{GlobSet, GlobSetBuilder};
1139
1140    fn build_glob_set(globs: &[&str]) -> GlobSet {
1141        let mut builder = GlobSetBuilder::new();
1142        for glob in globs {
1143            builder.add(Glob::new(glob).unwrap());
1144        }
1145        builder.build().unwrap()
1146    }
1147
1148    #[test]
1149    fn set_works() {
1150        let set = build_glob_set(&["src/**/*.rs", "*.c", "src/lib.rs"]);
1151
1152        assert!(set.is_match("foo.c"));
1153        assert!(set.is_match("src/foo.c"));
1154        assert!(!set.is_match("foo.rs"));
1155        assert!(!set.is_match("tests/foo.rs"));
1156        assert!(set.is_match("src/foo.rs"));
1157        assert!(set.is_match("src/grep/src/main.rs"));
1158
1159        assert!(!set.matches_all("src/lib.rs"));
1160        assert!(!set.matches_all("src/a/b.c"));
1161
1162        let matches = set.matches("src/lib.rs");
1163        assert_eq!(2, matches.len());
1164        assert_eq!(0, matches[0]);
1165        assert_eq!(2, matches[1]);
1166    }
1167
1168    #[test]
1169    fn empty_set_works() {
1170        let set = GlobSetBuilder::new().build().unwrap();
1171        assert!(!set.is_match(""));
1172        assert!(!set.is_match("a"));
1173        assert!(set.matches_all("a"));
1174    }
1175
1176    #[test]
1177    fn matches_all_literals() {
1178        let set = build_glob_set(&["abc", "def"]);
1179
1180        assert!(!set.matches_all("abc"));
1181        assert!(!set.matches_all("def"));
1182
1183        let set = build_glob_set(&["abc"]);
1184
1185        assert!(set.matches_all("abc"));
1186    }
1187
1188    #[test]
1189    fn matches_all_basename_literals() {
1190        let set = build_glob_set(&["**/abc", "**/a.c"]);
1191
1192        assert!(!set.matches_all("foo/abc"));
1193        assert!(!set.matches_all("foo/a.c"));
1194
1195        let set = build_glob_set(&["**/a.c"]);
1196        assert!(set.matches_all("foo/a.c"));
1197    }
1198
1199    #[test]
1200    fn matches_all_extensions() {
1201        let set = build_glob_set(&["**/*.rs", "**/*.c"]);
1202
1203        assert!(!set.matches_all("foo/a.rs"));
1204        assert!(!set.matches_all("foo/a.c"));
1205
1206        let set = build_glob_set(&["**/*.rs", "*.rs"]);
1207        assert!(set.matches_all("foo/a.rs"));
1208    }
1209
1210    #[test]
1211    fn matches_all_required_extensions() {
1212        let set = build_glob_set(&["*.rs", "**/m*.rs", "a/*.rs"]);
1213
1214        assert!(set.matches_all("a/main.rs"));
1215
1216        assert!(!set.matches_all("main.rs"));
1217        assert!(!set.matches_all("a/lib.rs"));
1218    }
1219
1220    #[test]
1221    fn matches_all_prefix() {
1222        let set = build_glob_set(&["a*", "ab*", "ab/c*"]);
1223
1224        assert!(set.matches_all("ab/c/def"));
1225        assert!(set.matches_all("ab/cd"));
1226
1227        assert!(!set.matches_all("abc"));
1228        assert!(!set.matches_all("ab/x"));
1229        assert!(!set.matches_all("a"));
1230    }
1231
1232    #[test]
1233    fn matches_all_suffix() {
1234        let set = build_glob_set(&["*.rs", "*s", "*/main.rs"]);
1235
1236        assert!(set.matches_all("a/b/c/main.rs"));
1237
1238        assert!(!set.matches_all("foo.rs"));
1239        assert!(!set.matches_all("as"));
1240    }
1241
1242    #[test]
1243    fn matches_all_regex() {
1244        let set = GlobSet::new(&[
1245            GlobBuilder::new("c*").case_insensitive(true).build().unwrap(),
1246            Glob::new("*{rs,c}").unwrap(),
1247        ])
1248        .unwrap();
1249
1250        assert!(set.matches_all("c/main.rs"));
1251        assert!(set.matches_all("C/main.c"));
1252
1253        assert!(!set.matches_all("Ca"));
1254        assert!(!set.matches_all("foo.c"));
1255    }
1256
1257    #[test]
1258    fn default_set_is_empty_works() {
1259        let set: GlobSet = Default::default();
1260        assert!(!set.is_match(""));
1261        assert!(!set.is_match("a"));
1262    }
1263
1264    #[test]
1265    fn escape() {
1266        use super::escape;
1267        assert_eq!("foo", escape("foo"));
1268        assert_eq!("foo[*]", escape("foo*"));
1269        assert_eq!("[[][]]", escape("[]"));
1270        assert_eq!("[*][?]", escape("*?"));
1271        assert_eq!("src/[*][*]/[*].rs", escape("src/**/*.rs"));
1272        assert_eq!("bar[[]ab[]]baz", escape("bar[ab]baz"));
1273        assert_eq!("bar[[]!![]]!baz", escape("bar[!!]!baz"));
1274    }
1275
1276    // This tests that regex matching doesn't "remember" the results of
1277    // previous searches. That is, if any memory is reused from a previous
1278    // search, then it should be cleared first.
1279    #[test]
1280    fn set_does_not_remember() {
1281        let mut builder = GlobSetBuilder::new();
1282        builder.add(Glob::new("*foo*").unwrap());
1283        builder.add(Glob::new("*bar*").unwrap());
1284        builder.add(Glob::new("*quux*").unwrap());
1285        let set = builder.build().unwrap();
1286
1287        let matches = set.matches("ZfooZquuxZ");
1288        assert_eq!(2, matches.len());
1289        assert_eq!(0, matches[0]);
1290        assert_eq!(2, matches[1]);
1291
1292        let matches = set.matches("nada");
1293        assert_eq!(0, matches.len());
1294    }
1295
1296    #[test]
1297    fn debug() {
1298        let mut builder = GlobSetBuilder::new();
1299        builder.add(Glob::new("*foo*").unwrap());
1300        builder.add(Glob::new("*bar*").unwrap());
1301        builder.add(Glob::new("*quux*").unwrap());
1302        assert_eq!(
1303            format!("{builder:?}"),
1304            "GlobSetBuilder { pats: [Glob(\"*foo*\"), Glob(\"*bar*\"), Glob(\"*quux*\")] }",
1305        );
1306    }
1307}