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.
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.
12 * Support for matching file paths against Unix shell style patterns.
14 * The `glob` and `glob_with` functions, in concert with the `Paths`
15 * type, allow querying the filesystem for all files that match a particular
16 * pattern - just like the libc `glob` function (for an example see the `glob`
17 * documentation). The methods on the `Pattern` type provide functionality
18 * for checking if individual paths match a particular pattern - in a similar
19 * manner to the libc `fnmatch` function
21 * For consistency across platforms, and for Windows support, this module
22 * is implemented entirely in Rust rather than deferring to the libc
23 * `glob`/`fnmatch` functions.
26 #![crate_id = "glob#0.11.0-pre"]
28 #![crate_type = "rlib"]
29 #![crate_type = "dylib"]
30 #![license = "MIT/ASL2"]
31 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
32 html_favicon_url = "http://www.rust-lang.org/favicon.ico",
33 html_root_url = "http://doc.rust-lang.org/",
34 html_playground_url = "http://play.rust-lang.org/")]
37 use std::{cmp, os, path};
39 use std::path::is_sep;
40 use std::string::String;
43 * An iterator that yields Paths from the filesystem that match a particular
44 * pattern - see the `glob` function for more details.
47 dir_patterns: Vec<Pattern>,
49 options: MatchOptions,
50 todo: Vec<(Path,uint)>,
54 /// Return an iterator that produces all the Paths that match the given pattern,
55 /// which may be absolute or relative to the current working directory.
57 /// This method uses the default match options and is equivalent to calling
58 /// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you
59 /// want to use non-default match options.
63 /// Consider a directory `/media/pictures` containing only the files `kittens.jpg`,
64 /// `puppies.jpg` and `hamsters.gif`:
69 /// for path in glob("/media/pictures/*.jpg") {
70 /// println!("{}", path.display());
74 /// The above code will print:
77 /// /media/pictures/kittens.jpg
78 /// /media/pictures/puppies.jpg
81 pub fn glob(pattern: &str) -> Paths {
82 glob_with(pattern, MatchOptions::new())
86 * Return an iterator that produces all the Paths that match the given pattern,
87 * which may be absolute or relative to the current working directory.
89 * This function accepts Unix shell style patterns as described by `Pattern::new(..)`.
90 * The options given are passed through unchanged to `Pattern::matches_with(..)` with
91 * the exception that `require_literal_separator` is always set to `true` regardless of the
92 * value passed to this function.
94 * Paths are yielded in alphabetical order, as absolute paths.
96 pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
98 fn check_windows_verbatim(p: &Path) -> bool { path::windows::is_verbatim(p) }
100 fn check_windows_verbatim(_: &Path) -> bool { false }
102 // calculate root this way to handle volume-relative Windows paths correctly
103 let mut root = os::getcwd();
104 let pat_root = Path::new(pattern).root_path();
105 if pat_root.is_some() {
106 if check_windows_verbatim(pat_root.get_ref()) {
107 // FIXME: How do we want to handle verbatim paths? I'm inclined to return nothing,
108 // since we can't very well find all UNC shares with a 1-letter server name.
110 dir_patterns: Vec::new(),
116 root.push(pat_root.get_ref());
119 let root_len = pat_root.map_or(0u, |p| p.as_vec().len());
120 let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
121 .split_terminator(is_sep)
122 .map(|s| Pattern::new(s))
123 .collect::<Vec<Pattern>>();
124 let require_dir = pattern.chars().next_back().map(is_sep) == Some(true);
126 let mut todo = Vec::new();
127 if dir_patterns.len() > 0 {
128 // Shouldn't happen, but we're using -1 as a special index.
129 assert!(dir_patterns.len() < -1 as uint);
131 fill_todo(&mut todo, dir_patterns.as_slice(), 0, &root, options);
135 dir_patterns: dir_patterns,
136 require_dir: require_dir,
142 impl Iterator<Path> for Paths {
144 fn next(&mut self) -> Option<Path> {
146 if self.dir_patterns.is_empty() || self.todo.is_empty() {
150 let (path,idx) = self.todo.pop().unwrap();
151 // idx -1: was already checked by fill_todo, maybe path was '.' or
152 // '..' that we can't match here because of normalization.
153 if idx == -1 as uint {
154 if self.require_dir && !path.is_dir() { continue; }
157 let ref pattern = *self.dir_patterns.get(idx);
159 if pattern.matches_with(match path.filename_str() {
160 // this ugly match needs to go here to avoid a borrowck error
162 // FIXME (#9639): How do we handle non-utf8 filenames? Ignore them for now
163 // Ideally we'd still match them against a *
168 if idx == self.dir_patterns.len() - 1 {
169 // it is not possible for a pattern to match a directory *AND* its children
170 // so we don't need to check the children
172 if !self.require_dir || path.is_dir() {
176 fill_todo(&mut self.todo, self.dir_patterns.as_slice(),
177 idx + 1, &path, self.options);
185 fn list_dir_sorted(path: &Path) -> Option<Vec<Path>> {
186 match fs::readdir(path) {
187 Ok(mut children) => {
188 children.sort_by(|p1, p2| p2.filename().cmp(&p1.filename()));
189 Some(children.move_iter().collect())
196 * A compiled Unix shell style pattern.
198 #[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
200 tokens: Vec<PatternToken>,
203 #[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
208 AnyWithin(Vec<CharSpecifier> ),
209 AnyExcept(Vec<CharSpecifier> )
212 #[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215 CharRange(char, char)
218 #[deriving(PartialEq)]
221 SubPatternDoesntMatch,
222 EntirePatternDoesntMatch
228 * This function compiles Unix shell style patterns: `?` matches any single
229 * character, `*` matches any (possibly empty) sequence of characters and
230 * `[...]` matches any character inside the brackets, unless the first
231 * character is `!` in which case it matches any character except those
232 * between the `!` and the `]`. Character sequences can also specify ranges
233 * of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any
234 * character between 0 and 9 inclusive.
236 * The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets
237 * (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then
238 * it is interpreted as being part of, rather then ending, the character
239 * set, so `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively.
240 * The `-` character can be specified inside a character sequence pattern by
241 * placing it at the start or the end, e.g. `[abc-]`.
243 * When a `[` does not have a closing `]` before the end of the string then
244 * the `[` will be treated literally.
246 pub fn new(pattern: &str) -> Pattern {
248 let chars = pattern.chars().collect::<Vec<_>>();
249 let mut tokens = Vec::new();
252 while i < chars.len() {
253 match *chars.get(i) {
255 tokens.push(AnyChar);
259 // *, **, ***, ****, ... are all equivalent
260 while i < chars.len() && *chars.get(i) == '*' {
263 tokens.push(AnySequence);
267 if i <= chars.len() - 4 && *chars.get(i + 1) == '!' {
268 match chars.slice_from(i + 3).position_elem(&']') {
271 let chars = chars.slice(i + 2, i + 3 + j);
272 let cs = parse_char_specifiers(chars);
273 tokens.push(AnyExcept(cs));
279 else if i <= chars.len() - 3 && *chars.get(i + 1) != '!' {
280 match chars.slice_from(i + 2).position_elem(&']') {
283 let cs = parse_char_specifiers(chars.slice(i + 1, i + 2 + j));
284 tokens.push(AnyWithin(cs));
291 // if we get here then this is not a valid range pattern
292 tokens.push(Char('['));
296 tokens.push(Char(c));
302 Pattern { tokens: tokens }
306 * Escape metacharacters within the given string by surrounding them in
307 * brackets. The resulting string will, when compiled into a `Pattern`,
308 * match the input string and nothing else.
310 pub fn escape(s: &str) -> String {
311 let mut escaped = String::new();
314 // note that ! does not need escaping because it is only special inside brackets
315 '?' | '*' | '[' | ']' => {
316 escaped.push_char('[');
317 escaped.push_char(c);
318 escaped.push_char(']');
321 escaped.push_char(c);
329 * Return if the given `str` matches this `Pattern` using the default
330 * match options (i.e. `MatchOptions::new()`).
337 * assert!(Pattern::new("c?t").matches("cat"));
338 * assert!(Pattern::new("k[!e]tteh").matches("kitteh"));
339 * assert!(Pattern::new("d*g").matches("doog"));
342 pub fn matches(&self, str: &str) -> bool {
343 self.matches_with(str, MatchOptions::new())
347 * Return if the given `Path`, when converted to a `str`, matches this `Pattern`
348 * using the default match options (i.e. `MatchOptions::new()`).
350 pub fn matches_path(&self, path: &Path) -> bool {
351 // FIXME (#9639): This needs to handle non-utf8 paths
352 path.as_str().map_or(false, |s| {
358 * Return if the given `str` matches this `Pattern` using the specified match options.
360 pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
361 self.matches_from(None, str, 0, options) == Match
365 * Return if the given `Path`, when converted to a `str`, matches this `Pattern`
366 * using the specified match options.
368 pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
369 // FIXME (#9639): This needs to handle non-utf8 paths
370 path.as_str().map_or(false, |s| {
371 self.matches_with(s, options)
375 fn matches_from(&self,
376 prev_char: Option<char>,
379 options: MatchOptions) -> MatchResult {
381 let prev_char = Cell::new(prev_char);
383 let require_literal = |c| {
384 (options.require_literal_separator && is_sep(c)) ||
385 (options.require_literal_leading_dot && c == '.'
386 && is_sep(prev_char.get().unwrap_or('/')))
389 for (ti, token) in self.tokens.slice_from(i).iter().enumerate() {
393 match self.matches_from(prev_char.get(), file, i + ti + 1, options) {
394 SubPatternDoesntMatch => (), // keep trying
399 return EntirePatternDoesntMatch;
402 let (some_c, next) = file.slice_shift_char();
403 if require_literal(some_c.unwrap()) {
404 return SubPatternDoesntMatch;
406 prev_char.set(some_c);
412 return EntirePatternDoesntMatch;
415 let (some_c, next) = file.slice_shift_char();
416 let c = some_c.unwrap();
417 let matches = match *token {
421 AnyWithin(ref specifiers) => {
422 !require_literal(c) &&
423 in_char_specifiers(specifiers.as_slice(),
427 AnyExcept(ref specifiers) => {
428 !require_literal(c) &&
429 !in_char_specifiers(specifiers.as_slice(),
434 chars_eq(c, c2, options.case_sensitive)
441 return SubPatternDoesntMatch;
443 prev_char.set(some_c);
452 SubPatternDoesntMatch
458 // Fills `todo` with paths under `path` to be matched by `patterns[idx]`,
459 // special-casing patterns to match `.` and `..`, and avoiding `readdir()`
460 // calls when there are no metacharacters in the pattern.
461 fn fill_todo(todo: &mut Vec<(Path, uint)>, patterns: &[Pattern], idx: uint, path: &Path,
462 options: MatchOptions) {
463 // convert a pattern that's just many Char(_) to a string
464 fn pattern_as_str(pattern: &Pattern) -> Option<String> {
465 let mut s = String::new();
466 for token in pattern.tokens.iter() {
468 Char(c) => s.push_char(c),
475 let add = |todo: &mut Vec<_>, next_path: Path| {
476 if idx + 1 == patterns.len() {
477 // We know it's good, so don't make the iterator match this path
478 // against the pattern again. In particular, it can't match
479 // . or .. globs since these never show up as path components.
480 todo.push((next_path, -1 as uint));
482 fill_todo(todo, patterns, idx + 1, &next_path, options);
486 let pattern = &patterns[idx];
488 match pattern_as_str(pattern) {
490 // This pattern component doesn't have any metacharacters, so we
491 // don't need to read the current directory to know where to
492 // continue. So instead of passing control back to the iterator,
493 // we can just check for that one entry and potentially recurse
495 let special = "." == s.as_slice() || ".." == s.as_slice();
496 let next_path = path.join(s.as_slice());
497 if (special && path.is_dir()) || (!special && next_path.exists()) {
498 add(todo, next_path);
502 match list_dir_sorted(path) {
504 todo.extend(entries.move_iter().map(|x|(x, idx)));
506 // Matching the special directory entries . and .. that refer to
507 // the current and parent directory respectively requires that
508 // the pattern has a leading dot, even if the `MatchOptions` field
509 // `require_literal_leading_dot` is not set.
510 if pattern.tokens.len() > 0 && pattern.tokens.get(0) == &Char('.') {
511 for &special in [".", ".."].iter() {
512 if pattern.matches_with(special, options) {
513 add(todo, path.join(special));
524 fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
525 let mut cs = Vec::new();
528 if i + 3 <= s.len() && s[i + 1] == '-' {
529 cs.push(CharRange(s[i], s[i + 2]));
532 cs.push(SingleChar(s[i]));
539 fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
541 for &specifier in specifiers.iter() {
544 if chars_eq(c, sc, options.case_sensitive) {
548 CharRange(start, end) => {
550 // FIXME: work with non-ascii chars properly (issue #1347)
551 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
553 let start = start.to_ascii().to_lowercase();
554 let end = end.to_ascii().to_lowercase();
556 let start_up = start.to_uppercase();
557 let end_up = end.to_uppercase();
559 // only allow case insensitive matching when
560 // both start and end are within a-z or A-Z
561 if start != start_up && end != end_up {
562 let start = start.to_char();
563 let end = end.to_char();
564 let c = c.to_ascii().to_lowercase().to_char();
565 if c >= start && c <= end {
571 if c >= start && c <= end {
581 /// A helper function to determine if two chars are (possibly case-insensitively) equal.
582 fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
583 if cfg!(windows) && path::windows::is_sep(a) && path::windows::is_sep(b) {
585 } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
586 // FIXME: work with non-ascii chars properly (issue #1347)
587 a.to_ascii().eq_ignore_case(b.to_ascii())
594 * Configuration options to modify the behaviour of `Pattern::matches_with(..)`
596 #[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
597 pub struct MatchOptions {
600 * Whether or not patterns should be matched in a case-sensitive manner. This
601 * currently only considers upper/lower case relationships between ASCII characters,
602 * but in future this might be extended to work with Unicode.
604 case_sensitive: bool,
607 * If this is true then path-component separator characters (e.g. `/` on Posix)
608 * must be matched by a literal `/`, rather than by `*` or `?` or `[...]`
610 require_literal_separator: bool,
613 * If this is true then paths that contain components that start with a `.` will
614 * not match unless the `.` appears literally in the pattern: `*`, `?` or `[...]`
615 * will not match. This is useful because such files are conventionally considered
616 * hidden on Unix systems and it might be desirable to skip them when listing files.
618 require_literal_leading_dot: bool
624 * Constructs a new `MatchOptions` with default field values. This is used
625 * when calling functions that do not take an explicit `MatchOptions` parameter.
627 * This function always returns this value:
631 * case_sensitive: true,
632 * require_literal_separator: false.
633 * require_literal_leading_dot: false
637 pub fn new() -> MatchOptions {
639 case_sensitive: true,
640 require_literal_separator: false,
641 require_literal_leading_dot: false
650 use super::{glob, Pattern, MatchOptions};
653 fn test_absolute_pattern() {
654 // assume that the filesystem is not empty!
655 assert!(glob("/*").next().is_some());
656 assert!(glob("//").next().is_some());
658 // check windows absolute paths with host/device components
659 let root_with_device = os::getcwd().root_path().unwrap().join("*");
660 // FIXME (#9639): This needs to handle non-utf8 paths
661 assert!(glob(root_with_device.as_str().unwrap()).next().is_some());
665 fn test_wildcard_optimizations() {
666 assert!(Pattern::new("a*b").matches("a___b"));
667 assert!(Pattern::new("a**b").matches("a___b"));
668 assert!(Pattern::new("a***b").matches("a___b"));
669 assert!(Pattern::new("a*b*c").matches("abc"));
670 assert!(!Pattern::new("a*b*c").matches("abcd"));
671 assert!(Pattern::new("a*b*c").matches("a_b_c"));
672 assert!(Pattern::new("a*b*c").matches("a___b___c"));
673 assert!(Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabc"));
674 assert!(!Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabca"));
675 assert!(Pattern::new("a*a*a*a*a*a*a*a*a").matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
676 assert!(Pattern::new("a*b[xyz]c*d").matches("abxcdbxcddd"));
680 fn test_lots_of_files() {
681 // this is a good test because it touches lots of differently named files
682 glob("/*/*/*/*").skip(10000).next();
686 fn test_range_pattern() {
688 let pat = Pattern::new("a[0-9]b");
689 for i in range(0u, 10) {
690 assert!(pat.matches(format!("a{}b", i).as_slice()));
692 assert!(!pat.matches("a_b"));
694 let pat = Pattern::new("a[!0-9]b");
695 for i in range(0u, 10) {
696 assert!(!pat.matches(format!("a{}b", i).as_slice()));
698 assert!(pat.matches("a_b"));
700 let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
701 for &p in pats.iter() {
702 let pat = Pattern::new(p);
703 for c in "abcdefghijklmnopqrstuvwxyz".chars() {
704 assert!(pat.matches(c.to_str().as_slice()));
706 for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
707 let options = MatchOptions {case_sensitive: false, .. MatchOptions::new()};
708 assert!(pat.matches_with(c.to_str().as_slice(), options));
710 assert!(pat.matches("1"));
711 assert!(pat.matches("2"));
712 assert!(pat.matches("3"));
715 let pats = ["[abc-]", "[-abc]", "[a-c-]"];
716 for &p in pats.iter() {
717 let pat = Pattern::new(p);
718 assert!(pat.matches("a"));
719 assert!(pat.matches("b"));
720 assert!(pat.matches("c"));
721 assert!(pat.matches("-"));
722 assert!(!pat.matches("d"));
725 let pat = Pattern::new("[2-1]");
726 assert!(!pat.matches("1"));
727 assert!(!pat.matches("2"));
729 assert!(Pattern::new("[-]").matches("-"));
730 assert!(!Pattern::new("[!-]").matches("-"));
734 fn test_unclosed_bracket() {
735 // unclosed `[` should be treated literally
736 assert!(Pattern::new("abc[def").matches("abc[def"));
737 assert!(Pattern::new("abc[!def").matches("abc[!def"));
738 assert!(Pattern::new("abc[").matches("abc["));
739 assert!(Pattern::new("abc[!").matches("abc[!"));
740 assert!(Pattern::new("abc[d").matches("abc[d"));
741 assert!(Pattern::new("abc[!d").matches("abc[!d"));
742 assert!(Pattern::new("abc[]").matches("abc[]"));
743 assert!(Pattern::new("abc[!]").matches("abc[!]"));
747 fn test_pattern_matches() {
748 let txt_pat = Pattern::new("*hello.txt");
749 assert!(txt_pat.matches("hello.txt"));
750 assert!(txt_pat.matches("gareth_says_hello.txt"));
751 assert!(txt_pat.matches("some/path/to/hello.txt"));
752 assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
753 assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
754 assert!(!txt_pat.matches("hello.txt-and-then-some"));
755 assert!(!txt_pat.matches("goodbye.txt"));
757 let dir_pat = Pattern::new("*some/path/to/hello.txt");
758 assert!(dir_pat.matches("some/path/to/hello.txt"));
759 assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
760 assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
761 assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
765 fn test_pattern_escape() {
766 let s = "_[_]_?_*_!_";
767 assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string());
768 assert!(Pattern::new(Pattern::escape(s).as_slice()).matches(s));
772 fn test_pattern_matches_case_insensitive() {
774 let pat = Pattern::new("aBcDeFg");
775 let options = MatchOptions {
776 case_sensitive: false,
777 require_literal_separator: false,
778 require_literal_leading_dot: false
781 assert!(pat.matches_with("aBcDeFg", options));
782 assert!(pat.matches_with("abcdefg", options));
783 assert!(pat.matches_with("ABCDEFG", options));
784 assert!(pat.matches_with("AbCdEfG", options));
788 fn test_pattern_matches_case_insensitive_range() {
790 let pat_within = Pattern::new("[a]");
791 let pat_except = Pattern::new("[!a]");
793 let options_case_insensitive = MatchOptions {
794 case_sensitive: false,
795 require_literal_separator: false,
796 require_literal_leading_dot: false
798 let options_case_sensitive = MatchOptions {
799 case_sensitive: true,
800 require_literal_separator: false,
801 require_literal_leading_dot: false
804 assert!(pat_within.matches_with("a", options_case_insensitive));
805 assert!(pat_within.matches_with("A", options_case_insensitive));
806 assert!(!pat_within.matches_with("A", options_case_sensitive));
808 assert!(!pat_except.matches_with("a", options_case_insensitive));
809 assert!(!pat_except.matches_with("A", options_case_insensitive));
810 assert!(pat_except.matches_with("A", options_case_sensitive));
814 fn test_pattern_matches_require_literal_separator() {
816 let options_require_literal = MatchOptions {
817 case_sensitive: true,
818 require_literal_separator: true,
819 require_literal_leading_dot: false
821 let options_not_require_literal = MatchOptions {
822 case_sensitive: true,
823 require_literal_separator: false,
824 require_literal_leading_dot: false
827 assert!(Pattern::new("abc/def").matches_with("abc/def", options_require_literal));
828 assert!(!Pattern::new("abc?def").matches_with("abc/def", options_require_literal));
829 assert!(!Pattern::new("abc*def").matches_with("abc/def", options_require_literal));
830 assert!(!Pattern::new("abc[/]def").matches_with("abc/def", options_require_literal));
832 assert!(Pattern::new("abc/def").matches_with("abc/def", options_not_require_literal));
833 assert!(Pattern::new("abc?def").matches_with("abc/def", options_not_require_literal));
834 assert!(Pattern::new("abc*def").matches_with("abc/def", options_not_require_literal));
835 assert!(Pattern::new("abc[/]def").matches_with("abc/def", options_not_require_literal));
839 fn test_pattern_matches_require_literal_leading_dot() {
841 let options_require_literal_leading_dot = MatchOptions {
842 case_sensitive: true,
843 require_literal_separator: false,
844 require_literal_leading_dot: true
846 let options_not_require_literal_leading_dot = MatchOptions {
847 case_sensitive: true,
848 require_literal_separator: false,
849 require_literal_leading_dot: false
852 let f = |options| Pattern::new("*.txt").matches_with(".hello.txt", options);
853 assert!(f(options_not_require_literal_leading_dot));
854 assert!(!f(options_require_literal_leading_dot));
856 let f = |options| Pattern::new(".*.*").matches_with(".hello.txt", options);
857 assert!(f(options_not_require_literal_leading_dot));
858 assert!(f(options_require_literal_leading_dot));
860 let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/.ccc", options);
861 assert!(f(options_not_require_literal_leading_dot));
862 assert!(!f(options_require_literal_leading_dot));
864 let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/c.c.c.", options);
865 assert!(f(options_not_require_literal_leading_dot));
866 assert!(f(options_require_literal_leading_dot));
868 let f = |options| Pattern::new("aaa/bbb/.*").matches_with("aaa/bbb/.ccc", options);
869 assert!(f(options_not_require_literal_leading_dot));
870 assert!(f(options_require_literal_leading_dot));
872 let f = |options| Pattern::new("aaa/?bbb").matches_with("aaa/.bbb", options);
873 assert!(f(options_not_require_literal_leading_dot));
874 assert!(!f(options_require_literal_leading_dot));
876 let f = |options| Pattern::new("aaa/[.]bbb").matches_with("aaa/.bbb", options);
877 assert!(f(options_not_require_literal_leading_dot));
878 assert!(!f(options_require_literal_leading_dot));
882 fn test_matches_path() {
883 // on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this
884 // tests that / and \ are considered equivalent on windows
885 assert!(Pattern::new("a/b").matches_path(&Path::new("a/b")));