Replace os::glob with extra::glob, which is written in rust,
fixing issue #6100.
This commit is contained in:
parent
d84a7b5ae3
commit
193a1c8af6
3 changed files with 821 additions and 84 deletions
|
@ -85,6 +85,7 @@ pub mod getopts;
|
|||
pub mod json;
|
||||
pub mod md4;
|
||||
pub mod tempfile;
|
||||
pub mod glob;
|
||||
pub mod term;
|
||||
pub mod time;
|
||||
pub mod arena;
|
||||
|
|
820
src/libextra/glob.rs
Normal file
820
src/libextra/glob.rs
Normal file
|
@ -0,0 +1,820 @@
|
|||
// Copyright 2013 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 `GlobIterator`
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use std::{os, path, util};
|
||||
|
||||
use sort;
|
||||
|
||||
/**
|
||||
* An iterator that yields Paths from the filesystem that match a particular
|
||||
* pattern - see the `glob` function for more details.
|
||||
*/
|
||||
pub struct GlobIterator {
|
||||
priv root: Path,
|
||||
priv dir_patterns: ~[Pattern],
|
||||
priv options: MatchOptions,
|
||||
priv todo: ~[Path]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* for path in glob("/media/pictures/*.jpg") {
|
||||
* println(path.to_str());
|
||||
* }
|
||||
* ~~~
|
||||
*
|
||||
* The above code will print:
|
||||
*
|
||||
* ~~~
|
||||
* /media/pictures/kittens.jpg
|
||||
* /media/pictures/puppies.jpg
|
||||
* ~~~
|
||||
*/
|
||||
pub fn glob(pattern: &str) -> GlobIterator {
|
||||
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) -> GlobIterator {
|
||||
|
||||
// note that this relies on the glob meta characters not
|
||||
// having any special meaning in actual pathnames
|
||||
let path = Path(pattern);
|
||||
let dir_patterns = path.components.map(|s| Pattern::new(*s));
|
||||
|
||||
let root = if path.is_absolute() {
|
||||
Path {components: ~[], .. path} // preserve windows path host/device
|
||||
} else {
|
||||
os::getcwd()
|
||||
};
|
||||
let todo = list_dir_sorted(&root);
|
||||
|
||||
GlobIterator {
|
||||
root: root,
|
||||
dir_patterns: dir_patterns,
|
||||
options: options,
|
||||
todo: todo,
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator<Path> for GlobIterator {
|
||||
|
||||
fn next(&mut self) -> Option<Path> {
|
||||
loop {
|
||||
if self.dir_patterns.is_empty() || self.todo.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = self.todo.pop();
|
||||
let pattern_index = path.components.len() - self.root.components.len() - 1;
|
||||
let ref pattern = self.dir_patterns[pattern_index];
|
||||
|
||||
if pattern.matches_with(*path.components.last(), self.options) {
|
||||
|
||||
if pattern_index == 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
|
||||
return Some(path);
|
||||
} else {
|
||||
self.todo.push_all(list_dir_sorted(&path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn list_dir_sorted(path: &Path) -> ~[Path] {
|
||||
let mut children = os::list_dir_path(path);
|
||||
sort::quick_sort(children, |p1, p2| p2.components.last() <= p1.components.last());
|
||||
children
|
||||
}
|
||||
|
||||
/**
|
||||
* A compiled Unix shell style pattern.
|
||||
*/
|
||||
#[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, IterBytes, Zero)]
|
||||
pub struct Pattern {
|
||||
priv tokens: ~[PatternToken]
|
||||
}
|
||||
|
||||
#[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, IterBytes)]
|
||||
enum PatternToken {
|
||||
Char(char),
|
||||
AnyChar,
|
||||
AnySequence,
|
||||
AnyWithin(~[char]),
|
||||
AnyExcept(~[char])
|
||||
}
|
||||
|
||||
#[deriving(Eq)]
|
||||
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 `]`.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.iter().to_owned_vec();
|
||||
let mut tokens = ~[];
|
||||
let mut i = 0;
|
||||
|
||||
while i < chars.len() {
|
||||
match chars[i] {
|
||||
'?' => {
|
||||
tokens.push(AnyChar);
|
||||
i += 1;
|
||||
}
|
||||
'*' => {
|
||||
// *, **, ***, ****, ... are all equivalent
|
||||
while i < chars.len() && chars[i] == '*' {
|
||||
i += 1;
|
||||
}
|
||||
tokens.push(AnySequence);
|
||||
}
|
||||
'[' => {
|
||||
|
||||
if i <= chars.len() - 4 && chars[i + 1] == '!' {
|
||||
match chars.slice_from(i + 3).position_elem(&']') {
|
||||
None => (),
|
||||
Some(j) => {
|
||||
tokens.push(AnyExcept(chars.slice(i + 2, i + 3 + j).to_owned()));
|
||||
i += j + 4;
|
||||
loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if i <= chars.len() - 3 && chars[i + 1] != '!' {
|
||||
match chars.slice_from(i + 2).position_elem(&']') {
|
||||
None => (),
|
||||
Some(j) => {
|
||||
tokens.push(AnyWithin(chars.slice(i + 1, i + 2 + j).to_owned()));
|
||||
i += j + 3;
|
||||
loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) -> ~str {
|
||||
let mut escaped = ~"";
|
||||
for c in s.iter() {
|
||||
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}
|
||||
* 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 {
|
||||
self.matches(path.to_str())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
self.matches_with(path.to_str(), options)
|
||||
}
|
||||
|
||||
fn matches_from(&self,
|
||||
mut prev_char: Option<char>,
|
||||
mut file: &str,
|
||||
i: uint,
|
||||
options: MatchOptions) -> MatchResult {
|
||||
|
||||
let require_literal = |c| {
|
||||
(options.require_literal_separator && is_sep(c)) ||
|
||||
(options.require_literal_leading_dot && c == '.'
|
||||
&& is_sep(prev_char.unwrap_or_default('/')))
|
||||
};
|
||||
|
||||
for ti in range(i, self.tokens.len()) {
|
||||
match self.tokens[ti] {
|
||||
AnySequence => {
|
||||
loop {
|
||||
match self.matches_from(prev_char, file, ti + 1, options) {
|
||||
SubPatternDoesntMatch => (), // keep trying
|
||||
m => return m,
|
||||
}
|
||||
|
||||
if file.is_empty() {
|
||||
return EntirePatternDoesntMatch;
|
||||
}
|
||||
|
||||
let (c, next) = file.slice_shift_char();
|
||||
if require_literal(c) {
|
||||
return SubPatternDoesntMatch;
|
||||
}
|
||||
prev_char = Some(c);
|
||||
file = next;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if file.is_empty() {
|
||||
return EntirePatternDoesntMatch;
|
||||
}
|
||||
|
||||
let (c, next) = file.slice_shift_char();
|
||||
let matches = match self.tokens[ti] {
|
||||
AnyChar => {
|
||||
!require_literal(c)
|
||||
}
|
||||
AnyWithin(ref chars) => {
|
||||
!require_literal(c) &&
|
||||
chars.iter()
|
||||
.rposition(|&e| chars_eq(e, c, options.case_sensitive)).is_some()
|
||||
}
|
||||
AnyExcept(ref chars) => {
|
||||
!require_literal(c) &&
|
||||
chars.iter()
|
||||
.rposition(|&e| chars_eq(e, c, options.case_sensitive)).is_none()
|
||||
}
|
||||
Char(c2) => {
|
||||
chars_eq(c, c2, options.case_sensitive)
|
||||
}
|
||||
AnySequence => {
|
||||
util::unreachable()
|
||||
}
|
||||
};
|
||||
if !matches {
|
||||
return SubPatternDoesntMatch;
|
||||
}
|
||||
prev_char = Some(c);
|
||||
file = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if file.is_empty() {
|
||||
Match
|
||||
} else {
|
||||
SubPatternDoesntMatch
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper function to determine if a char is a path separator on the current platform.
|
||||
fn is_sep(c: char) -> bool {
|
||||
if cfg!(windows) {
|
||||
path::windows::is_sep(c)
|
||||
} else {
|
||||
path::posix::is_sep(c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options to modify the behaviour of `Pattern::matches_with(..)`
|
||||
*/
|
||||
#[deriving(Clone, Eq, TotalEq, Ord, TotalOrd, IterBytes, Zero)]
|
||||
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}
|
||||
* 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::{io, os, unstable};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_relative_pattern() {
|
||||
|
||||
fn mk_file(path: &str, directory: bool) {
|
||||
if directory {
|
||||
os::make_dir(&Path(path), 0xFFFF);
|
||||
} else {
|
||||
io::mk_file_writer(&Path(path), [io::Create]);
|
||||
}
|
||||
}
|
||||
|
||||
fn abs_path(path: &str) -> Path {
|
||||
os::getcwd().push_many(Path(path).components)
|
||||
}
|
||||
|
||||
fn glob_vec(pattern: &str) -> ~[Path] {
|
||||
glob(pattern).collect()
|
||||
}
|
||||
|
||||
mk_file("tmp", true);
|
||||
mk_file("tmp/glob-tests", true);
|
||||
|
||||
do unstable::change_dir_locked(&Path("tmp/glob-tests")) {
|
||||
|
||||
mk_file("aaa", true);
|
||||
mk_file("aaa/apple", true);
|
||||
mk_file("aaa/orange", true);
|
||||
mk_file("aaa/tomato", true);
|
||||
mk_file("aaa/tomato/tomato.txt", false);
|
||||
mk_file("aaa/tomato/tomoto.txt", false);
|
||||
mk_file("bbb", true);
|
||||
mk_file("bbb/specials", true);
|
||||
mk_file("bbb/specials/!", false);
|
||||
|
||||
// windows does not allow `*` or `?` characters to exist in filenames
|
||||
if os::consts::FAMILY != os::consts::windows::FAMILY {
|
||||
mk_file("bbb/specials/*", false);
|
||||
mk_file("bbb/specials/?", false);
|
||||
}
|
||||
|
||||
mk_file("bbb/specials/[", false);
|
||||
mk_file("bbb/specials/]", false);
|
||||
mk_file("ccc", true);
|
||||
mk_file("xyz", true);
|
||||
mk_file("xyz/x", false);
|
||||
mk_file("xyz/y", false);
|
||||
mk_file("xyz/z", false);
|
||||
|
||||
assert_eq!(glob_vec(""), ~[]);
|
||||
assert_eq!(glob_vec("."), ~[]);
|
||||
assert_eq!(glob_vec(".."), ~[]);
|
||||
|
||||
assert_eq!(glob_vec("aaa"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aaa/"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("a"), ~[]);
|
||||
assert_eq!(glob_vec("aa"), ~[]);
|
||||
assert_eq!(glob_vec("aaaa"), ~[]);
|
||||
|
||||
assert_eq!(glob_vec("aaa/apple"), ~[abs_path("aaa/apple")]);
|
||||
assert_eq!(glob_vec("aaa/apple/nope"), ~[]);
|
||||
|
||||
// windows should support both / and \ as directory separators
|
||||
if os::consts::FAMILY == os::consts::windows::FAMILY {
|
||||
assert_eq!(glob_vec("aaa\\apple"), ~[abs_path("aaa/apple")]);
|
||||
}
|
||||
|
||||
assert_eq!(glob_vec("???/"), ~[
|
||||
abs_path("aaa"),
|
||||
abs_path("bbb"),
|
||||
abs_path("ccc"),
|
||||
abs_path("xyz")]);
|
||||
|
||||
assert_eq!(glob_vec("aaa/tomato/tom?to.txt"), ~[
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")]);
|
||||
|
||||
assert_eq!(glob_vec("xyz/?"), ~[
|
||||
abs_path("xyz/x"),
|
||||
abs_path("xyz/y"),
|
||||
abs_path("xyz/z")]);
|
||||
|
||||
assert_eq!(glob_vec("a*"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("*a*"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("a*a"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aaa*"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("*aaa"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("*aaa*"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("*a*a*a*"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aaa*/"), ~[abs_path("aaa")]);
|
||||
|
||||
assert_eq!(glob_vec("aaa/*"), ~[
|
||||
abs_path("aaa/apple"),
|
||||
abs_path("aaa/orange"),
|
||||
abs_path("aaa/tomato")]);
|
||||
|
||||
assert_eq!(glob_vec("aaa/*a*"), ~[
|
||||
abs_path("aaa/apple"),
|
||||
abs_path("aaa/orange"),
|
||||
abs_path("aaa/tomato")]);
|
||||
|
||||
assert_eq!(glob_vec("*/*/*.txt"), ~[
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")]);
|
||||
|
||||
assert_eq!(glob_vec("*/*/t[aob]m?to[.]t[!y]t"), ~[
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")]);
|
||||
|
||||
assert_eq!(glob_vec("aa[a]"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aa[abc]"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("a[bca]a"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aa[b]"), ~[]);
|
||||
assert_eq!(glob_vec("aa[xyz]"), ~[]);
|
||||
assert_eq!(glob_vec("aa[]]"), ~[]);
|
||||
|
||||
assert_eq!(glob_vec("aa[!b]"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aa[!bcd]"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("a[!bcd]a"), ~[abs_path("aaa")]);
|
||||
assert_eq!(glob_vec("aa[!a]"), ~[]);
|
||||
assert_eq!(glob_vec("aa[!abc]"), ~[]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[[]"), ~[abs_path("bbb/specials/[")]);
|
||||
assert_eq!(glob_vec("bbb/specials/!"), ~[abs_path("bbb/specials/!")]);
|
||||
assert_eq!(glob_vec("bbb/specials/[]]"), ~[abs_path("bbb/specials/]")]);
|
||||
|
||||
if os::consts::FAMILY != os::consts::windows::FAMILY {
|
||||
assert_eq!(glob_vec("bbb/specials/[*]"), ~[abs_path("bbb/specials/*")]);
|
||||
assert_eq!(glob_vec("bbb/specials/[?]"), ~[abs_path("bbb/specials/?")]);
|
||||
}
|
||||
|
||||
if os::consts::FAMILY == os::consts::windows::FAMILY {
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[![]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!]]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/[")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!!]"), ~[
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
} else {
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[![]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!]]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/[")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!!]"), ~[
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!*]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!?]"), ~[
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")]);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absolute_pattern() {
|
||||
// assume that the filesystem is not empty!
|
||||
assert!(glob("/*").next().is_some());
|
||||
assert!(glob("//").next().is_none());
|
||||
|
||||
// check windows absolute paths with host/device components
|
||||
let root_with_device = (Path {components: ~[], .. os::getcwd()}).to_str() + "*";
|
||||
assert!(glob(root_with_device).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_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), ~"_[[]_[]]_[?]_[*]_!_");
|
||||
assert!(Pattern::new(Pattern::escape(s)).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("a/b").to_str() == "a\\b"), so this
|
||||
// tests that / and \ are considered equivalent on windows
|
||||
assert!(Pattern::new("a/b").matches_path(&Path("a/b")));
|
||||
}
|
||||
}
|
||||
|
|
@ -1318,90 +1318,6 @@ pub fn args() -> ~[~str] {
|
|||
real_args()
|
||||
}
|
||||
|
||||
// FIXME #6100 we should really use an internal implementation of this - using
|
||||
// the POSIX glob functions isn't portable to windows, probably has slight
|
||||
// inconsistencies even where it is implemented, and makes extending
|
||||
// functionality a lot more difficult
|
||||
// FIXME #6101 also provide a non-allocating version - each_glob or so?
|
||||
/// Returns a vector of Path objects that match the given glob pattern
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(target_os = "freebsd")]
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn glob(pattern: &str) -> ~[Path] {
|
||||
#[fixed_stack_segment]; #[inline(never)];
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(target_os = "android")]
|
||||
fn default_glob_t () -> libc::glob_t {
|
||||
libc::glob_t {
|
||||
gl_pathc: 0,
|
||||
gl_pathv: ptr::null(),
|
||||
gl_offs: 0,
|
||||
__unused1: ptr::null(),
|
||||
__unused2: ptr::null(),
|
||||
__unused3: ptr::null(),
|
||||
__unused4: ptr::null(),
|
||||
__unused5: ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn default_glob_t () -> libc::glob_t {
|
||||
libc::glob_t {
|
||||
gl_pathc: 0,
|
||||
__unused1: 0,
|
||||
gl_offs: 0,
|
||||
__unused2: 0,
|
||||
gl_pathv: ptr::null(),
|
||||
__unused3: ptr::null(),
|
||||
__unused4: ptr::null(),
|
||||
__unused5: ptr::null(),
|
||||
__unused6: ptr::null(),
|
||||
__unused7: ptr::null(),
|
||||
__unused8: ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn default_glob_t () -> libc::glob_t {
|
||||
libc::glob_t {
|
||||
gl_pathc: 0,
|
||||
__unused1: 0,
|
||||
gl_offs: 0,
|
||||
__unused2: 0,
|
||||
gl_pathv: ptr::null(),
|
||||
__unused3: ptr::null(),
|
||||
__unused4: ptr::null(),
|
||||
__unused5: ptr::null(),
|
||||
__unused6: ptr::null(),
|
||||
__unused7: ptr::null(),
|
||||
__unused8: ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
let mut g = default_glob_t();
|
||||
do pattern.with_c_str |c_pattern| {
|
||||
unsafe { libc::glob(c_pattern, 0, ptr::null(), &mut g) }
|
||||
};
|
||||
do(|| {
|
||||
let paths = unsafe {
|
||||
vec::raw::from_buf_raw(g.gl_pathv, g.gl_pathc as uint)
|
||||
};
|
||||
do paths.map |&c_str| {
|
||||
Path(unsafe { str::raw::from_c_str(c_str) })
|
||||
}
|
||||
}).finally {
|
||||
unsafe { libc::globfree(&mut g) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a vector of Path objects that match the given glob pattern
|
||||
#[cfg(target_os = "win32")]
|
||||
pub fn glob(_pattern: &str) -> ~[Path] {
|
||||
fail!("glob() is unimplemented on Windows")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
extern {
|
||||
// These functions are in crt_externs.h.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue