blob: 3cb21601e331d8f2f9d73022ecd076c4fde92bae [file] [log] [blame]
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
* Support for matching file paths against Unix shell style patterns.
*
* The `glob` and `glob_with` functions, in concert with the `Paths`
* type, allow querying the filesystem for all files that match a particular
* pattern - just like the libc `glob` function (for an example see the `glob`
* documentation). The methods on the `Pattern` type provide functionality
* for checking if individual paths match a particular pattern - in a similar
* manner to the libc `fnmatch` function
*
* For consistency across platforms, and for Windows support, this module
* is implemented entirely in Rust rather than deferring to the libc
* `glob`/`fnmatch` functions.
*/
#![crate_id = "glob#0.11.0"]
#![experimental]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
html_root_url = "http://doc.rust-lang.org/0.11.0/",
html_playground_url = "http://play.rust-lang.org/")]
use std::cell::Cell;
use std::{cmp, os, path};
use std::io::fs;
use std::path::is_sep;
use std::string::String;
/**
* An iterator that yields Paths from the filesystem that match a particular
* pattern - see the `glob` function for more details.
*/
pub struct Paths {
dir_patterns: Vec<Pattern>,
require_dir: bool,
options: MatchOptions,
todo: Vec<(Path,uint)>,
}
///
/// Return an iterator that produces all the Paths that match the given pattern,
/// which may be absolute or relative to the current working directory.
///
/// This method uses the default match options and is equivalent to calling
/// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you
/// want to use non-default match options.
///
/// # Example
///
/// Consider a directory `/media/pictures` containing only the files `kittens.jpg`,
/// `puppies.jpg` and `hamsters.gif`:
///
/// ```rust
/// use glob::glob;
///
/// for path in glob("/media/pictures/*.jpg") {
/// println!("{}", path.display());
/// }
/// ```
///
/// The above code will print:
///
/// ```ignore
/// /media/pictures/kittens.jpg
/// /media/pictures/puppies.jpg
/// ```
///
pub fn glob(pattern: &str) -> Paths {
glob_with(pattern, MatchOptions::new())
}
/**
* Return an iterator that produces all the Paths that match the given pattern,
* which may be absolute or relative to the current working directory.
*
* This function accepts Unix shell style patterns as described by `Pattern::new(..)`.
* The options given are passed through unchanged to `Pattern::matches_with(..)` with
* the exception that `require_literal_separator` is always set to `true` regardless of the
* value passed to this function.
*
* Paths are yielded in alphabetical order, as absolute paths.
*/
pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
#[cfg(windows)]
fn check_windows_verbatim(p: &Path) -> bool { path::windows::is_verbatim(p) }
#[cfg(not(windows))]
fn check_windows_verbatim(_: &Path) -> bool { false }
// calculate root this way to handle volume-relative Windows paths correctly
let mut root = os::getcwd();
let pat_root = Path::new(pattern).root_path();
if pat_root.is_some() {
if check_windows_verbatim(pat_root.get_ref()) {
// FIXME: How do we want to handle verbatim paths? I'm inclined to return nothing,
// since we can't very well find all UNC shares with a 1-letter server name.
return Paths {
dir_patterns: Vec::new(),
require_dir: false,
options: options,
todo: Vec::new(),
};
}
root.push(pat_root.get_ref());
}
let root_len = pat_root.map_or(0u, |p| p.as_vec().len());
let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
.split_terminator(is_sep)
.map(|s| Pattern::new(s))
.collect::<Vec<Pattern>>();
let require_dir = pattern.chars().next_back().map(is_sep) == Some(true);
let mut todo = Vec::new();
if dir_patterns.len() > 0 {
// Shouldn't happen, but we're using -1 as a special index.
assert!(dir_patterns.len() < -1 as uint);
fill_todo(&mut todo, dir_patterns.as_slice(), 0, &root, options);
}
Paths {
dir_patterns: dir_patterns,
require_dir: require_dir,
options: options,
todo: todo,
}
}
impl Iterator<Path> for Paths {
fn next(&mut self) -> Option<Path> {
loop {
if self.dir_patterns.is_empty() || self.todo.is_empty() {
return None;
}
let (path,idx) = self.todo.pop().unwrap();
// idx -1: was already checked by fill_todo, maybe path was '.' or
// '..' that we can't match here because of normalization.
if idx == -1 as uint {
if self.require_dir && !path.is_dir() { continue; }
return Some(path);
}
let ref pattern = *self.dir_patterns.get(idx);
if pattern.matches_with(match path.filename_str() {
// this ugly match needs to go here to avoid a borrowck error
None => {
// FIXME (#9639): How do we handle non-utf8 filenames? Ignore them for now
// Ideally we'd still match them against a *
continue;
}
Some(x) => x
}, self.options) {
if idx == self.dir_patterns.len() - 1 {
// it is not possible for a pattern to match a directory *AND* its children
// so we don't need to check the children
if !self.require_dir || path.is_dir() {
return Some(path);
}
} else {
fill_todo(&mut self.todo, self.dir_patterns.as_slice(),
idx + 1, &path, self.options);
}
}
}
}
}
fn list_dir_sorted(path: &Path) -> Option<Vec<Path>> {
match fs::readdir(path) {
Ok(mut children) => {
children.sort_by(|p1, p2| p2.filename().cmp(&p1.filename()));
Some(children.move_iter().collect())
}
Err(..) => None
}
}
/**
* A compiled Unix shell style pattern.
*/
#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Pattern {
tokens: Vec<PatternToken>,
}
#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum PatternToken {
Char(char),
AnyChar,
AnySequence,
AnyWithin(Vec<CharSpecifier> ),
AnyExcept(Vec<CharSpecifier> )
}
#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum CharSpecifier {
SingleChar(char),
CharRange(char, char)
}
#[deriving(PartialEq)]
enum MatchResult {
Match,
SubPatternDoesntMatch,
EntirePatternDoesntMatch
}
impl Pattern {
/**
* This function compiles Unix shell style patterns: `?` matches any single
* character, `*` matches any (possibly empty) sequence of characters and
* `[...]` matches any character inside the brackets, unless the first
* character is `!` in which case it matches any character except those
* between the `!` and the `]`. Character sequences can also specify ranges
* of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any
* character between 0 and 9 inclusive.
*
* The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets
* (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then
* it is interpreted as being part of, rather then ending, the character
* set, so `]` and NOT `]` can be matched by `[]]` and `[!]]` respectively.
* The `-` character can be specified inside a character sequence pattern by
* placing it at the start or the end, e.g. `[abc-]`.
*
* When a `[` does not have a closing `]` before the end of the string then
* the `[` will be treated literally.
*/
pub fn new(pattern: &str) -> Pattern {
let chars = pattern.chars().collect::<Vec<_>>();
let mut tokens = Vec::new();
let mut i = 0;
while i < chars.len() {
match *chars.get(i) {
'?' => {
tokens.push(AnyChar);
i += 1;
}
'*' => {
// *, **, ***, ****, ... are all equivalent
while i < chars.len() && *chars.get(i) == '*' {
i += 1;
}
tokens.push(AnySequence);
}
'[' => {
if i <= chars.len() - 4 && *chars.get(i + 1) == '!' {
match chars.slice_from(i + 3).position_elem(&']') {
None => (),
Some(j) => {
let chars = chars.slice(i + 2, i + 3 + j);
let cs = parse_char_specifiers(chars);
tokens.push(AnyExcept(cs));
i += j + 4;
continue;
}
}
}
else if i <= chars.len() - 3 && *chars.get(i + 1) != '!' {
match chars.slice_from(i + 2).position_elem(&']') {
None => (),
Some(j) => {
let cs = parse_char_specifiers(chars.slice(i + 1, i + 2 + j));
tokens.push(AnyWithin(cs));
i += j + 3;
continue;
}
}
}
// if we get here then this is not a valid range pattern
tokens.push(Char('['));
i += 1;
}
c => {
tokens.push(Char(c));
i += 1;
}
}
}
Pattern { tokens: tokens }
}
/**
* Escape metacharacters within the given string by surrounding them in
* brackets. The resulting string will, when compiled into a `Pattern`,
* match the input string and nothing else.
*/
pub fn escape(s: &str) -> String {
let mut escaped = String::new();
for c in s.chars() {
match c {
// note that ! does not need escaping because it is only special inside brackets
'?' | '*' | '[' | ']' => {
escaped.push_char('[');
escaped.push_char(c);
escaped.push_char(']');
}
c => {
escaped.push_char(c);
}
}
}
escaped
}
/**
* Return if the given `str` matches this `Pattern` using the default
* match options (i.e. `MatchOptions::new()`).
*
* # Example
*
* ```rust
* use glob::Pattern;
*
* assert!(Pattern::new("c?t").matches("cat"));
* assert!(Pattern::new("k[!e]tteh").matches("kitteh"));
* assert!(Pattern::new("d*g").matches("doog"));
* ```
*/
pub fn matches(&self, str: &str) -> bool {
self.matches_with(str, MatchOptions::new())
}
/**
* Return if the given `Path`, when converted to a `str`, matches this `Pattern`
* using the default match options (i.e. `MatchOptions::new()`).
*/
pub fn matches_path(&self, path: &Path) -> bool {
// FIXME (#9639): This needs to handle non-utf8 paths
path.as_str().map_or(false, |s| {
self.matches(s)
})
}
/**
* Return if the given `str` matches this `Pattern` using the specified match options.
*/
pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool {
self.matches_from(None, str, 0, options) == Match
}
/**
* Return if the given `Path`, when converted to a `str`, matches this `Pattern`
* using the specified match options.
*/
pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool {
// FIXME (#9639): This needs to handle non-utf8 paths
path.as_str().map_or(false, |s| {
self.matches_with(s, options)
})
}
fn matches_from(&self,
prev_char: Option<char>,
mut file: &str,
i: uint,
options: MatchOptions) -> MatchResult {
let prev_char = Cell::new(prev_char);
let require_literal = |c| {
(options.require_literal_separator && is_sep(c)) ||
(options.require_literal_leading_dot && c == '.'
&& is_sep(prev_char.get().unwrap_or('/')))
};
for (ti, token) in self.tokens.slice_from(i).iter().enumerate() {
match *token {
AnySequence => {
loop {
match self.matches_from(prev_char.get(), file, i + ti + 1, options) {
SubPatternDoesntMatch => (), // keep trying
m => return m,
}
if file.is_empty() {
return EntirePatternDoesntMatch;
}
let (some_c, next) = file.slice_shift_char();
if require_literal(some_c.unwrap()) {
return SubPatternDoesntMatch;
}
prev_char.set(some_c);
file = next;
}
}
_ => {
if file.is_empty() {
return EntirePatternDoesntMatch;
}
let (some_c, next) = file.slice_shift_char();
let c = some_c.unwrap();
let matches = match *token {
AnyChar => {
!require_literal(c)
}
AnyWithin(ref specifiers) => {
!require_literal(c) &&
in_char_specifiers(specifiers.as_slice(),
c,
options)
}
AnyExcept(ref specifiers) => {
!require_literal(c) &&
!in_char_specifiers(specifiers.as_slice(),
c,
options)
}
Char(c2) => {
chars_eq(c, c2, options.case_sensitive)
}
AnySequence => {
unreachable!()
}
};
if !matches {
return SubPatternDoesntMatch;
}
prev_char.set(some_c);
file = next;
}
}
}
if file.is_empty() {
Match
} else {
SubPatternDoesntMatch
}
}
}
// Fills `todo` with paths under `path` to be matched by `patterns[idx]`,
// special-casing patterns to match `.` and `..`, and avoiding `readdir()`
// calls when there are no metacharacters in the pattern.
fn fill_todo(todo: &mut Vec<(Path, uint)>, patterns: &[Pattern], idx: uint, path: &Path,
options: MatchOptions) {
// convert a pattern that's just many Char(_) to a string
fn pattern_as_str(pattern: &Pattern) -> Option<String> {
let mut s = String::new();
for token in pattern.tokens.iter() {
match *token {
Char(c) => s.push_char(c),
_ => return None
}
}
return Some(s);
}
let add = |todo: &mut Vec<_>, next_path: Path| {
if idx + 1 == patterns.len() {
// We know it's good, so don't make the iterator match this path
// against the pattern again. In particular, it can't match
// . or .. globs since these never show up as path components.
todo.push((next_path, -1 as uint));
} else {
fill_todo(todo, patterns, idx + 1, &next_path, options);
}
};
let pattern = &patterns[idx];
match pattern_as_str(pattern) {
Some(s) => {
// This pattern component doesn't have any metacharacters, so we
// don't need to read the current directory to know where to
// continue. So instead of passing control back to the iterator,
// we can just check for that one entry and potentially recurse
// right away.
let special = "." == s.as_slice() || ".." == s.as_slice();
let next_path = path.join(s.as_slice());
if (special && path.is_dir()) || (!special && next_path.exists()) {
add(todo, next_path);
}
},
None => {
match list_dir_sorted(path) {
Some(entries) => {
todo.extend(entries.move_iter().map(|x|(x, idx)));
// Matching the special directory entries . and .. that refer to
// the current and parent directory respectively requires that
// the pattern has a leading dot, even if the `MatchOptions` field
// `require_literal_leading_dot` is not set.
if pattern.tokens.len() > 0 && pattern.tokens.get(0) == &Char('.') {
for &special in [".", ".."].iter() {
if pattern.matches_with(special, options) {
add(todo, path.join(special));
}
}
}
}
None => {}
}
}
}
}
fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
let mut cs = Vec::new();
let mut i = 0;
while i < s.len() {
if i + 3 <= s.len() && s[i + 1] == '-' {
cs.push(CharRange(s[i], s[i + 2]));
i += 3;
} else {
cs.push(SingleChar(s[i]));
i += 1;
}
}
cs
}
fn in_char_specifiers(specifiers: &[CharSpecifier], c: char, options: MatchOptions) -> bool {
for &specifier in specifiers.iter() {
match specifier {
SingleChar(sc) => {
if chars_eq(c, sc, options.case_sensitive) {
return true;
}
}
CharRange(start, end) => {
// FIXME: work with non-ascii chars properly (issue #1347)
if !options.case_sensitive && c.is_ascii() && start.is_ascii() && end.is_ascii() {
let start = start.to_ascii().to_lowercase();
let end = end.to_ascii().to_lowercase();
let start_up = start.to_uppercase();
let end_up = end.to_uppercase();
// only allow case insensitive matching when
// both start and end are within a-z or A-Z
if start != start_up && end != end_up {
let start = start.to_char();
let end = end.to_char();
let c = c.to_ascii().to_lowercase().to_char();
if c >= start && c <= end {
return true;
}
}
}
if c >= start && c <= end {
return true;
}
}
}
}
false
}
/// A helper function to determine if two chars are (possibly case-insensitively) equal.
fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool {
if cfg!(windows) && path::windows::is_sep(a) && path::windows::is_sep(b) {
true
} else if !case_sensitive && a.is_ascii() && b.is_ascii() {
// FIXME: work with non-ascii chars properly (issue #1347)
a.to_ascii().eq_ignore_case(b.to_ascii())
} else {
a == b
}
}
/**
* Configuration options to modify the behaviour of `Pattern::matches_with(..)`
*/
#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct MatchOptions {
/**
* Whether or not patterns should be matched in a case-sensitive manner. This
* currently only considers upper/lower case relationships between ASCII characters,
* but in future this might be extended to work with Unicode.
*/
case_sensitive: bool,
/**
* If this is true then path-component separator characters (e.g. `/` on Posix)
* must be matched by a literal `/`, rather than by `*` or `?` or `[...]`
*/
require_literal_separator: bool,
/**
* If this is true then paths that contain components that start with a `.` will
* not match unless the `.` appears literally in the pattern: `*`, `?` or `[...]`
* will not match. This is useful because such files are conventionally considered
* hidden on Unix systems and it might be desirable to skip them when listing files.
*/
require_literal_leading_dot: bool
}
impl MatchOptions {
/**
* Constructs a new `MatchOptions` with default field values. This is used
* when calling functions that do not take an explicit `MatchOptions` parameter.
*
* This function always returns this value:
*
* ```rust,ignore
* MatchOptions {
* case_sensitive: true,
* require_literal_separator: false.
* require_literal_leading_dot: false
* }
* ```
*/
pub fn new() -> MatchOptions {
MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false
}
}
}
#[cfg(test)]
mod test {
use std::os;
use super::{glob, Pattern, MatchOptions};
#[test]
fn test_absolute_pattern() {
// assume that the filesystem is not empty!
assert!(glob("/*").next().is_some());
assert!(glob("//").next().is_some());
// check windows absolute paths with host/device components
let root_with_device = os::getcwd().root_path().unwrap().join("*");
// FIXME (#9639): This needs to handle non-utf8 paths
assert!(glob(root_with_device.as_str().unwrap()).next().is_some());
}
#[test]
fn test_wildcard_optimizations() {
assert!(Pattern::new("a*b").matches("a___b"));
assert!(Pattern::new("a**b").matches("a___b"));
assert!(Pattern::new("a***b").matches("a___b"));
assert!(Pattern::new("a*b*c").matches("abc"));
assert!(!Pattern::new("a*b*c").matches("abcd"));
assert!(Pattern::new("a*b*c").matches("a_b_c"));
assert!(Pattern::new("a*b*c").matches("a___b___c"));
assert!(Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabc"));
assert!(!Pattern::new("abc*abc*abc").matches("abcabcabcabcabcabcabca"));
assert!(Pattern::new("a*a*a*a*a*a*a*a*a").matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
assert!(Pattern::new("a*b[xyz]c*d").matches("abxcdbxcddd"));
}
#[test]
fn test_lots_of_files() {
// this is a good test because it touches lots of differently named files
glob("/*/*/*/*").skip(10000).next();
}
#[test]
fn test_range_pattern() {
let pat = Pattern::new("a[0-9]b");
for i in range(0u, 10) {
assert!(pat.matches(format!("a{}b", i).as_slice()));
}
assert!(!pat.matches("a_b"));
let pat = Pattern::new("a[!0-9]b");
for i in range(0u, 10) {
assert!(!pat.matches(format!("a{}b", i).as_slice()));
}
assert!(pat.matches("a_b"));
let pats = ["[a-z123]", "[1a-z23]", "[123a-z]"];
for &p in pats.iter() {
let pat = Pattern::new(p);
for c in "abcdefghijklmnopqrstuvwxyz".chars() {
assert!(pat.matches(c.to_str().as_slice()));
}
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
let options = MatchOptions {case_sensitive: false, .. MatchOptions::new()};
assert!(pat.matches_with(c.to_str().as_slice(), options));
}
assert!(pat.matches("1"));
assert!(pat.matches("2"));
assert!(pat.matches("3"));
}
let pats = ["[abc-]", "[-abc]", "[a-c-]"];
for &p in pats.iter() {
let pat = Pattern::new(p);
assert!(pat.matches("a"));
assert!(pat.matches("b"));
assert!(pat.matches("c"));
assert!(pat.matches("-"));
assert!(!pat.matches("d"));
}
let pat = Pattern::new("[2-1]");
assert!(!pat.matches("1"));
assert!(!pat.matches("2"));
assert!(Pattern::new("[-]").matches("-"));
assert!(!Pattern::new("[!-]").matches("-"));
}
#[test]
fn test_unclosed_bracket() {
// unclosed `[` should be treated literally
assert!(Pattern::new("abc[def").matches("abc[def"));
assert!(Pattern::new("abc[!def").matches("abc[!def"));
assert!(Pattern::new("abc[").matches("abc["));
assert!(Pattern::new("abc[!").matches("abc[!"));
assert!(Pattern::new("abc[d").matches("abc[d"));
assert!(Pattern::new("abc[!d").matches("abc[!d"));
assert!(Pattern::new("abc[]").matches("abc[]"));
assert!(Pattern::new("abc[!]").matches("abc[!]"));
}
#[test]
fn test_pattern_matches() {
let txt_pat = Pattern::new("*hello.txt");
assert!(txt_pat.matches("hello.txt"));
assert!(txt_pat.matches("gareth_says_hello.txt"));
assert!(txt_pat.matches("some/path/to/hello.txt"));
assert!(txt_pat.matches("some\\path\\to\\hello.txt"));
assert!(txt_pat.matches("/an/absolute/path/to/hello.txt"));
assert!(!txt_pat.matches("hello.txt-and-then-some"));
assert!(!txt_pat.matches("goodbye.txt"));
let dir_pat = Pattern::new("*some/path/to/hello.txt");
assert!(dir_pat.matches("some/path/to/hello.txt"));
assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt"));
assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some"));
assert!(!dir_pat.matches("some/other/path/to/hello.txt"));
}
#[test]
fn test_pattern_escape() {
let s = "_[_]_?_*_!_";
assert_eq!(Pattern::escape(s), "_[[]_[]]_[?]_[*]_!_".to_string());
assert!(Pattern::new(Pattern::escape(s).as_slice()).matches(s));
}
#[test]
fn test_pattern_matches_case_insensitive() {
let pat = Pattern::new("aBcDeFg");
let options = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false
};
assert!(pat.matches_with("aBcDeFg", options));
assert!(pat.matches_with("abcdefg", options));
assert!(pat.matches_with("ABCDEFG", options));
assert!(pat.matches_with("AbCdEfG", options));
}
#[test]
fn test_pattern_matches_case_insensitive_range() {
let pat_within = Pattern::new("[a]");
let pat_except = Pattern::new("[!a]");
let options_case_insensitive = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false
};
let options_case_sensitive = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false
};
assert!(pat_within.matches_with("a", options_case_insensitive));
assert!(pat_within.matches_with("A", options_case_insensitive));
assert!(!pat_within.matches_with("A", options_case_sensitive));
assert!(!pat_except.matches_with("a", options_case_insensitive));
assert!(!pat_except.matches_with("A", options_case_insensitive));
assert!(pat_except.matches_with("A", options_case_sensitive));
}
#[test]
fn test_pattern_matches_require_literal_separator() {
let options_require_literal = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false
};
let options_not_require_literal = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false
};
assert!(Pattern::new("abc/def").matches_with("abc/def", options_require_literal));
assert!(!Pattern::new("abc?def").matches_with("abc/def", options_require_literal));
assert!(!Pattern::new("abc*def").matches_with("abc/def", options_require_literal));
assert!(!Pattern::new("abc[/]def").matches_with("abc/def", options_require_literal));
assert!(Pattern::new("abc/def").matches_with("abc/def", options_not_require_literal));
assert!(Pattern::new("abc?def").matches_with("abc/def", options_not_require_literal));
assert!(Pattern::new("abc*def").matches_with("abc/def", options_not_require_literal));
assert!(Pattern::new("abc[/]def").matches_with("abc/def", options_not_require_literal));
}
#[test]
fn test_pattern_matches_require_literal_leading_dot() {
let options_require_literal_leading_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: true
};
let options_not_require_literal_leading_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false
};
let f = |options| Pattern::new("*.txt").matches_with(".hello.txt", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(!f(options_require_literal_leading_dot));
let f = |options| Pattern::new(".*.*").matches_with(".hello.txt", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(f(options_require_literal_leading_dot));
let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/.ccc", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(!f(options_require_literal_leading_dot));
let f = |options| Pattern::new("aaa/bbb/*").matches_with("aaa/bbb/c.c.c.", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(f(options_require_literal_leading_dot));
let f = |options| Pattern::new("aaa/bbb/.*").matches_with("aaa/bbb/.ccc", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(f(options_require_literal_leading_dot));
let f = |options| Pattern::new("aaa/?bbb").matches_with("aaa/.bbb", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(!f(options_require_literal_leading_dot));
let f = |options| Pattern::new("aaa/[.]bbb").matches_with("aaa/.bbb", options);
assert!(f(options_not_require_literal_leading_dot));
assert!(!f(options_require_literal_leading_dot));
}
#[test]
fn test_matches_path() {
// on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this
// tests that / and \ are considered equivalent on windows
assert!(Pattern::new("a/b").matches_path(&Path::new("a/b")));
}
}