2 The globset crate provides cross platform single glob and glob set matching.
4 Glob set matching is the process of matching one or more glob patterns against
5 a single candidate path simultaneously, and returning all of the globs that
6 matched. For example, given this set of globs:
14 and a path `src/bar/baz/foo.rs`, then the set would report the first and third
19 This example shows how to match a single glob against a single file path.
22 # fn example() -> Result<(), globset::Error> {
25 let glob = try!(Glob::new("*.rs")).compile_matcher();
27 assert!(glob.is_match("foo.rs"));
28 assert!(glob.is_match("foo/bar.rs"));
29 assert!(!glob.is_match("Cargo.toml"));
30 # Ok(()) } example().unwrap();
33 # Example: configuring a glob matcher
35 This example shows how to use a `GlobBuilder` to configure aspects of match
36 semantics. In this example, we prevent wildcards from matching path separators.
39 # fn example() -> Result<(), globset::Error> {
40 use globset::GlobBuilder;
42 let glob = try!(GlobBuilder::new("*.rs")
43 .literal_separator(true).build()).compile_matcher();
45 assert!(glob.is_match("foo.rs"));
46 assert!(!glob.is_match("foo/bar.rs")); // no longer matches
47 assert!(!glob.is_match("Cargo.toml"));
48 # Ok(()) } example().unwrap();
51 # Example: match multiple globs at once
53 This example shows how to match multiple glob patterns at once.
56 # fn example() -> Result<(), globset::Error> {
57 use globset::{Glob, GlobSetBuilder};
59 let mut builder = GlobSetBuilder::new();
60 // A GlobBuilder can be used to configure each glob's match semantics
62 builder.add(try!(Glob::new("*.rs")));
63 builder.add(try!(Glob::new("src/lib.rs")));
64 builder.add(try!(Glob::new("src/**/foo.rs")));
65 let set = try!(builder.build());
67 assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
68 # Ok(()) } example().unwrap();
73 Standard Unix-style glob syntax is supported:
75 * `?` matches any single character. (If the `literal_separator` option is
76 enabled, then `?` can never match a path separator.)
77 * `*` matches zero or more characters. (If the `literal_separator` option is
78 enabled, then `*` can never match a path separator.)
79 * `**` recursively matches directories but are only legal in three situations.
80 First, if the glob starts with <code>\*\*/</code>, then it matches
81 all directories. For example, <code>\*\*/foo</code> matches `foo`
82 and `bar/foo` but not `foo/bar`. Secondly, if the glob ends with
83 <code>/\*\*</code>, then it matches all sub-entries. For example,
84 <code>foo/\*\*</code> matches `foo/a` and `foo/a/b`, but not `foo`.
85 Thirdly, if the glob contains <code>/\*\*/</code> anywhere within
86 the pattern, then it matches zero or more directories. Using `**` anywhere
87 else is illegal (N.B. the glob `**` is allowed and means "match everything").
88 * `{a,b}` matches `a` or `b` where `a` and `b` are arbitrary glob patterns.
89 (N.B. Nesting `{...}` is not currently allowed.)
90 * `[ab]` matches `a` or `b` where `a` and `b` are characters. Use
91 `[!ab]` to match any character except for `a` and `b`.
92 * Metacharacters such as `*` and `?` can be escaped with character class
93 notation. e.g., `[*]` matches `*`.
95 A `GlobBuilder` can be used to prevent wildcards from matching path separators,
96 or to enable case insensitive matching.
99 #![deny(missing_docs)]
101 extern crate aho_corasick
;
108 use std
::borrow
::Cow
;
109 use std
::collections
::{BTreeMap, HashMap}
;
110 use std
::error
::Error
as StdError
;
111 use std
::ffi
::{OsStr, OsString}
;
117 use aho_corasick
::{Automaton, AcAutomaton, FullAcAutomaton}
;
118 use regex
::bytes
::{Regex, RegexBuilder, RegexSet}
;
121 file_name
, file_name_ext
, normalize_path
, os_str_bytes
, path_bytes
,
123 use glob
::MatchStrategy
;
124 pub use glob
::{Glob, GlobBuilder, GlobMatcher}
;
129 /// Represents an error that can occur when parsing a glob pattern.
130 #[derive(Clone, Debug, Eq, PartialEq)]
132 /// The original glob provided by the caller.
133 glob
: Option
<String
>,
134 /// The kind of error.
138 /// The kind of error that can occur when parsing a glob pattern.
139 #[derive(Clone, Debug, Eq, PartialEq)]
141 /// Occurs when a use of `**` is invalid. Namely, `**` can only appear
142 /// adjacent to a path separator, or the beginning/end of a glob.
144 /// Occurs when a character class (e.g., `[abc]`) is not closed.
146 /// Occurs when a range in a character (e.g., `[a-z]`) is invalid. For
147 /// example, if the range starts with a lexicographically larger character
148 /// than it ends with.
149 InvalidRange(char, char),
150 /// Occurs when a `}` is found without a matching `{`.
152 /// Occurs when a `{` is found without a matching `}`.
154 /// Occurs when an alternating group is nested inside another alternating
155 /// group, e.g., `{{a,b},{c,d}}`.
157 /// An error associated with parsing or compiling a regex.
161 impl StdError
for Error
{
162 fn description(&self) -> &str {
163 self.kind
.description()
168 /// Return the glob that caused this error, if one exists.
169 pub fn glob(&self) -> Option
<&str> {
170 self.glob
.as_ref().map(|s
| &**s
)
173 /// Return the kind of this error.
174 pub fn kind(&self) -> &ErrorKind
{
180 fn description(&self) -> &str {
182 ErrorKind
::InvalidRecursive
=> {
183 "invalid use of **; must be one path component"
185 ErrorKind
::UnclosedClass
=> {
186 "unclosed character class; missing ']'"
188 ErrorKind
::InvalidRange(_
, _
) => {
189 "invalid character range"
191 ErrorKind
::UnopenedAlternates
=> {
192 "unopened alternate group; missing '{' \
193 (maybe escape '}' with '[}]'?)"
195 ErrorKind
::UnclosedAlternates
=> {
196 "unclosed alternate group; missing '}' \
197 (maybe escape '{' with '[{]'?)"
199 ErrorKind
::NestedAlternates
=> {
200 "nested alternate groups are not allowed"
202 ErrorKind
::Regex(ref err
) => err
,
207 impl fmt
::Display
for Error
{
208 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
210 None
=> self.kind
.fmt(f
),
212 write
!(f
, "error parsing glob '{}': {}", glob
, self.kind
)
218 impl fmt
::Display
for ErrorKind
{
219 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
221 ErrorKind
::InvalidRecursive
222 | ErrorKind
::UnclosedClass
223 | ErrorKind
::UnopenedAlternates
224 | ErrorKind
::UnclosedAlternates
225 | ErrorKind
::NestedAlternates
226 | ErrorKind
::Regex(_
) => {
227 write
!(f
, "{}", self.description())
229 ErrorKind
::InvalidRange(s
, e
) => {
230 write
!(f
, "invalid range; '{}' > '{}'", s
, e
)
236 fn new_regex(pat
: &str) -> Result
<Regex
, Error
> {
237 RegexBuilder
::new(pat
)
238 .dot_matches_new_line(true)
239 .size_limit(10 * (1 << 20))
240 .dfa_size_limit(10 * (1 << 20))
244 glob
: Some(pat
.to_string()),
245 kind
: ErrorKind
::Regex(err
.to_string()),
250 fn new_regex_set
<I
, S
>(pats
: I
) -> Result
<RegexSet
, Error
>
251 where S
: AsRef
<str>, I
: IntoIterator
<Item
=S
> {
252 RegexSet
::new(pats
).map_err(|err
| {
255 kind
: ErrorKind
::Regex(err
.to_string()),
260 type Fnv
= hash
::BuildHasherDefault
<fnv
::FnvHasher
>;
262 /// GlobSet represents a group of globs that can be matched together in a
264 #[derive(Clone, Debug)]
267 strats
: Vec
<GlobSetMatchStrategy
>,
271 /// Returns true if this set is empty, and therefore matches nothing.
272 pub fn is_empty(&self) -> bool
{
276 /// Returns the number of globs in this set.
277 pub fn len(&self) -> usize {
281 /// Returns true if any glob in this set matches the path given.
282 pub fn is_match
<P
: AsRef
<Path
>>(&self, path
: P
) -> bool
{
283 self.is_match_candidate(&Candidate
::new(path
.as_ref()))
286 /// Returns true if any glob in this set matches the path given.
288 /// This takes a Candidate as input, which can be used to amortize the
289 /// cost of preparing a path for matching.
290 pub fn is_match_candidate(&self, path
: &Candidate
) -> bool
{
294 for strat
in &self.strats
{
295 if strat
.is_match(path
) {
302 /// Returns the sequence number of every glob pattern that matches the
304 pub fn matches
<P
: AsRef
<Path
>>(&self, path
: P
) -> Vec
<usize> {
305 self.matches_candidate(&Candidate
::new(path
.as_ref()))
308 /// Returns the sequence number of every glob pattern that matches the
311 /// This takes a Candidate as input, which can be used to amortize the
312 /// cost of preparing a path for matching.
313 pub fn matches_candidate(&self, path
: &Candidate
) -> Vec
<usize> {
314 let mut into
= vec
![];
318 self.matches_candidate_into(path
, &mut into
);
322 /// Adds the sequence number of every glob pattern that matches the given
323 /// path to the vec given.
325 /// `into` is is cleared before matching begins, and contains the set of
326 /// sequence numbers (in ascending order) after matching ends. If no globs
327 /// were matched, then `into` will be empty.
328 pub fn matches_into
<P
: AsRef
<Path
>>(
331 into
: &mut Vec
<usize>,
333 self.matches_candidate_into(&Candidate
::new(path
.as_ref()), into
);
336 /// Adds the sequence number of every glob pattern that matches the given
337 /// path to the vec given.
339 /// `into` is is cleared before matching begins, and contains the set of
340 /// sequence numbers (in ascending order) after matching ends. If no globs
341 /// were matched, then `into` will be empty.
343 /// This takes a Candidate as input, which can be used to amortize the
344 /// cost of preparing a path for matching.
345 pub fn matches_candidate_into(
348 into
: &mut Vec
<usize>,
354 for strat
in &self.strats
{
355 strat
.matches_into(path
, into
);
361 fn new(pats
: &[Glob
]) -> Result
<GlobSet
, Error
> {
363 return Ok(GlobSet { len: 0, strats: vec![] }
);
365 let mut lits
= LiteralStrategy
::new();
366 let mut base_lits
= BasenameLiteralStrategy
::new();
367 let mut exts
= ExtensionStrategy
::new();
368 let mut prefixes
= MultiStrategyBuilder
::new();
369 let mut suffixes
= MultiStrategyBuilder
::new();
370 let mut required_exts
= RequiredExtensionStrategyBuilder
::new();
371 let mut regexes
= MultiStrategyBuilder
::new();
372 for (i
, p
) in pats
.iter().enumerate() {
373 match MatchStrategy
::new(p
) {
374 MatchStrategy
::Literal(lit
) => {
377 MatchStrategy
::BasenameLiteral(lit
) => {
378 base_lits
.add(i
, lit
);
380 MatchStrategy
::Extension(ext
) => {
383 MatchStrategy
::Prefix(prefix
) => {
384 prefixes
.add(i
, prefix
);
386 MatchStrategy
::Suffix { suffix, component }
=> {
388 lits
.add(i
, suffix
[1..].to_string());
390 suffixes
.add(i
, suffix
);
392 MatchStrategy
::RequiredExtension(ext
) => {
393 required_exts
.add(i
, ext
, p
.regex().to_owned());
395 MatchStrategy
::Regex
=> {
396 debug
!("glob converted to regex: {:?}", p
);
397 regexes
.add(i
, p
.regex().to_owned());
401 debug
!("built glob set; {} literals, {} basenames, {} extensions, \
402 {} prefixes, {} suffixes, {} required extensions, {} regexes",
403 lits
.0.len(), base_lits
.0.len(), exts
.0.len(),
404 prefixes
.literals
.len(), suffixes
.literals
.len(),
405 required_exts
.0.len(), regexes
.literals
.len());
409 GlobSetMatchStrategy
::Extension(exts
),
410 GlobSetMatchStrategy
::BasenameLiteral(base_lits
),
411 GlobSetMatchStrategy
::Literal(lits
),
412 GlobSetMatchStrategy
::Suffix(suffixes
.suffix()),
413 GlobSetMatchStrategy
::Prefix(prefixes
.prefix()),
414 GlobSetMatchStrategy
::RequiredExtension(
415 try
!(required_exts
.build())),
416 GlobSetMatchStrategy
::Regex(try
!(regexes
.regex_set())),
422 /// GlobSetBuilder builds a group of patterns that can be used to
423 /// simultaneously match a file path.
424 pub struct GlobSetBuilder
{
428 impl GlobSetBuilder
{
429 /// Create a new GlobSetBuilder. A GlobSetBuilder can be used to add new
430 /// patterns. Once all patterns have been added, `build` should be called
431 /// to produce a `GlobSet`, which can then be used for matching.
432 pub fn new() -> GlobSetBuilder
{
433 GlobSetBuilder { pats: vec![] }
436 /// Builds a new matcher from all of the glob patterns added so far.
438 /// Once a matcher is built, no new patterns can be added to it.
439 pub fn build(&self) -> Result
<GlobSet
, Error
> {
440 GlobSet
::new(&self.pats
)
443 /// Add a new pattern to this set.
445 pub fn add(&mut self, pat
: Glob
) -> &mut GlobSetBuilder
{
451 /// A candidate path for matching.
453 /// All glob matching in this crate operates on `Candidate` values.
454 /// Constructing candidates has a very small cost associated with it, so
455 /// callers may find it beneficial to amortize that cost when matching a single
456 /// path against multiple globs or sets of globs.
457 #[derive(Clone, Debug)]
458 pub struct Candidate
<'a
> {
460 basename
: Cow
<'a
, [u8]>,
464 impl<'a
> Candidate
<'a
> {
465 /// Create a new candidate for matching from the given path.
466 pub fn new
<P
: AsRef
<Path
> + ?Sized
>(path
: &'a P
) -> Candidate
<'a
> {
467 let path
= path
.as_ref();
468 let basename
= file_name(path
).unwrap_or(OsStr
::new(""));
470 path
: normalize_path(path_bytes(path
)),
471 basename
: os_str_bytes(basename
),
472 ext
: file_name_ext(basename
).unwrap_or(OsStr
::new("")),
476 fn path_prefix(&self, max
: usize) -> &[u8] {
477 if self.path
.len() <= max
{
484 fn path_suffix(&self, max
: usize) -> &[u8] {
485 if self.path
.len() <= max
{
488 &self.path
[self.path
.len() - max
..]
493 #[derive(Clone, Debug)]
494 enum GlobSetMatchStrategy
{
495 Literal(LiteralStrategy
),
496 BasenameLiteral(BasenameLiteralStrategy
),
497 Extension(ExtensionStrategy
),
498 Prefix(PrefixStrategy
),
499 Suffix(SuffixStrategy
),
500 RequiredExtension(RequiredExtensionStrategy
),
501 Regex(RegexSetStrategy
),
504 impl GlobSetMatchStrategy
{
505 fn is_match(&self, candidate
: &Candidate
) -> bool
{
506 use self::GlobSetMatchStrategy
::*;
508 Literal(ref s
) => s
.is_match(candidate
),
509 BasenameLiteral(ref s
) => s
.is_match(candidate
),
510 Extension(ref s
) => s
.is_match(candidate
),
511 Prefix(ref s
) => s
.is_match(candidate
),
512 Suffix(ref s
) => s
.is_match(candidate
),
513 RequiredExtension(ref s
) => s
.is_match(candidate
),
514 Regex(ref s
) => s
.is_match(candidate
),
518 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
519 use self::GlobSetMatchStrategy
::*;
521 Literal(ref s
) => s
.matches_into(candidate
, matches
),
522 BasenameLiteral(ref s
) => s
.matches_into(candidate
, matches
),
523 Extension(ref s
) => s
.matches_into(candidate
, matches
),
524 Prefix(ref s
) => s
.matches_into(candidate
, matches
),
525 Suffix(ref s
) => s
.matches_into(candidate
, matches
),
526 RequiredExtension(ref s
) => s
.matches_into(candidate
, matches
),
527 Regex(ref s
) => s
.matches_into(candidate
, matches
),
532 #[derive(Clone, Debug)]
533 struct LiteralStrategy(BTreeMap
<Vec
<u8>, Vec
<usize>>);
535 impl LiteralStrategy
{
536 fn new() -> LiteralStrategy
{
537 LiteralStrategy(BTreeMap
::new())
540 fn add(&mut self, global_index
: usize, lit
: String
) {
541 self.0.entry(lit
.into_bytes()).or_insert(vec
![]).push(global_index
);
544 fn is_match(&self, candidate
: &Candidate
) -> bool
{
545 self.0.contains_key(&*candidate
.path
)
549 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
550 if let Some(hits
) = self.0.get(&*candidate
.path
) {
551 matches
.extend(hits
);
556 #[derive(Clone, Debug)]
557 struct BasenameLiteralStrategy(BTreeMap
<Vec
<u8>, Vec
<usize>>);
559 impl BasenameLiteralStrategy
{
560 fn new() -> BasenameLiteralStrategy
{
561 BasenameLiteralStrategy(BTreeMap
::new())
564 fn add(&mut self, global_index
: usize, lit
: String
) {
565 self.0.entry(lit
.into_bytes()).or_insert(vec
![]).push(global_index
);
568 fn is_match(&self, candidate
: &Candidate
) -> bool
{
569 if candidate
.basename
.is_empty() {
572 self.0.contains_key(&*candidate
.basename
)
576 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
577 if candidate
.basename
.is_empty() {
580 if let Some(hits
) = self.0.get(&*candidate
.basename
) {
581 matches
.extend(hits
);
586 #[derive(Clone, Debug)]
587 struct ExtensionStrategy(HashMap
<OsString
, Vec
<usize>, Fnv
>);
589 impl ExtensionStrategy
{
590 fn new() -> ExtensionStrategy
{
591 ExtensionStrategy(HashMap
::with_hasher(Fnv
::default()))
594 fn add(&mut self, global_index
: usize, ext
: OsString
) {
595 self.0.entry(ext
).or_insert(vec
![]).push(global_index
);
598 fn is_match(&self, candidate
: &Candidate
) -> bool
{
599 if candidate
.ext
.is_empty() {
602 self.0.contains_key(candidate
.ext
)
606 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
607 if candidate
.ext
.is_empty() {
610 if let Some(hits
) = self.0.get(candidate
.ext
) {
611 matches
.extend(hits
);
616 #[derive(Clone, Debug)]
617 struct PrefixStrategy
{
618 matcher
: FullAcAutomaton
<Vec
<u8>>,
623 impl PrefixStrategy
{
624 fn is_match(&self, candidate
: &Candidate
) -> bool
{
625 let path
= candidate
.path_prefix(self.longest
);
626 for m
in self.matcher
.find_overlapping(path
) {
634 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
635 let path
= candidate
.path_prefix(self.longest
);
636 for m
in self.matcher
.find_overlapping(path
) {
638 matches
.push(self.map
[m
.pati
]);
644 #[derive(Clone, Debug)]
645 struct SuffixStrategy
{
646 matcher
: FullAcAutomaton
<Vec
<u8>>,
651 impl SuffixStrategy
{
652 fn is_match(&self, candidate
: &Candidate
) -> bool
{
653 let path
= candidate
.path_suffix(self.longest
);
654 for m
in self.matcher
.find_overlapping(path
) {
655 if m
.end
== path
.len() {
662 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
663 let path
= candidate
.path_suffix(self.longest
);
664 for m
in self.matcher
.find_overlapping(path
) {
665 if m
.end
== path
.len() {
666 matches
.push(self.map
[m
.pati
]);
672 #[derive(Clone, Debug)]
673 struct RequiredExtensionStrategy(HashMap
<OsString
, Vec
<(usize, Regex
)>, Fnv
>);
675 impl RequiredExtensionStrategy
{
676 fn is_match(&self, candidate
: &Candidate
) -> bool
{
677 if candidate
.ext
.is_empty() {
680 match self.0.get(candidate
.ext
) {
683 for &(_
, ref re
) in regexes
{
684 if re
.is_match(&*candidate
.path
) {
694 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
695 if candidate
.ext
.is_empty() {
698 if let Some(regexes
) = self.0.get(candidate
.ext
) {
699 for &(global_index
, ref re
) in regexes
{
700 if re
.is_match(&*candidate
.path
) {
701 matches
.push(global_index
);
708 #[derive(Clone, Debug)]
709 struct RegexSetStrategy
{
714 impl RegexSetStrategy
{
715 fn is_match(&self, candidate
: &Candidate
) -> bool
{
716 self.matcher
.is_match(&*candidate
.path
)
719 fn matches_into(&self, candidate
: &Candidate
, matches
: &mut Vec
<usize>) {
720 for i
in self.matcher
.matches(&*candidate
.path
) {
721 matches
.push(self.map
[i
]);
726 #[derive(Clone, Debug)]
727 struct MultiStrategyBuilder
{
728 literals
: Vec
<String
>,
733 impl MultiStrategyBuilder
{
734 fn new() -> MultiStrategyBuilder
{
735 MultiStrategyBuilder
{
742 fn add(&mut self, global_index
: usize, literal
: String
) {
743 if literal
.len() > self.longest
{
744 self.longest
= literal
.len();
746 self.map
.push(global_index
);
747 self.literals
.push(literal
);
750 fn prefix(self) -> PrefixStrategy
{
751 let it
= self.literals
.into_iter().map(|s
| s
.into_bytes());
753 matcher
: AcAutomaton
::new(it
).into_full(),
755 longest
: self.longest
,
759 fn suffix(self) -> SuffixStrategy
{
760 let it
= self.literals
.into_iter().map(|s
| s
.into_bytes());
762 matcher
: AcAutomaton
::new(it
).into_full(),
764 longest
: self.longest
,
768 fn regex_set(self) -> Result
<RegexSetStrategy
, Error
> {
769 Ok(RegexSetStrategy
{
770 matcher
: try
!(new_regex_set(self.literals
)),
776 #[derive(Clone, Debug)]
777 struct RequiredExtensionStrategyBuilder(
778 HashMap
<OsString
, Vec
<(usize, String
)>>,
781 impl RequiredExtensionStrategyBuilder
{
782 fn new() -> RequiredExtensionStrategyBuilder
{
783 RequiredExtensionStrategyBuilder(HashMap
::new())
786 fn add(&mut self, global_index
: usize, ext
: OsString
, regex
: String
) {
787 self.0.entry(ext
).or_insert(vec
![]).push((global_index
, regex
));
790 fn build(self) -> Result
<RequiredExtensionStrategy
, Error
> {
791 let mut exts
= HashMap
::with_hasher(Fnv
::default());
792 for (ext
, regexes
) in self.0.into_iter
() {
793 exts
.insert(ext
.clone(), vec
![]);
794 for (global_index
, regex
) in regexes
{
795 let compiled
= try
!(new_regex(®ex
));
796 exts
.get_mut(&ext
).unwrap().push((global_index
, compiled
));
799 Ok(RequiredExtensionStrategy(exts
))
805 use super::GlobSetBuilder
;
810 let mut builder
= GlobSetBuilder
::new();
811 builder
.add(Glob
::new("src/**/*.rs").unwrap());
812 builder
.add(Glob
::new("*.c").unwrap());
813 builder
.add(Glob
::new("src/lib.rs").unwrap());
814 let set
= builder
.build().unwrap();
816 assert
!(set
.is_match("foo.c"));
817 assert
!(set
.is_match("src/foo.c"));
818 assert
!(!set
.is_match("foo.rs"));
819 assert
!(!set
.is_match("tests/foo.rs"));
820 assert
!(set
.is_match("src/foo.rs"));
821 assert
!(set
.is_match("src/grep/src/main.rs"));
823 let matches
= set
.matches("src/lib.rs");
824 assert_eq
!(2, matches
.len());
825 assert_eq
!(0, matches
[0]);
826 assert_eq
!(2, matches
[1]);
830 fn empty_set_works() {
831 let set
= GlobSetBuilder
::new().build().unwrap();
832 assert
!(!set
.is_match(""));
833 assert
!(!set
.is_match("a"));