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