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