]> git.lizzy.rs Git - rust.git/blob - src/libglob/lib.rs
c5af3309e2146da1eceb7d1c8b983dc970a81e7f
[rust.git] / src / libglob / lib.rs
1 // Copyright 2013 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 /*!
12  * Support for matching file paths against Unix shell style patterns.
13  *
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
20  *
21  * For consistency across platforms, and for Windows support, this module
22  * is implemented entirely in Rust rather than deferring to the libc
23  * `glob`/`fnmatch` functions.
24  */
25
26 #[crate_id = "glob#0.10-pre"];
27 #[crate_type = "rlib"];
28 #[crate_type = "dylib"];
29 #[license = "MIT/ASL2"];
30 #[doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
31       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
32       html_root_url = "http://static.rust-lang.org/doc/master")];
33
34 use std::cell::Cell;
35 use std::{cmp, os, path};
36 use std::io::fs;
37 use std::path::is_sep;
38
39 /**
40  * An iterator that yields Paths from the filesystem that match a particular
41  * pattern - see the `glob` function for more details.
42  */
43 pub struct Paths {
44     priv root: Path,
45     priv dir_patterns: ~[Pattern],
46     priv options: MatchOptions,
47     priv todo: ~[(Path,uint)]
48 }
49
50 ///
51 /// Return an iterator that produces all the Paths that match the given pattern,
52 /// which may be absolute or relative to the current working directory.
53 ///
54 /// is method uses the default match options and is equivalent to calling
55 /// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you
56 /// want to use non-default match options.
57 ///
58 /// # Example
59 ///
60 /// Consider a directory `/media/pictures` containing only the files `kittens.jpg`,
61 /// `puppies.jpg` and `hamsters.gif`:
62 ///
63 /// ```rust
64 /// use glob::glob;
65 ///
66 /// for path in glob("/media/pictures/*.jpg") {
67 ///     println!("{}", path.display());
68 /// }
69 /// ```
70 ///
71 /// The above code will print:
72 ///
73 /// ```ignore
74 /// /media/pictures/kittens.jpg
75 /// /media/pictures/puppies.jpg
76 /// ```
77 ///
78 pub fn glob(pattern: &str) -> Paths {
79     glob_with(pattern, MatchOptions::new())
80 }
81
82 /**
83  * Return an iterator that produces all the Paths that match the given pattern,
84  * which may be absolute or relative to the current working directory.
85  *
86  * This function accepts Unix shell style patterns as described by `Pattern::new(..)`.
87  * The options given are passed through unchanged to `Pattern::matches_with(..)` with
88  * the exception that `require_literal_separator` is always set to `true` regardless of the
89  * value passed to this function.
90  *
91  * Paths are yielded in alphabetical order, as absolute paths.
92  */
93 pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
94     #[cfg(windows)]
95     fn check_windows_verbatim(p: &Path) -> bool { path::windows::is_verbatim(p) }
96     #[cfg(not(windows))]
97     fn check_windows_verbatim(_: &Path) -> bool { false }
98
99     // calculate root this way to handle volume-relative Windows paths correctly
100     let mut root = os::getcwd();
101     let pat_root = Path::new(pattern).root_path();
102     if pat_root.is_some() {
103         if check_windows_verbatim(pat_root.get_ref()) {
104             // FIXME: How do we want to handle verbatim paths? I'm inclined to return nothing,
105             // since we can't very well find all UNC shares with a 1-letter server name.
106             return Paths { root: root, dir_patterns: ~[], options: options, todo: ~[] };
107         }
108         root.push(pat_root.get_ref());
109     }
110
111     let root_len = pat_root.map_or(0u, |p| p.as_vec().len());
112     let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
113                        .split_terminator(is_sep).map(|s| Pattern::new(s)).to_owned_vec();
114
115     let todo = list_dir_sorted(&root).move_iter().map(|x|(x,0u)).to_owned_vec();
116
117     Paths {
118         root: root,
119         dir_patterns: dir_patterns,
120         options: options,
121         todo: todo,
122     }
123 }
124
125 impl Iterator<Path> for Paths {
126
127     fn next(&mut self) -> Option<Path> {
128         loop {
129             if self.dir_patterns.is_empty() || self.todo.is_empty() {
130                 return None;
131             }
132
133             let (path,idx) = self.todo.pop().unwrap();
134             let ref pattern = self.dir_patterns[idx];
135
136             if pattern.matches_with(match path.filename_str() {
137                 // this ugly match needs to go here to avoid a borrowck error
138                 None => {
139                     // FIXME (#9639): How do we handle non-utf8 filenames? Ignore them for now
140                     // Ideally we'd still match them against a *
141                     continue;
142                 }
143                 Some(x) => x
144             }, self.options) {
145                 if idx == self.dir_patterns.len() - 1 {
146                     // it is not possible for a pattern to match a directory *AND* its children
147                     // so we don't need to check the children
148                     return Some(path);
149                 } else {
150                     self.todo.extend(&mut list_dir_sorted(&path).move_iter().map(|x|(x,idx+1)));
151                 }
152             }
153         }
154     }
155
156 }
157
158 fn list_dir_sorted(path: &Path) -> ~[Path] {
159     match fs::readdir(path) {
160         Ok(mut children) => {
161             children.sort_by(|p1, p2| p2.filename().cmp(&p1.filename()));
162             children
163         }
164         Err(..) => ~[]
165     }
166 }
167
168 /**
169  * A compiled Unix shell style pattern.
170  */
171 #[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, Hash, Default)]
172 pub struct Pattern {
173     priv tokens: ~[PatternToken]
174 }
175
176 #[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, Hash)]
177 enum PatternToken {
178     Char(char),
179     AnyChar,
180     AnySequence,
181     AnyWithin(~[CharSpecifier]),
182     AnyExcept(~[CharSpecifier])
183 }
184
185 #[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, Hash)]
186 enum CharSpecifier {
187     SingleChar(char),
188     CharRange(char, char)
189 }
190
191 #[deriving(Eq)]
192 enum MatchResult {
193     Match,
194     SubPatternDoesntMatch,
195     EntirePatternDoesntMatch
196 }
197
198 impl Pattern {
199
200     /**
201      * This function compiles Unix shell style patterns: `?` matches any single
202      * character, `*` matches any (possibly empty) sequence of characters and
203      * `[...]` matches any character inside the brackets, unless the first
204      * character is `!` in which case it matches any character except those
205      * between the `!` and the `]`. Character sequences can also specify ranges
206      * of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any
207      * character between 0 and 9 inclusive.
208      *
209      * The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets
210      * (e.g. `[?]`).  When a `]` occurs immediately following `[` or `[!` then
211      * it is interpreted as being part of, rather then ending, the character
212      * set, so `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively.
213      * The `-` character can be specified inside a character sequence pattern by
214      * placing it at the start or the end, e.g. `[abc-]`.
215      *
216      * When a `[` does not have a closing `]` before the end of the string then
217      * the `[` will be treated literally.
218      */
219     pub fn new(pattern: &str) -> Pattern {
220
221         let chars = pattern.chars().to_owned_vec();
222         let mut tokens = ~[];
223         let mut i = 0;
224
225         while i < chars.len() {
226             match chars[i] {
227                 '?' => {
228                     tokens.push(AnyChar);
229                     i += 1;
230                 }
231                 '*' => {
232                     // *, **, ***, ****, ... are all equivalent
233                     while i < chars.len() && chars[i] == '*' {
234                         i += 1;
235                     }
236                     tokens.push(AnySequence);
237                 }
238                 '[' => {
239
240                     if i <= chars.len() - 4 && chars[i + 1] == '!' {
241                         match chars.slice_from(i + 3).position_elem(&']') {
242                             None => (),
243                             Some(j) => {
244                                 let chars = chars.slice(i + 2, i + 3 + j);
245                                 let cs = parse_char_specifiers(chars);
246                                 tokens.push(AnyExcept(cs));
247                                 i += j + 4;
248                                 continue;
249                             }
250                         }
251                     }
252                     else if i <= chars.len() - 3 && chars[i + 1] != '!' {
253                         match chars.slice_from(i + 2).position_elem(&']') {
254                             None => (),
255                             Some(j) => {
256                                 let cs = parse_char_specifiers(chars.slice(i + 1, i + 2 + j));
257                                 tokens.push(AnyWithin(cs));
258                                 i += j + 3;
259                                 continue;
260                             }
261                         }
262                     }
263
264                     // if we get here then this is not a valid range pattern
265                     tokens.push(Char('['));
266                     i += 1;
267                 }
268                 c => {
269                     tokens.push(Char(c));
270                     i += 1;
271                 }
272             }
273         }
274
275         Pattern { tokens: tokens }
276     }
277
278     /**
279      * Escape metacharacters within the given string by surrounding them in
280      * brackets. The resulting string will, when compiled into a `Pattern`,
281      * match the input string and nothing else.
282      */
283     pub fn escape(s: &str) -> ~str {
284         let mut escaped = ~"";
285         for c in s.chars() {
286             match c {
287                 // note that ! does not need escaping because it is only special inside brackets
288                 '?' | '*' | '[' | ']' => {
289                     escaped.push_char('[');
290                     escaped.push_char(c);
291                     escaped.push_char(']');
292                 }
293                 c => {
294                     escaped.push_char(c);
295                 }
296             }
297         }
298         escaped
299     }
300
301     /**
302      * Return if the given `str` matches this `Pattern` using the default
303      * match options (i.e. `MatchOptions::new()`).
304      *
305      * # Example
306      *
307      * ```rust
308      * use glob::Pattern;
309      *
310      * assert!(Pattern::new("c?t").matches("cat"));
311      * assert!(Pattern::new("k[!e]tteh").matches("kitteh"));
312      * assert!(Pattern::new("d*g").matches("doog"));
313      * ```
314      */
315     pub fn matches(&self, str: &str) -> bool {
316         self.matches_with(str, MatchOptions::new())
317     }
318
319     /**
320      * Return if the given `Path`, when converted to a `str`, matches this `Pattern`
321      * using the default match options (i.e. `MatchOptions::new()`).
322      */
323     pub fn matches_path(&self, path: &Path) -> bool {
324         // FIXME (#9639): This needs to handle non-utf8 paths
325         path.as_str().map_or(false, |s| {
326             self.matches(s)
327         })
328     }
329
330     /**
331      * Return if the given `str` matches this `Pattern` using the specified match options.
332      */
333     pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
334         self.matches_from(None, str, 0, options) == Match
335     }
336
337     /**
338      * Return if the given `Path`, when converted to a `str`, matches this `Pattern`
339      * using the specified match options.
340      */
341     pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
342         // FIXME (#9639): This needs to handle non-utf8 paths
343         path.as_str().map_or(false, |s| {
344             self.matches_with(s, options)
345         })
346     }
347
348     fn matches_from(&self,
349                     prev_char: Option<char>,
350                     mut file: &str,
351                     i: uint,
352                     options: MatchOptions) -> MatchResult {
353
354         let prev_char = Cell::new(prev_char);
355
356         let require_literal = |c| {
357             (options.require_literal_separator && is_sep(c)) ||
358             (options.require_literal_leading_dot && c == '.'
359              && is_sep(prev_char.get().unwrap_or('/')))
360         };
361
362         for (ti, token) in self.tokens.slice_from(i).iter().enumerate() {
363             match *token {
364                 AnySequence => {
365                     loop {
366                         match self.matches_from(prev_char.get(), file, i + ti + 1, options) {
367                             SubPatternDoesntMatch => (), // keep trying
368                             m => return m,
369                         }
370
371                         if file.is_empty() {
372                             return EntirePatternDoesntMatch;
373                         }
374
375                         let (some_c, next) = file.slice_shift_char();
376                         if require_literal(some_c.unwrap()) {
377                             return SubPatternDoesntMatch;
378                         }
379                         prev_char.set(some_c);
380                         file = next;
381                     }
382                 }
383                 _ => {
384                     if file.is_empty() {
385                         return EntirePatternDoesntMatch;
386                     }
387
388                     let (some_c, next) = file.slice_shift_char();
389                     let c = some_c.unwrap();
390                     let matches = match *token {
391                         AnyChar => {
392                             !require_literal(c)
393                         }
394                         AnyWithin(ref specifiers) => {
395                             !require_literal(c) && in_char_specifiers(*specifiers, c, options)
396                         }
397                         AnyExcept(ref specifiers) => {
398                             !require_literal(c) && !in_char_specifiers(*specifiers, c, options)
399                         }
400                         Char(c2) => {
401                             chars_eq(c, c2, options.case_sensitive)
402                         }
403                         AnySequence => {
404                             unreachable!()
405                         }
406                     };
407                     if !matches {
408                         return SubPatternDoesntMatch;
409                     }
410                     prev_char.set(some_c);
411                     file = next;
412                 }
413             }
414         }
415
416         if file.is_empty() {
417             Match
418         } else {
419             SubPatternDoesntMatch
420         }
421     }
422
423 }
424
425 fn parse_char_specifiers(s: &[char]) -> ~[CharSpecifier] {
426     let mut cs = ~[];
427     let mut i = 0;
428     while i < s.len() {
429         if i + 3 <= s.len() && s[i + 1] == '-' {
430             cs.push(CharRange(s[i], s[i + 2]));
431             i += 3;
432         } else {
433             cs.push(SingleChar(s[i]));
434             i += 1;
435         }
436     }
437     cs
438 }
439
440 fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
441
442     for &specifier in specifiers.iter() {
443         match specifier {
444             SingleChar(sc) => {
445                 if chars_eq(c, sc, options.case_sensitive) {
446                     return true;
447                 }
448             }
449             CharRange(start, end) => {
450
451                 // FIXME: work with non-ascii chars properly (issue #1347)
452                 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
453
454                     let start = start.to_ascii().to_lower();
455                     let end = end.to_ascii().to_lower();
456
457                     let start_up = start.to_upper();
458                     let end_up = end.to_upper();
459
460                     // only allow case insensitive matching when
461                     // both start and end are within a-z or A-Z
462                     if start != start_up && end != end_up {
463                         let start = start.to_char();
464                         let end = end.to_char();
465                         let c = c.to_ascii().to_lower().to_char();
466                         if c >= start && c <= end {
467                             return true;
468                         }
469                     }
470                 }
471
472                 if c >= start && c <= end {
473                     return true;
474                 }
475             }
476         }
477     }
478
479     false
480 }
481
482 /// A helper function to determine if two chars are (possibly case-insensitively) equal.
483 fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
484     if cfg!(windows) && path::windows::is_sep(a) && path::windows::is_sep(b) {
485         true
486     } else if !case_sensitive && a.is_ascii() && b.is_ascii() {
487         // FIXME: work with non-ascii chars properly (issue #1347)
488         a.to_ascii().eq_ignore_case(b.to_ascii())
489     } else {
490         a == b
491     }
492 }
493
494 /**
495  * Configuration options to modify the behaviour of `Pattern::matches_with(..)`
496  */
497 #[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, Hash, Default)]
498 pub struct MatchOptions {
499
500     /**
501      * Whether or not patterns should be matched in a case-sensitive manner. This
502      * currently only considers upper/lower case relationships between ASCII characters,
503      * but in future this might be extended to work with Unicode.
504      */
505     priv case_sensitive: bool,
506
507     /**
508      * If this is true then path-component separator characters (e.g. `/` on Posix)
509      * must be matched by a literal `/`, rather than by `*` or `?` or `[...]`
510      */
511     priv require_literal_separator: bool,
512
513     /**
514      * If this is true then paths that contain components that start with a `.` will
515      * not match unless the `.` appears literally in the pattern: `*`, `?` or `[...]`
516      * will not match. This is useful because such files are conventionally considered
517      * hidden on Unix systems and it might be desirable to skip them when listing files.
518      */
519     priv require_literal_leading_dot: bool
520 }
521
522 impl MatchOptions {
523
524     /**
525      * Constructs a new `MatchOptions` with default field values. This is used
526      * when calling functions that do not take an explicit `MatchOptions` parameter.
527      *
528      * This function always returns this value:
529      *
530      * ```rust,ignore
531      * MatchOptions {
532      *     case_sensitive: true,
533      *     require_literal_separator: false.
534      *     require_literal_leading_dot: false
535      * }
536      * ```
537      */
538     pub fn new() -> MatchOptions {
539         MatchOptions {
540             case_sensitive: true,
541             require_literal_separator: false,
542             require_literal_leading_dot: false
543         }
544     }
545
546 }
547
548 #[cfg(test)]
549 mod test {
550     use std::os;
551     use super::{glob, Pattern, MatchOptions};
552
553     #[test]
554     fn test_absolute_pattern() {
555         // assume that the filesystem is not empty!
556         assert!(glob("/*").next().is_some());
557         assert!(glob("//").next().is_none());
558
559         // check windows absolute paths with host/device components
560         let root_with_device = os::getcwd().root_path().unwrap().join("*");
561         // FIXME (#9639): This needs to handle non-utf8 paths
562         assert!(glob(root_with_device.as_str().unwrap()).next().is_some());
563     }
564
565     #[test]
566     fn test_wildcard_optimizations() {
567         assert!(Pattern::new("a*b").matches("a___b"));
568         assert!(Pattern::new("a**b").matches("a___b"));
569         assert!(Pattern::new("a***b").matches("a___b"));
570         assert!(Pattern::new("a*b*c").matches("abc"));
571         assert!(!Pattern::new("a*b*c").matches("abcd"));
572         assert!(Pattern::new("a*b*c").matches("a_b_c"));
573         assert!(Pattern::new("a*b*c").matches("a___b___c"));
574         assert!(Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabc"));
575         assert!(!Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabca"));
576         assert!(Pattern::new("a*a*a*a*a*a*a*a*a").matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
577         assert!(Pattern::new("a*b[xyz]c*d").matches("abxcdbxcddd"));
578     }
579
580     #[test]
581     fn test_lots_of_files() {
582         // this is a good test because it touches lots of differently named files
583         glob("/*/*/*/*").skip(10000).next();
584     }
585
586     #[test]
587     fn test_range_pattern() {
588
589         let pat = Pattern::new("a[0-9]b");
590         for i in range(0, 10) {
591             assert!(pat.matches(format!("a{}b", i)));
592         }
593         assert!(!pat.matches("a_b"));
594
595         let pat = Pattern::new("a[!0-9]b");
596         for i in range(0, 10) {
597             assert!(!pat.matches(format!("a{}b", i)));
598         }
599         assert!(pat.matches("a_b"));
600
601         let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
602         for &p in pats.iter() {
603             let pat = Pattern::new(p);
604             for c in "abcdefghijklmnopqrstuvwxyz".chars() {
605                 assert!(pat.matches(c.to_str()));
606             }
607             for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
608                 let options = MatchOptions {case_sensitive: false, .. MatchOptions::new()};
609                 assert!(pat.matches_with(c.to_str(), options));
610             }
611             assert!(pat.matches("1"));
612             assert!(pat.matches("2"));
613             assert!(pat.matches("3"));
614         }
615
616         let pats = ["[abc-]", "[-abc]", "[a-c-]"];
617         for &p in pats.iter() {
618             let pat = Pattern::new(p);
619             assert!(pat.matches("a"));
620             assert!(pat.matches("b"));
621             assert!(pat.matches("c"));
622             assert!(pat.matches("-"));
623             assert!(!pat.matches("d"));
624         }
625
626         let pat = Pattern::new("[2-1]");
627         assert!(!pat.matches("1"));
628         assert!(!pat.matches("2"));
629
630         assert!(Pattern::new("[-]").matches("-"));
631         assert!(!Pattern::new("[!-]").matches("-"));
632     }
633
634     #[test]
635     fn test_unclosed_bracket() {
636         // unclosed `[` should be treated literally
637         assert!(Pattern::new("abc[def").matches("abc[def"));
638         assert!(Pattern::new("abc[!def").matches("abc[!def"));
639         assert!(Pattern::new("abc[").matches("abc["));
640         assert!(Pattern::new("abc[!").matches("abc[!"));
641         assert!(Pattern::new("abc[d").matches("abc[d"));
642         assert!(Pattern::new("abc[!d").matches("abc[!d"));
643         assert!(Pattern::new("abc[]").matches("abc[]"));
644         assert!(Pattern::new("abc[!]").matches("abc[!]"));
645     }
646
647     #[test]
648     fn test_pattern_matches() {
649         let txt_pat = Pattern::new("*hello.txt");
650         assert!(txt_pat.matches("hello.txt"));
651         assert!(txt_pat.matches("gareth_says_hello.txt"));
652         assert!(txt_pat.matches("some/path/to/hello.txt"));
653         assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
654         assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
655         assert!(!txt_pat.matches("hello.txt-and-then-some"));
656         assert!(!txt_pat.matches("goodbye.txt"));
657
658         let dir_pat = Pattern::new("*some/path/to/hello.txt");
659         assert!(dir_pat.matches("some/path/to/hello.txt"));
660         assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
661         assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
662         assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
663     }
664
665     #[test]
666     fn test_pattern_escape() {
667         let s = "_[_]_?_*_!_";
668         assert_eq!(Pattern::escape(s), ~"_[[]_[]]_[?]_[*]_!_");
669         assert!(Pattern::new(Pattern::escape(s)).matches(s));
670     }
671
672     #[test]
673     fn test_pattern_matches_case_insensitive() {
674
675         let pat = Pattern::new("aBcDeFg");
676         let options = MatchOptions {
677             case_sensitive: false,
678             require_literal_separator: false,
679             require_literal_leading_dot: false
680         };
681
682         assert!(pat.matches_with("aBcDeFg", options));
683         assert!(pat.matches_with("abcdefg", options));
684         assert!(pat.matches_with("ABCDEFG", options));
685         assert!(pat.matches_with("AbCdEfG", options));
686     }
687
688     #[test]
689     fn test_pattern_matches_case_insensitive_range() {
690
691         let pat_within = Pattern::new("[a]");
692         let pat_except = Pattern::new("[!a]");
693
694         let options_case_insensitive = MatchOptions {
695             case_sensitive: false,
696             require_literal_separator: false,
697             require_literal_leading_dot: false
698         };
699         let options_case_sensitive = MatchOptions {
700             case_sensitive: true,
701             require_literal_separator: false,
702             require_literal_leading_dot: false
703         };
704
705         assert!(pat_within.matches_with("a", options_case_insensitive));
706         assert!(pat_within.matches_with("A", options_case_insensitive));
707         assert!(!pat_within.matches_with("A", options_case_sensitive));
708
709         assert!(!pat_except.matches_with("a", options_case_insensitive));
710         assert!(!pat_except.matches_with("A", options_case_insensitive));
711         assert!(pat_except.matches_with("A", options_case_sensitive));
712     }
713
714     #[test]
715     fn test_pattern_matches_require_literal_separator() {
716
717         let options_require_literal = MatchOptions {
718             case_sensitive: true,
719             require_literal_separator: true,
720             require_literal_leading_dot: false
721         };
722         let options_not_require_literal = MatchOptions {
723             case_sensitive: true,
724             require_literal_separator: false,
725             require_literal_leading_dot: false
726         };
727
728         assert!(Pattern::new("abc/def").matches_with("abc/def", options_require_literal));
729         assert!(!Pattern::new("abc?def").matches_with("abc/def", options_require_literal));
730         assert!(!Pattern::new("abc*def").matches_with("abc/def", options_require_literal));
731         assert!(!Pattern::new("abc[/]def").matches_with("abc/def", options_require_literal));
732
733         assert!(Pattern::new("abc/def").matches_with("abc/def", options_not_require_literal));
734         assert!(Pattern::new("abc?def").matches_with("abc/def", options_not_require_literal));
735         assert!(Pattern::new("abc*def").matches_with("abc/def", options_not_require_literal));
736         assert!(Pattern::new("abc[/]def").matches_with("abc/def", options_not_require_literal));
737     }
738
739     #[test]
740     fn test_pattern_matches_require_literal_leading_dot() {
741
742         let options_require_literal_leading_dot = MatchOptions {
743             case_sensitive: true,
744             require_literal_separator: false,
745             require_literal_leading_dot: true
746         };
747         let options_not_require_literal_leading_dot = MatchOptions {
748             case_sensitive: true,
749             require_literal_separator: false,
750             require_literal_leading_dot: false
751         };
752
753         let f = |options| Pattern::new("*.txt").matches_with(".hello.txt", options);
754         assert!(f(options_not_require_literal_leading_dot));
755         assert!(!f(options_require_literal_leading_dot));
756
757         let f = |options| Pattern::new(".*.*").matches_with(".hello.txt", options);
758         assert!(f(options_not_require_literal_leading_dot));
759         assert!(f(options_require_literal_leading_dot));
760
761         let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/.ccc", options);
762         assert!(f(options_not_require_literal_leading_dot));
763         assert!(!f(options_require_literal_leading_dot));
764
765         let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/c.c.c.", options);
766         assert!(f(options_not_require_literal_leading_dot));
767         assert!(f(options_require_literal_leading_dot));
768
769         let f = |options| Pattern::new("aaa/bbb/.*").matches_with("aaa/bbb/.ccc", options);
770         assert!(f(options_not_require_literal_leading_dot));
771         assert!(f(options_require_literal_leading_dot));
772
773         let f = |options| Pattern::new("aaa/?bbb").matches_with("aaa/.bbb", options);
774         assert!(f(options_not_require_literal_leading_dot));
775         assert!(!f(options_require_literal_leading_dot));
776
777         let f = |options| Pattern::new("aaa/[.]bbb").matches_with("aaa/.bbb", options);
778         assert!(f(options_not_require_literal_leading_dot));
779         assert!(!f(options_require_literal_leading_dot));
780     }
781
782     #[test]
783     fn test_matches_path() {
784         // on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this
785         // tests that / and \ are considered equivalent on windows
786         assert!(Pattern::new("a/b").matches_path(&Path::new("a/b")));
787     }
788 }