(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, optionsMatchOptions) -> 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, optionsMatchOptions) -> 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, optionsMatchOptions) -> bool {
 303          self.matches_with(path.to_str(), options)
 304      }
 305  
 306      fn matches_from(&self,
 307                      mut prev_charOption<char>,
 308                      mut file&str,
 309                      iuint,
 310                      optionsMatchOptions) -> 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], cchar, optionsMatchOptions) -> 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(achar, bchar, case_sensitivebool) -> 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(cchar) -> 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)]