Skip to content

Commit 4051bd9

Browse files
committed
libglob: allow "." and ".." to be matched
... also don't read the whole directory if the glob for that path component doesn't contain any metacharacters. Patterns like `../*.jpg` will work now, and `.*` will match both `.` and `..` to be consistent with shell expansion. As before: Just `*` still won't match `.` and `..`, while it will still match dotfiles like `.git` by default.
1 parent f1f5056 commit 4051bd9

File tree

2 files changed

+98
-16
lines changed

2 files changed

+98
-16
lines changed

src/libglob/lib.rs

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
22
// file at the top-level directory of this distribution and at
33
// http://rust-lang.org/COPYRIGHT.
44
//
@@ -51,7 +51,7 @@ pub struct Paths {
5151
/// Return an iterator that produces all the Paths that match the given pattern,
5252
/// which may be absolute or relative to the current working directory.
5353
///
54-
/// is method uses the default match options and is equivalent to calling
54+
/// This method uses the default match options and is equivalent to calling
5555
/// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you
5656
/// want to use non-default match options.
5757
///
@@ -117,9 +117,15 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
117117
let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
118118
.split_terminator(is_sep)
119119
.map(|s| Pattern::new(s))
120-
.collect();
120+
.collect::<Vec<Pattern>>();
121121

122-
let todo = list_dir_sorted(&root).move_iter().map(|x|(x,0u)).collect();
122+
let mut todo = Vec::new();
123+
if dir_patterns.len() > 0 {
124+
// Shouldn't happen, but we're using -1 as a special index.
125+
assert!(dir_patterns.len() < -1 as uint);
126+
127+
fill_todo(&mut todo, dir_patterns.as_slice(), 0, &root, options);
128+
}
123129

124130
Paths {
125131
root: root,
@@ -138,6 +144,9 @@ impl Iterator<Path> for Paths {
138144
}
139145

140146
let (path,idx) = self.todo.pop().unwrap();
147+
// idx -1: was already checked by fill_todo, maybe path was '.' or
148+
// '..' that we can't match here because of normalization.
149+
if idx == -1 as uint { return Some(path); }
141150
let ref pattern = *self.dir_patterns.get(idx);
142151

143152
if pattern.matches_with(match path.filename_str() {
@@ -154,21 +163,22 @@ impl Iterator<Path> for Paths {
154163
// so we don't need to check the children
155164
return Some(path);
156165
} else {
157-
self.todo.extend(list_dir_sorted(&path).move_iter().map(|x|(x,idx+1)));
166+
fill_todo(&mut self.todo, self.dir_patterns.as_slice(),
167+
idx + 1, &path, self.options);
158168
}
159169
}
160170
}
161171
}
162172

163173
}
164174

