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