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//! Support for matching file paths against Unix shell style patterns. 12//! 13//! The `glob` and `glob_with` functions allow querying the filesystem for all 14//! files that match a particular pattern (similar to the libc `glob` function). 15//! The methods on the `Pattern` type provide functionality for checking if 16//! individual paths match a particular pattern (similar to the libc `fnmatch` 17//! function). 18//! 19//! For consistency across platforms, and for Windows support, this module 20//! is implemented entirely in Rust rather than deferring to the libc 21//! `glob`/`fnmatch` functions. 22//! 23//! # Examples 24//! 25//! To print all jpg files in `/media/` and all of its subdirectories. 26//! 27//! ```rust,no_run 28//! use glob::glob; 29//! 30//! for entry in glob("/media/**/*.jpg").expect("Failed to read glob pattern") { 31//! match entry { 32//! Ok(path) => println!("{:?}", path.display()), 33//! Err(e) => println!("{:?}", e), 34//! } 35//! } 36//! ``` 37//! 38//! To print all files containing the letter "a", case insensitive, in a `local` 39//! directory relative to the current working directory. This ignores errors 40//! instead of printing them. 41//! 42//! ```rust,no_run 43//! use glob::glob_with; 44//! use glob::MatchOptions; 45//! 46//! let options = MatchOptions { 47//! case_sensitive: false, 48//! require_literal_separator: false, 49//! require_literal_leading_dot: false, 50//! }; 51//! for entry in glob_with("local/*a*", options).unwrap() { 52//! if let Ok(path) = entry { 53//! println!("{:?}", path.display()) 54//! } 55//! } 56//! ``` 57 58#![doc( 59 html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", 60 html_favicon_url = "https://www.rust-lang.org/favicon.ico", 61 html_root_url = "https://docs.rs/glob/0.3.1" 62)] 63#![deny(missing_docs)] 64 65#[cfg(test)] 66#[macro_use] 67extern crate doc_comment; 68 69#[cfg(test)] 70doctest!("../README.md"); 71 72use std::cmp; 73use std::error::Error; 74use std::fmt; 75use std::fs; 76use std::io; 77use std::path::{self, Component, Path, PathBuf}; 78use std::str::FromStr; 79 80use CharSpecifier::{CharRange, SingleChar}; 81use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch}; 82use PatternToken::AnyExcept; 83use PatternToken::{AnyChar, AnyRecursiveSequence, AnySequence, AnyWithin, Char}; 84 85/// An iterator that yields `Path`s from the filesystem that match a particular 86/// pattern. 87/// 88/// Note that it yields `GlobResult` in order to report any `IoErrors` that may 89/// arise during iteration. If a directory matches but is unreadable, 90/// thereby preventing its contents from being checked for matches, a 91/// `GlobError` is returned to express this. 92/// 93/// See the `glob` function for more details. 94#[derive(Debug)] 95pub struct Paths { 96 dir_patterns: Vec<Pattern>, 97 require_dir: bool, 98 options: MatchOptions, 99 todo: Vec<Result<(PathBuf, usize), GlobError>>, 100 scope: Option<PathBuf>, 101} 102 103/// Return an iterator that produces all the `Path`s that match the given 104/// pattern using default match options, which may be absolute or relative to 105/// the current working directory. 106/// 107/// This may return an error if the pattern is invalid. 108/// 109/// This method uses the default match options and is equivalent to calling 110/// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you 111/// want to use non-default match options. 112/// 113/// When iterating, each result is a `GlobResult` which expresses the 114/// possibility that there was an `IoError` when attempting to read the contents 115/// of the matched path. In other words, each item returned by the iterator 116/// will either be an `Ok(Path)` if the path matched, or an `Err(GlobError)` if 117/// the path (partially) matched _but_ its contents could not be read in order 118/// to determine if its contents matched. 119/// 120/// See the `Paths` documentation for more information. 121/// 122/// # Examples 123/// 124/// Consider a directory `/media/pictures` containing only the files 125/// `kittens.jpg`, `puppies.jpg` and `hamsters.gif`: 126/// 127/// ```rust,no_run 128/// use glob::glob; 129/// 130/// for entry in glob("/media/pictures/*.jpg").unwrap() { 131/// match entry { 132/// Ok(path) => println!("{:?}", path.display()), 133/// 134/// // if the path matched but was unreadable, 135/// // thereby preventing its contents from matching 136/// Err(e) => println!("{:?}", e), 137/// } 138/// } 139/// ``` 140/// 141/// The above code will print: 142/// 143/// ```ignore 144/// /media/pictures/kittens.jpg 145/// /media/pictures/puppies.jpg 146/// ``` 147/// 148/// If you want to ignore unreadable paths, you can use something like 149/// `filter_map`: 150/// 151/// ```rust 152/// use glob::glob; 153/// use std::result::Result; 154/// 155/// for path in glob("/media/pictures/*.jpg").unwrap().filter_map(Result::ok) { 156/// println!("{}", path.display()); 157/// } 158/// ``` 159/// Paths are yielded in alphabetical order. 160pub fn glob(pattern: &str) -> Result<Paths, PatternError> { 161 glob_with(pattern, MatchOptions::new()) 162} 163 164/// Return an iterator that produces all the `Path`s that match the given 165/// pattern using the specified match options, which may be absolute or relative 166/// to the current working directory. 167/// 168/// This may return an error if the pattern is invalid. 169/// 170/// This function accepts Unix shell style patterns as described by 171/// `Pattern::new(..)`. The options given are passed through unchanged to 172/// `Pattern::matches_with(..)` with the exception that 173/// `require_literal_separator` is always set to `true` regardless of the value 174/// passed to this function. 175/// 176/// Paths are yielded in alphabetical order. 177pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternError> { 178 #[cfg(windows)] 179 fn check_windows_verbatim(p: &Path) -> bool { 180 match p.components().next() { 181 Some(Component::Prefix(ref p)) => p.kind().is_verbatim(), 182 _ => false, 183 } 184 } 185 #[cfg(not(windows))] 186 fn check_windows_verbatim(_: &Path) -> bool { 187 false 188 } 189 190 #[cfg(windows)] 191 fn to_scope(p: &Path) -> PathBuf { 192 // FIXME handle volume relative paths here 193 p.to_path_buf() 194 } 195 #[cfg(not(windows))] 196 fn to_scope(p: &Path) -> PathBuf { 197 p.to_path_buf() 198 } 199 200 // make sure that the pattern is valid first, else early return with error 201 if let Err(err) = Pattern::new(pattern) { 202 return Err(err); 203 } 204 205 let mut components = Path::new(pattern).components().peekable(); 206 loop { 207 match components.peek() { 208 Some(&Component::Prefix(..)) | Some(&Component::RootDir) => { 209 components.next(); 210 } 211 _ => break, 212 } 213 } 214 let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>(); 215 let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>(); 216 let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len(); 217 let root = if root_len > 0 { 218 Some(Path::new(&pattern[..root_len])) 219 } else { 220 None 221 }; 222 223 if root_len > 0 && check_windows_verbatim(root.unwrap()) { 224 // FIXME: How do we want to handle verbatim paths? I'm inclined to 225 // return nothing, since we can't very well find all UNC shares with a 226 // 1-letter server name. 227 return Ok(Paths { 228 dir_patterns: Vec::new(), 229 require_dir: false, 230 options, 231 todo: Vec::new(), 232 scope: None, 233 }); 234 } 235 236 let scope = root.map_or_else(|| PathBuf::from("."), to_scope); 237 238 let mut dir_patterns = Vec::new(); 239 let components = 240 pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator); 241 242 for component in components { 243 dir_patterns.push(Pattern::new(component)?); 244 } 245 246 if root_len == pattern.len() { 247 dir_patterns.push(Pattern { 248 original: "".to_string(), 249 tokens: Vec::new(), 250 is_recursive: false, 251 }); 252 } 253 254 let last_is_separator = pattern.chars().next_back().map(path::is_separator); 255 let require_dir = last_is_separator == Some(true); 256 let todo = Vec::new(); 257 258 Ok(Paths { 259 dir_patterns, 260 require_dir, 261 options, 262 todo, 263 scope: Some(scope), 264 }) 265} 266 267/// A glob iteration error. 268/// 269/// This is typically returned when a particular path cannot be read 270/// to determine if its contents match the glob pattern. This is possible 271/// if the program lacks the appropriate permissions, for example. 272#[derive(Debug)] 273pub struct GlobError { 274 path: PathBuf, 275 error: io::Error, 276} 277 278impl GlobError { 279 /// The Path that the error corresponds to. 280 pub fn path(&self) -> &Path { 281 &self.path 282 } 283 284 /// The error in question. 285 pub fn error(&self) -> &io::Error { 286 &self.error 287 } 288 289 /// Consumes self, returning the _raw_ underlying `io::Error` 290 pub fn into_error(self) -> io::Error { 291 self.error 292 } 293} 294 295impl Error for GlobError { 296 #[allow(deprecated)] 297 fn description(&self) -> &str { 298 self.error.description() 299 } 300 301 #[allow(unknown_lints, bare_trait_objects)] 302 fn cause(&self) -> Option<&Error> { 303 Some(&self.error) 304 } 305} 306 307impl fmt::Display for GlobError { 308 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 309 write!( 310 f, 311 "attempting to read `{}` resulted in an error: {}", 312 self.path.display(), 313 self.error 314 ) 315 } 316} 317 318fn is_dir(p: &Path) -> bool { 319 fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false) 320} 321 322/// An alias for a glob iteration result. 323/// 324/// This represents either a matched path or a glob iteration error, 325/// such as failing to read a particular directory's contents. 326pub type GlobResult = Result<PathBuf, GlobError>; 327 328impl Iterator for Paths { 329 type Item = GlobResult; 330 331 fn next(&mut self) -> Option<GlobResult> { 332 // the todo buffer hasn't been initialized yet, so it's done at this 333 // point rather than in glob() so that the errors are unified that is, 334 // failing to fill the buffer is an iteration error construction of the 335 // iterator (i.e. glob()) only fails if it fails to compile the Pattern 336 if let Some(scope) = self.scope.take() { 337 if !self.dir_patterns.is_empty() { 338 // Shouldn't happen, but we're using -1 as a special index. 339 assert!(self.dir_patterns.len() < !0 as usize); 340 341 fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options); 342 } 343 } 344 345 loop { 346 if self.dir_patterns.is_empty() || self.todo.is_empty() { 347 return None; 348 } 349 350 let (path, mut idx) = match self.todo.pop().unwrap() { 351 Ok(pair) => pair, 352 Err(e) => return Some(Err(e)), 353 }; 354 355 // idx -1: was already checked by fill_todo, maybe path was '.' or 356 // '..' that we can't match here because of normalization. 357 if idx == !0 as usize { 358 if self.require_dir && !is_dir(&path) { 359 continue; 360 } 361 return Some(Ok(path)); 362 } 363 364 if self.dir_patterns[idx].is_recursive { 365 let mut next = idx; 366 367 // collapse consecutive recursive patterns 368 while (next + 1) < self.dir_patterns.len() 369 && self.dir_patterns[next + 1].is_recursive 370 { 371 next += 1; 372 } 373 374 if is_dir(&path) { 375 // the path is a directory, so it's a match 376 377 // push this directory's contents 378 fill_todo( 379 &mut self.todo, 380 &self.dir_patterns, 381 next, 382 &path, 383 self.options, 384 ); 385 386 if next == self.dir_patterns.len() - 1 { 387 // pattern ends in recursive pattern, so return this 388 // directory as a result 389 return Some(Ok(path)); 390 } else { 391 // advanced to the next pattern for this path 392 idx = next + 1; 393 } 394 } else if next == self.dir_patterns.len() - 1 { 395 // not a directory and it's the last pattern, meaning no 396 // match 397 continue; 398 } else { 399 // advanced to the next pattern for this path 400 idx = next + 1; 401 } 402 } 403 404 // not recursive, so match normally 405 if self.dir_patterns[idx].matches_with( 406 { 407 match path.file_name().and_then(|s| s.to_str()) { 408 // FIXME (#9639): How do we handle non-utf8 filenames? 409 // Ignore them for now; ideally we'd still match them 410 // against a * 411 None => continue, 412 Some(x) => x, 413 } 414 }, 415 self.options, 416 ) { 417 if idx == self.dir_patterns.len() - 1 { 418 // it is not possible for a pattern to match a directory 419 // *AND* its children so we don't need to check the 420 // children 421 422 if !self.require_dir || is_dir(&path) { 423 return Some(Ok(path)); 424 } 425 } else { 426 fill_todo( 427 &mut self.todo, 428 &self.dir_patterns, 429 idx + 1, 430 &path, 431 self.options, 432 ); 433 } 434 } 435 } 436 } 437} 438 439/// A pattern parsing error. 440#[derive(Debug)] 441#[allow(missing_copy_implementations)] 442pub struct PatternError { 443 /// The approximate character index of where the error occurred. 444 pub pos: usize, 445 446 /// A message describing the error. 447 pub msg: &'static str, 448} 449 450impl Error for PatternError { 451 fn description(&self) -> &str { 452 self.msg 453 } 454} 455 456impl fmt::Display for PatternError { 457 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 458 write!( 459 f, 460 "Pattern syntax error near position {}: {}", 461 self.pos, self.msg 462 ) 463 } 464} 465 466/// A compiled Unix shell style pattern. 467/// 468/// - `?` matches any single character. 469/// 470/// - `*` matches any (possibly empty) sequence of characters. 471/// 472/// - `**` matches the current directory and arbitrary subdirectories. This 473/// sequence **must** form a single path component, so both `**a` and `b**` 474/// are invalid and will result in an error. A sequence of more than two 475/// consecutive `*` characters is also invalid. 476/// 477/// - `[...]` matches any character inside the brackets. Character sequences 478/// can also specify ranges of characters, as ordered by Unicode, so e.g. 479/// `[0-9]` specifies any character between 0 and 9 inclusive. An unclosed 480/// bracket is invalid. 481/// 482/// - `[!...]` is the negation of `[...]`, i.e. it matches any characters 483/// **not** in the brackets. 484/// 485/// - The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets 486/// (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then it 487/// is interpreted as being part of, rather then ending, the character set, so 488/// `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively. The `-` 489/// character can be specified inside a character sequence pattern by placing 490/// it at the start or the end, e.g. `[abc-]`. 491#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] 492pub struct Pattern { 493 original: String, 494 tokens: Vec<PatternToken>, 495 is_recursive: bool, 496} 497 498/// Show the original glob pattern. 499impl fmt::Display for Pattern { 500 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 501 self.original.fmt(f) 502 } 503} 504 505impl FromStr for Pattern { 506 type Err = PatternError; 507 508 fn from_str(s: &str) -> Result<Self, PatternError> { 509 Self::new(s) 510 } 511} 512 513#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] 514enum PatternToken { 515 Char(char), 516 AnyChar, 517 AnySequence, 518 AnyRecursiveSequence, 519 AnyWithin(Vec<CharSpecifier>), 520 AnyExcept(Vec<CharSpecifier>), 521} 522 523#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] 524enum CharSpecifier { 525 SingleChar(char), 526 CharRange(char, char), 527} 528 529#[derive(Copy, Clone, PartialEq)] 530enum MatchResult { 531 Match, 532 SubPatternDoesntMatch, 533 EntirePatternDoesntMatch, 534} 535 536const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`"; 537const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \ 538 component"; 539const ERROR_INVALID_RANGE: &str = "invalid range pattern"; 540 541impl Pattern { 542 /// This function compiles Unix shell style patterns. 543 /// 544 /// An invalid glob pattern will yield a `PatternError`. 545 pub fn new(pattern: &str) -> Result<Self, PatternError> { 546 let chars = pattern.chars().collect::<Vec<_>>(); 547 let mut tokens = Vec::new(); 548 let mut is_recursive = false; 549 let mut i = 0; 550 551 while i < chars.len() { 552 match chars[i] { 553 '?' => { 554 tokens.push(AnyChar); 555 i += 1; 556 } 557 '*' => { 558 let old = i; 559 560 while i < chars.len() && chars[i] == '*' { 561 i += 1; 562 } 563 564 let count = i - old; 565 566 if count > 2 { 567 return Err(PatternError { 568 pos: old + 2, 569 msg: ERROR_WILDCARDS, 570 }); 571 } else if count == 2 { 572 // ** can only be an entire path component 573 // i.e. a/**/b is valid, but a**/b or a/**b is not 574 // invalid matches are treated literally 575 let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) { 576 // it ends in a '/' 577 if i < chars.len() && path::is_separator(chars[i]) { 578 i += 1; 579 true 580 // or the pattern ends here 581 // this enables the existing globbing mechanism 582 } else if i == chars.len() { 583 true 584 // `**` ends in non-separator 585 } else { 586 return Err(PatternError { 587 pos: i, 588 msg: ERROR_RECURSIVE_WILDCARDS, 589 }); 590 } 591 // `**` begins with non-separator 592 } else { 593 return Err(PatternError { 594 pos: old - 1, 595 msg: ERROR_RECURSIVE_WILDCARDS, 596 }); 597 }; 598 599 if is_valid { 600 // collapse consecutive AnyRecursiveSequence to a 601 // single one 602 603 let tokens_len = tokens.len(); 604 605 if !(tokens_len > 1 && tokens[tokens_len - 1] == AnyRecursiveSequence) { 606 is_recursive = true; 607 tokens.push(AnyRecursiveSequence); 608 } 609 } 610 } else { 611 tokens.push(AnySequence); 612 } 613 } 614 '[' => { 615 if i + 4 <= chars.len() && chars[i + 1] == '!' { 616 match chars[i + 3..].iter().position(|x| *x == ']') { 617 None => (), 618 Some(j) => { 619 let chars = &chars[i + 2..i + 3 + j]; 620 let cs = parse_char_specifiers(chars); 621 tokens.push(AnyExcept(cs)); 622 i += j + 4; 623 continue; 624 } 625 } 626 } else if i + 3 <= chars.len() && chars[i + 1] != '!' { 627 match chars[i + 2..].iter().position(|x| *x == ']') { 628 None => (), 629 Some(j) => { 630 let cs = parse_char_specifiers(&chars[i + 1..i + 2 + j]); 631 tokens.push(AnyWithin(cs)); 632 i += j + 3; 633 continue; 634 } 635 } 636 } 637 638 // if we get here then this is not a valid range pattern 639 return Err(PatternError { 640 pos: i, 641 msg: ERROR_INVALID_RANGE, 642 }); 643 } 644 c => { 645 tokens.push(Char(c)); 646 i += 1; 647 } 648 } 649 } 650 651 Ok(Self { 652 tokens, 653 original: pattern.to_string(), 654 is_recursive, 655 }) 656 } 657 658 /// Escape metacharacters within the given string by surrounding them in 659 /// brackets. The resulting string will, when compiled into a `Pattern`, 660 /// match the input string and nothing else. 661 pub fn escape(s: &str) -> String { 662 let mut escaped = String::new(); 663 for c in s.chars() { 664 match c { 665 // note that ! does not need escaping because it is only special 666 // inside brackets 667 '?' | '*' | '[' | ']' => { 668 escaped.push('['); 669 escaped.push(c); 670 escaped.push(']'); 671 } 672 c => { 673 escaped.push(c); 674 } 675 } 676 } 677 escaped 678 } 679 680 /// Return if the given `str` matches this `Pattern` using the default 681 /// match options (i.e. `MatchOptions::new()`). 682 /// 683 /// # Examples 684 /// 685 /// ```rust 686 /// use glob::Pattern; 687 /// 688 /// assert!(Pattern::new("c?t").unwrap().matches("cat")); 689 /// assert!(Pattern::new("k[!e]tteh").unwrap().matches("kitteh")); 690 /// assert!(Pattern::new("d*g").unwrap().matches("doog")); 691 /// ``` 692 pub fn matches(&self, str: &str) -> bool { 693 self.matches_with(str, MatchOptions::new()) 694 } 695 696 /// Return if the given `Path`, when converted to a `str`, matches this 697 /// `Pattern` using the default match options (i.e. `MatchOptions::new()`). 698 pub fn matches_path(&self, path: &Path) -> bool { 699 // FIXME (#9639): This needs to handle non-utf8 paths 700 path.to_str().map_or(false, |s| self.matches(s)) 701 } 702 703 /// Return if the given `str` matches this `Pattern` using the specified 704 /// match options. 705 pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool { 706 self.matches_from(true, str.chars(), 0, options) == Match 707 } 708 709 /// Return if the given `Path`, when converted to a `str`, matches this 710 /// `Pattern` using the specified match options. 711 pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool { 712 // FIXME (#9639): This needs to handle non-utf8 paths 713 path.to_str() 714 .map_or(false, |s| self.matches_with(s, options)) 715 } 716 717 /// Access the original glob pattern. 718 pub fn as_str(&self) -> &str { 719 &self.original 720 } 721 722 fn matches_from( 723 &self, 724 mut follows_separator: bool, 725 mut file: std::str::Chars, 726 i: usize, 727 options: MatchOptions, 728 ) -> MatchResult { 729 for (ti, token) in self.tokens[i..].iter().enumerate() { 730 match *token { 731 AnySequence | AnyRecursiveSequence => { 732 // ** must be at the start. 733 debug_assert!(match *token { 734 AnyRecursiveSequence => follows_separator, 735 _ => true, 736 }); 737 738 // Empty match 739 match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) { 740 SubPatternDoesntMatch => (), // keep trying 741 m => return m, 742 }; 743 744 while let Some(c) = file.next() { 745 if follows_separator && options.require_literal_leading_dot && c == '.' { 746 return SubPatternDoesntMatch; 747 } 748 follows_separator = path::is_separator(c); 749 match *token { 750 AnyRecursiveSequence if !follows_separator => continue, 751 AnySequence 752 if options.require_literal_separator && follows_separator => 753 { 754 return SubPatternDoesntMatch 755 } 756 _ => (), 757 } 758 match self.matches_from( 759 follows_separator, 760 file.clone(), 761 i + ti + 1, 762 options, 763 ) { 764 SubPatternDoesntMatch => (), // keep trying 765 m => return m, 766 } 767 } 768 } 769 _ => { 770 let c = match file.next() { 771 Some(c) => c, 772 None => return EntirePatternDoesntMatch, 773 }; 774 775 let is_sep = path::is_separator(c); 776 777 if !match *token { 778 AnyChar | AnyWithin(..) | AnyExcept(..) 779 if (options.require_literal_separator && is_sep) 780 || (follows_separator 781 && options.require_literal_leading_dot 782 && c == '.') => 783 { 784 false 785 } 786 AnyChar => true, 787 AnyWithin(ref specifiers) => in_char_specifiers(&specifiers, c, options), 788 AnyExcept(ref specifiers) => !in_char_specifiers(&specifiers, c, options), 789 Char(c2) => chars_eq(c, c2, options.case_sensitive), 790 AnySequence | AnyRecursiveSequence => unreachable!(), 791 } { 792 return SubPatternDoesntMatch; 793 } 794 follows_separator = is_sep; 795 } 796 } 797 } 798 799 // Iter is fused. 800 if file.next().is_none() { 801 Match 802 } else { 803 SubPatternDoesntMatch 804 } 805 } 806} 807 808// Fills `todo` with paths under `path` to be matched by `patterns[idx]`, 809// special-casing patterns to match `.` and `..`, and avoiding `readdir()` 810// calls when there are no metacharacters in the pattern. 811fn fill_todo( 812 todo: &mut Vec<Result<(PathBuf, usize), GlobError>>, 813 patterns: &[Pattern], 814 idx: usize, 815 path: &Path, 816 options: MatchOptions, 817) { 818 // convert a pattern that's just many Char(_) to a string 819 fn pattern_as_str(pattern: &Pattern) -> Option<String> { 820 let mut s = String::new(); 821 for token in &pattern.tokens { 822 match *token { 823 Char(c) => s.push(c), 824 _ => return None, 825 } 826 } 827 828 Some(s) 829 } 830 831 let add = |todo: &mut Vec<_>, next_path: PathBuf| { 832 if idx + 1 == patterns.len() { 833 // We know it's good, so don't make the iterator match this path 834 // against the pattern again. In particular, it can't match 835 // . or .. globs since these never show up as path components. 836 todo.push(Ok((next_path, !0 as usize))); 837 } else { 838 fill_todo(todo, patterns, idx + 1, &next_path, options); 839 } 840 }; 841 842 let pattern = &patterns[idx]; 843 let is_dir = is_dir(path); 844 let curdir = path == Path::new("."); 845 match pattern_as_str(pattern) { 846 Some(s) => { 847 // This pattern component doesn't have any metacharacters, so we 848 // don't need to read the current directory to know where to 849 // continue. So instead of passing control back to the iterator, 850 // we can just check for that one entry and potentially recurse 851 // right away. 852 let special = "." == s || ".." == s; 853 let next_path = if curdir { 854 PathBuf::from(s) 855 } else { 856 path.join(&s) 857 }; 858 if (special && is_dir) || (!special && fs::metadata(&next_path).is_ok()) { 859 add(todo, next_path); 860 } 861 } 862 None if is_dir => { 863 let dirs = fs::read_dir(path).and_then(|d| { 864 d.map(|e| { 865 e.map(|e| { 866 if curdir { 867 PathBuf::from(e.path().file_name().unwrap()) 868 } else { 869 e.path() 870 } 871 }) 872 }) 873 .collect::<Result<Vec<_>, _>>() 874 }); 875 match dirs { 876 Ok(mut children) => { 877 children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name())); 878 todo.extend(children.into_iter().map(|x| Ok((x, idx)))); 879 880 // Matching the special directory entries . and .. that 881 // refer to the current and parent directory respectively 882 // requires that the pattern has a leading dot, even if the 883 // `MatchOptions` field `require_literal_leading_dot` is not 884 // set. 885 if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') { 886 for &special in &[".", ".."] { 887 if pattern.matches_with(special, options) { 888 add(todo, path.join(special)); 889 } 890 } 891 } 892 } 893 Err(e) => { 894 todo.push(Err(GlobError { 895 path: path.to_path_buf(), 896 error: e, 897 })); 898 } 899 } 900 } 901 None => { 902 // not a directory, nothing more to find 903 } 904 } 905} 906 907fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> { 908 let mut cs = Vec::new(); 909 let mut i = 0; 910 while i < s.len() { 911 if i + 3 <= s.len() && s[i + 1] == '-' { 912 cs.push(CharRange(s[i], s[i + 2])); 913 i += 3; 914 } else { 915 cs.push(SingleChar(s[i])); 916 i += 1; 917 } 918 } 919 cs 920} 921 922fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool { 923 for &specifier in specifiers.iter() { 924 match specifier { 925 SingleChar(sc) => { 926 if chars_eq(c, sc, options.case_sensitive) { 927 return true; 928 } 929 } 930 CharRange(start, end) => { 931 // FIXME: work with non-ascii chars properly (issue #1347) 932 if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() { 933 let start = start.to_ascii_lowercase(); 934 let end = end.to_ascii_lowercase(); 935 936 let start_up = start.to_uppercase().next().unwrap(); 937 let end_up = end.to_uppercase().next().unwrap(); 938 939 // only allow case insensitive matching when 940 // both start and end are within a-z or A-Z 941 if start != start_up && end != end_up { 942 let c = c.to_ascii_lowercase(); 943 if c >= start && c <= end { 944 return true; 945 } 946 } 947 } 948 949 if c >= start && c <= end { 950 return true; 951 } 952 } 953 } 954 } 955 956 false 957} 958 959/// A helper function to determine if two chars are (possibly case-insensitively) equal. 960fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool { 961 if cfg!(windows) && path::is_separator(a) && path::is_separator(b) { 962 true 963 } else if !case_sensitive && a.is_ascii() && b.is_ascii() { 964 // FIXME: work with non-ascii chars properly (issue #9084) 965 a.to_ascii_lowercase() == b.to_ascii_lowercase() 966 } else { 967 a == b 968 } 969} 970 971/// Configuration options to modify the behaviour of `Pattern::matches_with(..)`. 972#[allow(missing_copy_implementations)] 973#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 974pub struct MatchOptions { 975 /// Whether or not patterns should be matched in a case-sensitive manner. 976 /// This currently only considers upper/lower case relationships between 977 /// ASCII characters, but in future this might be extended to work with 978 /// Unicode. 979 pub case_sensitive: bool, 980 981 /// Whether or not path-component separator characters (e.g. `/` on 982 /// Posix) must be matched by a literal `/`, rather than by `*` or `?` or 983 /// `[...]`. 984 pub require_literal_separator: bool, 985 986 /// Whether or not paths that contain components that start with a `.` 987 /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, 988 /// or `[...]` will not match. This is useful because such files are 989 /// conventionally considered hidden on Unix systems and it might be 990 /// desirable to skip them when listing files. 991 pub require_literal_leading_dot: bool, 992} 993 994impl MatchOptions { 995 /// Constructs a new `MatchOptions` with default field values. This is used 996 /// when calling functions that do not take an explicit `MatchOptions` 997 /// parameter. 998 /// 999 /// This function always returns this value: 1000 /// 1001 /// ```rust,ignore 1002 /// MatchOptions { 1003 /// case_sensitive: true, 1004 /// require_literal_separator: false, 1005 /// require_literal_leading_dot: false 1006 /// } 1007 /// ``` 1008 /// 1009 /// # Note 1010 /// The behavior of this method doesn't match `default()`'s. This returns 1011 /// `case_sensitive` as `true` while `default()` does it as `false`. 1012 // FIXME: Consider unity the behavior with `default()` in a next major release. 1013 pub fn new() -> Self { 1014 Self { 1015 case_sensitive: true, 1016 require_literal_separator: false, 1017 require_literal_leading_dot: false, 1018 } 1019 } 1020} 1021 1022#[cfg(test)] 1023mod test { 1024 use super::{glob, MatchOptions, Pattern}; 1025 use std::path::Path; 1026 1027 #[test] 1028 fn test_pattern_from_str() { 1029 assert!("a*b".parse::<Pattern>().unwrap().matches("a_b")); 1030 assert!("a/**b".parse::<Pattern>().unwrap_err().pos == 4); 1031 } 1032 1033 #[test] 1034 fn test_wildcard_errors() { 1035 assert!(Pattern::new("a/**b").unwrap_err().pos == 4); 1036 assert!(Pattern::new("a/bc**").unwrap_err().pos == 3); 1037 assert!(Pattern::new("a/*****").unwrap_err().pos == 4); 1038 assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2); 1039 assert!(Pattern::new("a**b").unwrap_err().pos == 0); 1040 } 1041 1042 #[test] 1043 fn test_unclosed_bracket_errors() { 1044 assert!(Pattern::new("abc[def").unwrap_err().pos == 3); 1045 assert!(Pattern::new("abc[!def").unwrap_err().pos == 3); 1046 assert!(Pattern::new("abc[").unwrap_err().pos == 3); 1047 assert!(Pattern::new("abc[!").unwrap_err().pos == 3); 1048 assert!(Pattern::new("abc[d").unwrap_err().pos == 3); 1049 assert!(Pattern::new("abc[!d").unwrap_err().pos == 3); 1050 assert!(Pattern::new("abc[]").unwrap_err().pos == 3); 1051 assert!(Pattern::new("abc[!]").unwrap_err().pos == 3); 1052 } 1053 1054 #[test] 1055 fn test_glob_errors() { 1056 assert!(glob("a/**b").err().unwrap().pos == 4); 1057 assert!(glob("abc[def").err().unwrap().pos == 3); 1058 } 1059 1060 // this test assumes that there is a /root directory and that 1061 // the user running this test is not root or otherwise doesn't 1062 // have permission to read its contents 1063 #[cfg(all(unix, not(target_os = "macos")))] 1064 #[test] 1065 fn test_iteration_errors() { 1066 use std::io; 1067 let mut iter = glob("/root/*").unwrap(); 1068 1069 // GlobErrors shouldn't halt iteration 1070 let next = iter.next(); 1071 assert!(next.is_some()); 1072 1073 let err = next.unwrap(); 1074 assert!(err.is_err()); 1075 1076 let err = err.err().unwrap(); 1077 assert!(err.path() == Path::new("/root")); 1078 assert!(err.error().kind() == io::ErrorKind::PermissionDenied); 1079 } 1080 1081 #[test] 1082 fn test_absolute_pattern() { 1083 assert!(glob("/").unwrap().next().is_some()); 1084 assert!(glob("//").unwrap().next().is_some()); 1085 1086 // assume that the filesystem is not empty! 1087 assert!(glob("/*").unwrap().next().is_some()); 1088 1089 #[cfg(not(windows))] 1090 fn win() {} 1091 1092 #[cfg(windows)] 1093 fn win() { 1094 use std::env::current_dir; 1095 use std::path::Component; 1096 1097 // check windows absolute paths with host/device components 1098 let root_with_device = current_dir() 1099 .ok() 1100 .and_then(|p| { 1101 match p.components().next().unwrap() { 1102 Component::Prefix(prefix_component) => { 1103 let path = Path::new(prefix_component.as_os_str()); 1104 path.join("*"); 1105 Some(path.to_path_buf()) 1106 } 1107 _ => panic!("no prefix in this path"), 1108 } 1109 }) 1110 .unwrap(); 1111 // FIXME (#9639): This needs to handle non-utf8 paths 1112 assert!(glob(root_with_device.as_os_str().to_str().unwrap()) 1113 .unwrap() 1114 .next() 1115 .is_some()); 1116 } 1117 win() 1118 } 1119 1120 #[test] 1121 fn test_wildcards() { 1122 assert!(Pattern::new("a*b").unwrap().matches("a_b")); 1123 assert!(Pattern::new("a*b*c").unwrap().matches("abc")); 1124 assert!(!Pattern::new("a*b*c").unwrap().matches("abcd")); 1125 assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c")); 1126 assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c")); 1127 assert!(Pattern::new("abc*abc*abc") 1128 .unwrap() 1129 .matches("abcabcabcabcabcabcabc")); 1130 assert!(!Pattern::new("abc*abc*abc") 1131 .unwrap() 1132 .matches("abcabcabcabcabcabcabca")); 1133 assert!(Pattern::new("a*a*a*a*a*a*a*a*a") 1134 .unwrap() 1135 .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); 1136 assert!(Pattern::new("a*b[xyz]c*d").unwrap().matches("abxcdbxcddd")); 1137 } 1138 1139 #[test] 1140 fn test_recursive_wildcards() { 1141 let pat = Pattern::new("some/**/needle.txt").unwrap(); 1142 assert!(pat.matches("some/needle.txt")); 1143 assert!(pat.matches("some/one/needle.txt")); 1144 assert!(pat.matches("some/one/two/needle.txt")); 1145 assert!(pat.matches("some/other/needle.txt")); 1146 assert!(!pat.matches("some/other/notthis.txt")); 1147 1148 // a single ** should be valid, for globs 1149 // Should accept anything 1150 let pat = Pattern::new("**").unwrap(); 1151 assert!(pat.is_recursive); 1152 assert!(pat.matches("abcde")); 1153 assert!(pat.matches("")); 1154 assert!(pat.matches(".asdf")); 1155 assert!(pat.matches("/x/.asdf")); 1156 1157 // collapse consecutive wildcards 1158 let pat = Pattern::new("some/**/**/needle.txt").unwrap(); 1159 assert!(pat.matches("some/needle.txt")); 1160 assert!(pat.matches("some/one/needle.txt")); 1161 assert!(pat.matches("some/one/two/needle.txt")); 1162 assert!(pat.matches("some/other/needle.txt")); 1163 assert!(!pat.matches("some/other/notthis.txt")); 1164 1165 // ** can begin the pattern 1166 let pat = Pattern::new("**/test").unwrap(); 1167 assert!(pat.matches("one/two/test")); 1168 assert!(pat.matches("one/test")); 1169 assert!(pat.matches("test")); 1170 1171 // /** can begin the pattern 1172 let pat = Pattern::new("/**/test").unwrap(); 1173 assert!(pat.matches("/one/two/test")); 1174 assert!(pat.matches("/one/test")); 1175 assert!(pat.matches("/test")); 1176 assert!(!pat.matches("/one/notthis")); 1177 assert!(!pat.matches("/notthis")); 1178 1179 // Only start sub-patterns on start of path segment. 1180 let pat = Pattern::new("**/.*").unwrap(); 1181 assert!(pat.matches(".abc")); 1182 assert!(pat.matches("abc/.abc")); 1183 assert!(!pat.matches("ab.c")); 1184 assert!(!pat.matches("abc/ab.c")); 1185 } 1186 1187 #[test] 1188 fn test_lots_of_files() { 1189 // this is a good test because it touches lots of differently named files 1190 glob("/*/*/*/*").unwrap().skip(10000).next(); 1191 } 1192 1193 #[test] 1194 fn test_range_pattern() { 1195 let pat = Pattern::new("a[0-9]b").unwrap(); 1196 for i in 0..10 { 1197 assert!(pat.matches(&format!("a{}b", i))); 1198 } 1199 assert!(!pat.matches("a_b")); 1200 1201 let pat = Pattern::new("a[!0-9]b").unwrap(); 1202 for i in 0..10 { 1203 assert!(!pat.matches(&format!("a{}b", i))); 1204 } 1205 assert!(pat.matches("a_b")); 1206 1207 let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"]; 1208 for &p in pats.iter() { 1209 let pat = Pattern::new(p).unwrap(); 1210 for c in "abcdefghijklmnopqrstuvwxyz".chars() { 1211 assert!(pat.matches(&c.to_string())); 1212 } 1213 for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() { 1214 let options = MatchOptions { 1215 case_sensitive: false, 1216 ..MatchOptions::new() 1217 }; 1218 assert!(pat.matches_with(&c.to_string(), options)); 1219 } 1220 assert!(pat.matches("1")); 1221 assert!(pat.matches("2")); 1222 assert!(pat.matches("3")); 1223 } 1224 1225 let pats = ["[abc-]", "[-abc]", "[a-c-]"]; 1226 for &p in pats.iter() { 1227 let pat = Pattern::new(p).unwrap(); 1228 assert!(pat.matches("a")); 1229 assert!(pat.matches("b")); 1230 assert!(pat.matches("c")); 1231 assert!(pat.matches("-")); 1232 assert!(!pat.matches("d")); 1233 } 1234 1235 let pat = Pattern::new("[2-1]").unwrap(); 1236 assert!(!pat.matches("1")); 1237 assert!(!pat.matches("2")); 1238 1239 assert!(Pattern::new("[-]").unwrap().matches("-")); 1240 assert!(!Pattern::new("[!-]").unwrap().matches("-")); 1241 } 1242 1243 #[test] 1244 fn test_pattern_matches() { 1245 let txt_pat = Pattern::new("*hello.txt").unwrap(); 1246 assert!(txt_pat.matches("hello.txt")); 1247 assert!(txt_pat.matches("gareth_says_hello.txt")); 1248 assert!(txt_pat.matches("some/path/to/hello.txt")); 1249 assert!(txt_pat.matches("some\\path\\to\\hello.txt")); 1250 assert!(txt_pat.matches("/an/absolute/path/to/hello.txt")); 1251 assert!(!txt_pat.matches("hello.txt-and-then-some")); 1252 assert!(!txt_pat.matches("goodbye.txt")); 1253 1254 let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap(); 1255 assert!(dir_pat.matches("some/path/to/hello.txt")); 1256 assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt")); 1257 assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some")); 1258 assert!(!dir_pat.matches("some/other/path/to/hello.txt")); 1259 } 1260 1261 #[test] 1262 fn test_pattern_escape() { 1263 let s = "_[_]_?_*_!_"; 1264 assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string()); 1265 assert!(Pattern::new(&Pattern::escape(s)).unwrap().matches(s)); 1266 } 1267 1268 #[test] 1269 fn test_pattern_matches_case_insensitive() { 1270 let pat = Pattern::new("aBcDeFg").unwrap(); 1271 let options = MatchOptions { 1272 case_sensitive: false, 1273 require_literal_separator: false, 1274 require_literal_leading_dot: false, 1275 }; 1276 1277 assert!(pat.matches_with("aBcDeFg", options)); 1278 assert!(pat.matches_with("abcdefg", options)); 1279 assert!(pat.matches_with("ABCDEFG", options)); 1280 assert!(pat.matches_with("AbCdEfG", options)); 1281 } 1282 1283 #[test] 1284 fn test_pattern_matches_case_insensitive_range() { 1285 let pat_within = Pattern::new("[a]").unwrap(); 1286 let pat_except = Pattern::new("[!a]").unwrap(); 1287 1288 let options_case_insensitive = MatchOptions { 1289 case_sensitive: false, 1290 require_literal_separator: false, 1291 require_literal_leading_dot: false, 1292 }; 1293 let options_case_sensitive = MatchOptions { 1294 case_sensitive: true, 1295 require_literal_separator: false, 1296 require_literal_leading_dot: false, 1297 }; 1298 1299 assert!(pat_within.matches_with("a", options_case_insensitive)); 1300 assert!(pat_within.matches_with("A", options_case_insensitive)); 1301 assert!(!pat_within.matches_with("A", options_case_sensitive)); 1302 1303 assert!(!pat_except.matches_with("a", options_case_insensitive)); 1304 assert!(!pat_except.matches_with("A", options_case_insensitive)); 1305 assert!(pat_except.matches_with("A", options_case_sensitive)); 1306 } 1307 1308 #[test] 1309 fn test_pattern_matches_require_literal_separator() { 1310 let options_require_literal = MatchOptions { 1311 case_sensitive: true, 1312 require_literal_separator: true, 1313 require_literal_leading_dot: false, 1314 }; 1315 let options_not_require_literal = MatchOptions { 1316 case_sensitive: true, 1317 require_literal_separator: false, 1318 require_literal_leading_dot: false, 1319 }; 1320 1321 assert!(Pattern::new("abc/def") 1322 .unwrap() 1323 .matches_with("abc/def", options_require_literal)); 1324 assert!(!Pattern::new("abc?def") 1325 .unwrap() 1326 .matches_with("abc/def", options_require_literal)); 1327 assert!(!Pattern::new("abc*def") 1328 .unwrap() 1329 .matches_with("abc/def", options_require_literal)); 1330 assert!(!Pattern::new("abc[/]def") 1331 .unwrap() 1332 .matches_with("abc/def", options_require_literal)); 1333 1334 assert!(Pattern::new("abc/def") 1335 .unwrap() 1336 .matches_with("abc/def", options_not_require_literal)); 1337 assert!(Pattern::new("abc?def") 1338 .unwrap() 1339 .matches_with("abc/def", options_not_require_literal)); 1340 assert!(Pattern::new("abc*def") 1341 .unwrap() 1342 .matches_with("abc/def", options_not_require_literal)); 1343 assert!(Pattern::new("abc[/]def") 1344 .unwrap() 1345 .matches_with("abc/def", options_not_require_literal)); 1346 } 1347 1348 #[test] 1349 fn test_pattern_matches_require_literal_leading_dot() { 1350 let options_require_literal_leading_dot = MatchOptions { 1351 case_sensitive: true, 1352 require_literal_separator: false, 1353 require_literal_leading_dot: true, 1354 }; 1355 let options_not_require_literal_leading_dot = MatchOptions { 1356 case_sensitive: true, 1357 require_literal_separator: false, 1358 require_literal_leading_dot: false, 1359 }; 1360 1361 let f = |options| { 1362 Pattern::new("*.txt") 1363 .unwrap() 1364 .matches_with(".hello.txt", options) 1365 }; 1366 assert!(f(options_not_require_literal_leading_dot)); 1367 assert!(!f(options_require_literal_leading_dot)); 1368 1369 let f = |options| { 1370 Pattern::new(".*.*") 1371 .unwrap() 1372 .matches_with(".hello.txt", options) 1373 }; 1374 assert!(f(options_not_require_literal_leading_dot)); 1375 assert!(f(options_require_literal_leading_dot)); 1376 1377 let f = |options| { 1378 Pattern::new("aaa/bbb/*") 1379 .unwrap() 1380 .matches_with("aaa/bbb/.ccc", options) 1381 }; 1382 assert!(f(options_not_require_literal_leading_dot)); 1383 assert!(!f(options_require_literal_leading_dot)); 1384 1385 let f = |options| { 1386 Pattern::new("aaa/bbb/*") 1387 .unwrap() 1388 .matches_with("aaa/bbb/c.c.c.", options) 1389 }; 1390 assert!(f(options_not_require_literal_leading_dot)); 1391 assert!(f(options_require_literal_leading_dot)); 1392 1393 let f = |options| { 1394 Pattern::new("aaa/bbb/.*") 1395 .unwrap() 1396 .matches_with("aaa/bbb/.ccc", options) 1397 }; 1398 assert!(f(options_not_require_literal_leading_dot)); 1399 assert!(f(options_require_literal_leading_dot)); 1400 1401 let f = |options| { 1402 Pattern::new("aaa/?bbb") 1403 .unwrap() 1404 .matches_with("aaa/.bbb", options) 1405 }; 1406 assert!(f(options_not_require_literal_leading_dot)); 1407 assert!(!f(options_require_literal_leading_dot)); 1408 1409 let f = |options| { 1410 Pattern::new("aaa/[.]bbb") 1411 .unwrap() 1412 .matches_with("aaa/.bbb", options) 1413 }; 1414 assert!(f(options_not_require_literal_leading_dot)); 1415 assert!(!f(options_require_literal_leading_dot)); 1416 1417 let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options); 1418 assert!(f(options_not_require_literal_leading_dot)); 1419 assert!(!f(options_require_literal_leading_dot)); 1420 } 1421 1422 #[test] 1423 fn test_matches_path() { 1424 // on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this 1425 // tests that / and \ are considered equivalent on windows 1426 assert!(Pattern::new("a/b").unwrap().matches_path(&Path::new("a/b"))); 1427 } 1428 1429 #[test] 1430 fn test_path_join() { 1431 let pattern = Path::new("one").join(&Path::new("**/*.rs")); 1432 assert!(Pattern::new(pattern.to_str().unwrap()).is_ok()); 1433 } 1434} 1435