165-
fn list_dir_sorted(path: &Path) -> Vec<Path> {
175+
fn list_dir_sorted(path: &Path) -> Option<Vec<Path>> {
166176
match fs::readdir(path) {
167177
Ok(mut children) => {
168178
children.sort_by(|p1, p2| p2.filename().cmp(&p1.filename()));
169-
children.move_iter().collect()
179+
Some(children.move_iter().collect())
170180
}
171-
Err(..) => Vec::new()
181+
Err(..) => None
172182
}
173183
}
174184

@@ -435,6 +445,72 @@ impl Pattern {
435445

436446
}
437447

448+
// Fills `todo` with paths under `path` to be matched by `patterns[idx]`,
449+
// special-casing patterns to match `.` and `..`, and avoiding `readdir()`
450+
// calls when there are no metacharacters in the pattern.
451+
fn fill_todo(todo: &mut Vec<(Path, uint)>, patterns: &[Pattern], idx: uint, path: &Path,
452+
options: MatchOptions) {
453+
// convert a pattern that's just many Char(_) to a string
454+
fn pattern_as_str(pattern: &Pattern) -> Option<~str> {
455+
let mut s = ~"";
456+
for token in pattern.tokens.iter() {
457+
match *token {
458+
Char(c) => s.push_char(c),
459+
_ => return None
460+
}
461+
}
462+
return Some(s);
463+
}
464+
465+
let add = |todo: &mut Vec<_>, next_path: Path| {
466+
if idx + 1 == patterns.len() {
467+
// We know it's good, so don't make the iterator match this path
468+
// against the pattern again. In particular, it can't match
469+
// . or .. globs since these never show up as path components.
470+
todo.push((next_path, -1 as uint));
471+
} else {
472+
fill_todo(todo, patterns, idx + 1, &next_path, options);
473+
}
474+
};
475+
476+
let pattern = &patterns[idx];
477+
478+
match pattern_as_str(pattern) {
479+
Some(s) => {
480+
// This pattern component doesn't have any metacharacters, so we
481+
// don't need to read the current directory to know where to
482+
// continue. So instead of passing control back to the iterator,
483+
// we can just check for that one entry and potentially recurse
484+
// right away.
485+
let special = "." == s || ".." == s;
486+
let next_path = path.join(s);
487+
if (special && path.is_dir()) || (!special && next_path.exists()) {
488+
add(todo, next_path);
489+
}
490+
},
491+
None => {
492+
match list_dir_sorted(path) {
493+
Some(entries) => {
494+
todo.extend(entries.move_iter().map(|x|(x, idx)));
495+
496+
// Matching the special directory entries . and .. that refer to
497+
// the current and parent directory respectively requires that
498+
// the pattern has a leading dot, even if the `MatchOptions` field
499+
// `require_literal_leading_dot` is not set.
500+
if pattern.tokens.len() > 0 && pattern.tokens.get(0) == &Char('.') {
501+
for &special in [".", ".."].iter() {
502+
if pattern.matches_with(special, options) {
503+
add(todo, path.join(special));
504+
}
505+
}
506+
}
507+
}
508+
None => {}
509+
}
510+
}
511+
}
512+
}
513+
438514
fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
439515
let mut cs = Vec::new();
440516
let mut i = 0;
@@ -567,7 +643,7 @@ mod test {
567643
fn test_absolute_pattern() {
568644
// assume that the filesystem is not empty!
569645
assert!(glob("/*").next().is_some());
570-
assert!(glob("//").next().is_none());
646+
assert!(glob("//").next().is_some());
571647

572648
// check windows absolute paths with host/device components
573649
let root_with_device = os::getcwd().root_path().unwrap().join("*");

src/test/run-pass/glob-std.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
// ignore-fast check-fast doesn't like 'extern crate extra'
1212
// ignore-win32 TempDir may cause IoError on windows: #10462
1313

14-
#[feature(macro_rules)];
14+
#![feature(macro_rules)]
1515

1616
extern crate glob;
1717

1818
use glob::glob;
19-
use std::unstable::finally::Finally;
20-
use std::{os, unstable};
19+
use std::os;
2120
use std::io;
2221
use std::io::TempDir;
2322

@@ -30,9 +29,9 @@ macro_rules! assert_eq ( ($e1:expr, $e2:expr) => (
3029
pub fn main() {
3130
fn mk_file(path: &str, directory: bool) {
3231
if directory {
33-
io::fs::mkdir(&Path::new(path), io::UserRWX);
32+
io::fs::mkdir(&Path::new(path), io::UserRWX).unwrap();
3433
} else {
35-
io::File::create(&Path::new(path));
34+
io::File::create(&Path::new(path)).unwrap();
3635
}
3736
}
3837

@@ -73,8 +72,8 @@ pub fn main() {
7372
mk_file("xyz/z", false);
7473

7574
assert_eq!(glob_vec(""), Vec::new());
76-
assert_eq!(glob_vec("."), Vec::new());
77-
assert_eq!(glob_vec(".."), Vec::new());
75+
assert_eq!(glob_vec("."), vec!(os::getcwd()));
76+
assert_eq!(glob_vec(".."), vec!(os::getcwd().join("..")));
7877

7978
assert_eq!(glob_vec("aaa"), vec!(abs_path("aaa")));
8079
assert_eq!(glob_vec("aaa/"), vec!(abs_path("aaa")));
@@ -132,6 +131,13 @@ pub fn main() {
132131
abs_path("aaa/tomato/tomato.txt"),
133132
abs_path("aaa/tomato/tomoto.txt")));
134133

134+
assert_eq!(glob_vec("./aaa"), vec!(abs_path("aaa")));
135+
assert_eq!(glob_vec("./*"), glob_vec("*"));
136+
assert_eq!(glob_vec("*/..").pop().unwrap(), abs_path("."));
137+
assert_eq!(glob_vec("aaa/../bbb"), vec!(abs_path("bbb")));
138+
assert_eq!(glob_vec("nonexistent/../bbb"), Vec::new());
139+
assert_eq!(glob_vec("aaa/tomato/tomato.txt/.."), Vec::new());
140+
135141
assert_eq!(glob_vec("aa[a]"), vec!(abs_path("aaa")));
136142
assert_eq!(glob_vec("aa[abc]"), vec!(abs_path("aaa")));
137143
assert_eq!(glob_vec("a[bca]a"), vec!(abs_path("aaa")));

0 commit comments

Comments
 (0)