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