Skip to content

Commit e2c84a7

Browse files
committed
auto merge of #13383 : ben0x539/rust/glob-dots, r=brson
Fixes #12930.
2 parents 8801d89 + 1700f35 commit e2c84a7

File tree

2 files changed

+111
-17
lines changed

2 files changed

+111
-17
lines changed

src/libglob/lib.rs

+96-10
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
//
@@ -43,6 +43,7 @@ use std::path::is_sep;
4343
pub struct Paths {
4444
root: Path,
4545
dir_patterns: Vec<Pattern>,
46+
require_dir: bool,
4647
options: MatchOptions,
4748
todo: Vec<(Path,uint)>,
4849
}
@@ -51,7 +52,7 @@ pub struct Paths {
5152
/// Return an iterator that produces all the Paths that match the given pattern,
5253
/// which may be absolute or relative to the current working directory.
5354
///
54-
/// is method uses the default match options and is equivalent to calling
55+
/// This method uses the default match options and is equivalent to calling
5556
/// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you
5657
/// want to use non-default match options.
5758
///
@@ -106,6 +107,7 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
106107
return Paths {
107108
root: root,
108109
dir_patterns: Vec::new(),
110+
require_dir: false,
109111
options: options,
110112
todo: Vec::new(),
111113
};
@@ -117,13 +119,21 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
117119
let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
118120
.split_terminator(is_sep)
119121
.map(|s| Pattern::new(s))
120-
.collect();
122+
.collect::<Vec<Pattern>>();
123+
let require_dir = pattern.chars().next_back().map(is_sep) == Some(true);
121124

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

124133
Paths {
125134
root: root,
126135
dir_patterns: dir_patterns,
136+
require_dir: require_dir,
127137
options: options,
128138
todo: todo,
129139
}
@@ -138,6 +148,12 @@ impl Iterator<Path> for Paths {
138148
}
139149

140150
let (path,idx) = self.todo.pop().unwrap();
151+
// idx -1: was already checked by fill_todo, maybe path was '.' or
152+
// '..' that we can't match here because of normalization.
153+
if idx == -1 as uint {
154+
if self.require_dir && !path.is_dir() { continue; }
155+
return Some(path);
156+
}
141157
let ref pattern = *self.dir_patterns.get(idx);
142158

143159
if pattern.matches_with(match path.filename_str() {
@@ -152,23 +168,27 @@ impl Iterator<Path> for Paths {
152168
if idx == self.dir_patterns.len() - 1 {
153169
// it is not possible for a pattern to match a directory *AND* its children
154170
// so we don't need to check the children
155-
return Some(path);
171+
172+
if !self.require_dir || path.is_dir() {
173+
return Some(path);
174+
}
156175
} else {
157-
self.todo.extend(list_dir_sorted(&path).move_iter().map(|x|(x,idx+1)));
176+
fill_todo(&mut self.todo, self.dir_patterns.as_slice(),
177+
idx + 1, &path, self.options);
158178
}
159179
}
160180
}
161181
}
162182

163183
}
164184

165-
fn list_dir_sorted(path: &Path) -> Vec<Path> {
185+
fn list_dir_sorted(path: &Path) -> Option<Vec<Path>> {
166186
match fs::readdir(path) {
167187
Ok(mut children) => {
168188
children.sort_by(|p1, p2| p2.filename().cmp(&p1.filename()));
169-
children.move_iter().collect()
189+
Some(children.move_iter().collect())
170190
}
171-
Err(..) => Vec::new()
191+
Err(..) => None
172192
}
173193
}
174194

@@ -435,6 +455,72 @@ impl Pattern {
435455

436456
}
437457

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

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

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

+15-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010

1111
// ignore-win32 TempDir may cause IoError on windows: #10462
1212

13-
#[feature(macro_rules)];
13+
#![feature(macro_rules)]
1414

1515
extern crate glob;
1616

1717
use glob::glob;
18-
use std::unstable::finally::Finally;
19-
use std::{os, unstable};
18+
use std::os;
2019
use std::io;
2120
use std::io::TempDir;
2221

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

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

7473
assert_eq!(glob_vec(""), Vec::new());
75-
assert_eq!(glob_vec("."), Vec::new());
76-
assert_eq!(glob_vec(".."), Vec::new());
74+
assert_eq!(glob_vec("."), vec!(os::getcwd()));
75+
assert_eq!(glob_vec(".."), vec!(os::getcwd().join("..")));
7776

7877
assert_eq!(glob_vec("aaa"), vec!(abs_path("aaa")));
7978
assert_eq!(glob_vec("aaa/"), vec!(abs_path("aaa")));
@@ -131,6 +130,15 @@ pub fn main() {
131130
abs_path("aaa/tomato/tomato.txt"),
132131
abs_path("aaa/tomato/tomoto.txt")));
133132

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

0 commit comments

Comments
 (0)