mirror of
https://gitee.com/openharmony/third_party_rust_glob
synced 2024-11-27 01:30:24 +00:00
Initial commit.
This commit is contained in:
commit
abb5da2148
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@ -0,0 +1,5 @@
|
||||
install:
|
||||
- curl http://www.rust-lang.org/rustup.sh | sudo sh -
|
||||
script:
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
5
Cargo.toml
Normal file
5
Cargo.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[package]
|
||||
|
||||
name = "glob"
|
||||
version = "0.0.1"
|
||||
authors = ["The Rust Project Developers"]
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
25
LICENSE-MIT
Normal file
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2014 The Rust Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
6
README.md
Normal file
6
README.md
Normal file
@ -0,0 +1,6 @@
|
||||
glob
|
||||
====
|
||||
|
||||
Support for matching file paths against Unix shell style patterns.
|
||||
|
||||
[![Build Status](https://travis-ci.org/rust-lang/glob.svg?branch=master)](https://travis-ci.org/rust-lang/glob)
|
885
src/lib.rs
Normal file
885
src/lib.rs
Normal file
@ -0,0 +1,885 @@
|
||||
// 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_name = "glob"]
|
||||
#![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")]
|
||||
|
||||
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[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[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) => {
|
||||
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[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[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.
|
||||
*/
|
||||
pub 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 `[...]`
|
||||
*/
|
||||
pub 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.
|
||||
*/
|
||||
pub 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_string().as_slice()));
|
||||
}
|
||||
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
|
||||
let options = MatchOptions {case_sensitive: false, .. MatchOptions::new()};
|
||||
assert!(pat.matches_with(c.to_string().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")));
|
||||
}
|
||||
}
|
212
tests/glob-std.rs
Normal file
212
tests/glob-std.rs
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright 2013-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.
|
||||
|
||||
// ignore-windows TempDir may cause IoError on windows: #10462
|
||||
|
||||
#![feature(macro_rules)]
|
||||
|
||||
extern crate glob;
|
||||
|
||||
use glob::glob;
|
||||
use std::os;
|
||||
use std::io;
|
||||
use std::io::TempDir;
|
||||
|
||||
macro_rules! assert_eq ( ($e1:expr, $e2:expr) => (
|
||||
if $e1 != $e2 {
|
||||
fail!("{} != {}", stringify!($e1), stringify!($e2))
|
||||
}
|
||||
) )
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
fn mk_file(path: &str, directory: bool) {
|
||||
if directory {
|
||||
io::fs::mkdir(&Path::new(path), io::UserRWX).unwrap();
|
||||
} else {
|
||||
io::File::create(&Path::new(path)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn abs_path(path: &str) -> Path {
|
||||
os::getcwd().join(&Path::new(path))
|
||||
}
|
||||
|
||||
fn glob_vec(pattern: &str) -> Vec<Path> {
|
||||
glob(pattern).collect()
|
||||
}
|
||||
|
||||
let root = TempDir::new("glob-tests");
|
||||
let root = root.expect("Should have created a temp directory");
|
||||
assert!(os::change_dir(root.path()));
|
||||
|
||||
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 != "windows" {
|
||||
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(""), Vec::new());
|
||||
assert_eq!(glob_vec("."), vec!(os::getcwd()));
|
||||
assert_eq!(glob_vec(".."), vec!(os::getcwd().join("..")));
|
||||
|
||||
assert_eq!(glob_vec("aaa"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aaa/"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("a"), Vec::new());
|
||||
assert_eq!(glob_vec("aa"), Vec::new());
|
||||
assert_eq!(glob_vec("aaaa"), Vec::new());
|
||||
|
||||
assert_eq!(glob_vec("aaa/apple"), vec!(abs_path("aaa/apple")));
|
||||
assert_eq!(glob_vec("aaa/apple/nope"), Vec::new());
|
||||
|
||||
// windows should support both / and \ as directory separators
|
||||
if os::consts::FAMILY == "windows" {
|
||||
assert_eq!(glob_vec("aaa\\apple"), vec!(abs_path("aaa/apple")));
|
||||
}
|
||||
|
||||
assert_eq!(glob_vec("???/"), vec!(
|
||||
abs_path("aaa"),
|
||||
abs_path("bbb"),
|
||||
abs_path("ccc"),
|
||||
abs_path("xyz")));
|
||||
|
||||
assert_eq!(glob_vec("aaa/tomato/tom?to.txt"), vec!(
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")));
|
||||
|
||||
assert_eq!(glob_vec("xyz/?"), vec!(
|
||||
abs_path("xyz/x"),
|
||||
abs_path("xyz/y"),
|
||||
abs_path("xyz/z")));
|
||||
|
||||
assert_eq!(glob_vec("a*"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("*a*"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("a*a"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aaa*"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("*aaa"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("*aaa*"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("*a*a*a*"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aaa*/"), vec!(abs_path("aaa")));
|
||||
|
||||
assert_eq!(glob_vec("aaa/*"), vec!(
|
||||
abs_path("aaa/apple"),
|
||||
abs_path("aaa/orange"),
|
||||
abs_path("aaa/tomato")));
|
||||
|
||||
assert_eq!(glob_vec("aaa/*a*"), vec!(
|
||||
abs_path("aaa/apple"),
|
||||
abs_path("aaa/orange"),
|
||||
abs_path("aaa/tomato")));
|
||||
|
||||
assert_eq!(glob_vec("*/*/*.txt"), vec!(
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")));
|
||||
|
||||
assert_eq!(glob_vec("*/*/t[aob]m?to[.]t[!y]t"), vec!(
|
||||
abs_path("aaa/tomato/tomato.txt"),
|
||||
abs_path("aaa/tomato/tomoto.txt")));
|
||||
|
||||
assert_eq!(glob_vec("./aaa"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("./*"), glob_vec("*"));
|
||||
assert_eq!(glob_vec("*/..").pop().unwrap(), abs_path("."));
|
||||
assert_eq!(glob_vec("aaa/../bbb"), vec!(abs_path("bbb")));
|
||||
assert_eq!(glob_vec("nonexistent/../bbb"), Vec::new());
|
||||
assert_eq!(glob_vec("aaa/tomato/tomato.txt/.."), Vec::new());
|
||||
|
||||
assert_eq!(glob_vec("aaa/tomato/tomato.txt/"), Vec::new());
|
||||
|
||||
assert_eq!(glob_vec("aa[a]"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aa[abc]"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("a[bca]a"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aa[b]"), Vec::new());
|
||||
assert_eq!(glob_vec("aa[xyz]"), Vec::new());
|
||||
assert_eq!(glob_vec("aa[]]"), Vec::new());
|
||||
|
||||
assert_eq!(glob_vec("aa[!b]"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aa[!bcd]"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("a[!bcd]a"), vec!(abs_path("aaa")));
|
||||
assert_eq!(glob_vec("aa[!a]"), Vec::new());
|
||||
assert_eq!(glob_vec("aa[!abc]"), Vec::new());
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[[]"), vec!(abs_path("bbb/specials/[")));
|
||||
assert_eq!(glob_vec("bbb/specials/!"), vec!(abs_path("bbb/specials/!")));
|
||||
assert_eq!(glob_vec("bbb/specials/[]]"), vec!(abs_path("bbb/specials/]")));
|
||||
|
||||
if os::consts::FAMILY != "windows" {
|
||||
assert_eq!(glob_vec("bbb/specials/[*]"), vec!(abs_path("bbb/specials/*")));
|
||||
assert_eq!(glob_vec("bbb/specials/[?]"), vec!(abs_path("bbb/specials/?")));
|
||||
}
|
||||
|
||||
if os::consts::FAMILY == "windows" {
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[![]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!]]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/[")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!!]"), vec!(
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
} else {
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[![]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!]]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/[")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!!]"), vec!(
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!*]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/?"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
assert_eq!(glob_vec("bbb/specials/[!?]"), vec!(
|
||||
abs_path("bbb/specials/!"),
|
||||
abs_path("bbb/specials/*"),
|
||||
abs_path("bbb/specials/["),
|
||||
abs_path("bbb/specials/]")));
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user