about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-04-09 14:11:56 -0700
committerbors <bors@rust-lang.org>2014-04-09 14:11:56 -0700
commite2c84a78b4a3e95ea0def29172022ef4cf695958 (patch)
tree793ca5b48afeae1aaab984a2956d16670930a697
parent8801d891c4674f335fcdc67cb34237902d89a5ec (diff)
parent1700f359bc5d6b8086194e3cc0f3698666dd41a4 (diff)
downloadrust-e2c84a78b4a3e95ea0def29172022ef4cf695958.tar.gz
rust-e2c84a78b4a3e95ea0def29172022ef4cf695958.zip
auto merge of #13383 : ben0x539/rust/glob-dots, r=brson
Fixes #12930.
-rw-r--r--src/libglob/lib.rs106
-rw-r--r--src/test/run-pass/glob-std.rs22
2 files changed, 111 insertions, 17 deletions
diff --git a/src/libglob/lib.rs b/src/libglob/lib.rs
index 5065fed0129..d19924da5be 100644
--- a/src/libglob/lib.rs
+++ b/src/libglob/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// 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.
 //
@@ -43,6 +43,7 @@ use std::path::is_sep;
 pub struct Paths {
     root: Path,
     dir_patterns: Vec<Pattern>,
+    require_dir: bool,
     options: MatchOptions,
     todo: Vec<(Path,uint)>,
 }
@@ -51,7 +52,7 @@ pub struct Paths {
 /// Return an iterator that produces all the Paths that match the given pattern,
 /// which may be absolute or relative to the current working directory.
 ///
-/// is method uses the default match options and is equivalent to calling
+/// 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.
 ///
@@ -106,6 +107,7 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
             return Paths {
                 root: root,
                 dir_patterns: Vec::new(),
+                require_dir: false,
                 options: options,
                 todo: Vec::new(),
             };
@@ -117,13 +119,21 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Paths {
     let dir_patterns = pattern.slice_from(cmp::min(root_len, pattern.len()))
                        .split_terminator(is_sep)
                        .map(|s| Pattern::new(s))
-                       .collect();
+                       .collect::<Vec<Pattern>>();
+    let require_dir = pattern.chars().next_back().map(is_sep) == Some(true);
 
-    let todo = list_dir_sorted(&root).move_iter().map(|x|(x,0u)).collect();
+    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 {
         root: root,
         dir_patterns: dir_patterns,
+        require_dir: require_dir,
         options: options,
         todo: todo,
     }
@@ -138,6 +148,12 @@ impl Iterator<Path> for Paths {
             }
 
             let (path,idx) = self.todo.pop().unwrap();
+            // idx -1: was already checked by fill_todo, maybe path was '.' or
+            // '..' that we can't match here because of normalization.
+            if idx == -1 as uint {
+                if self.require_dir && !path.is_dir() { continue; }
+                return Some(path);
+            }
             let ref pattern = *self.dir_patterns.get(idx);
 
             if pattern.matches_with(match path.filename_str() {
@@ -152,9 +168,13 @@ impl Iterator<Path> for Paths {
                 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
-                    return Some(path);
+
+                    if !self.require_dir || path.is_dir() {
+                        return Some(path);
+                    }
                 } else {
-                    self.todo.extend(list_dir_sorted(&path).move_iter().map(|x|(x,idx+1)));
+                    fill_todo(&mut self.todo, self.dir_patterns.as_slice(),
+                              idx + 1, &path, self.options);
                 }
             }
         }
@@ -162,13 +182,13 @@ impl Iterator<Path> for Paths {
 
 }
 
-fn list_dir_sorted(path: &Path) -> Vec<Path> {
+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()));
-            children.move_iter().collect()
+            Some(children.move_iter().collect())
         }
-        Err(..) => Vec::new()
+        Err(..) => None
     }
 }
 
@@ -435,6 +455,72 @@ impl Pattern {
 
 }
 
+// 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<~str> {
+        let mut s = ~"";
+        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 || ".." == s;
+            let next_path = path.join(s);
+            if (special && path.is_dir()) || (!special && next_path.exists()) {
+                add(todo, next_path);
+            }
+        },
+        None => {
+            match list_dir_sorted(path) {
+                Some(entries) => {
+                    todo.extend(entries.move_iter().map(|x|(x, idx)));
+
+                    // Matching the special directory entries . and .. that refer to
+                    // the current and parent directory respectively requires that
+                    // the pattern has a leading dot, even if the `MatchOptions` field
+                    // `require_literal_leading_dot` is not set.
+                    if pattern.tokens.len() > 0 && pattern.tokens.get(0) == &Char('.') {
+                        for &special in [".", ".."].iter() {
+                            if pattern.matches_with(special, options) {
+                                add(todo, path.join(special));
+                            }
+                        }
+                    }
+                }
+                None => {}
+            }
+        }
+    }
+}
+
 fn parse_char_specifiers(s: &[char]) -> Vec<CharSpecifier> {
     let mut cs = Vec::new();
     let mut i = 0;
@@ -567,7 +653,7 @@ mod test {
     fn test_absolute_pattern() {
         // assume that the filesystem is not empty!
         assert!(glob("/*").next().is_some());
-        assert!(glob("//").next().is_none());
+        assert!(glob("//").next().is_some());
 
         // check windows absolute paths with host/device components
         let root_with_device = os::getcwd().root_path().unwrap().join("*");
diff --git a/src/test/run-pass/glob-std.rs b/src/test/run-pass/glob-std.rs
index a19a8a1c8ec..2918d5776f0 100644
--- a/src/test/run-pass/glob-std.rs
+++ b/src/test/run-pass/glob-std.rs
@@ -10,13 +10,12 @@
 
 // ignore-win32 TempDir may cause IoError on windows: #10462
 
-#[feature(macro_rules)];
+#![feature(macro_rules)]
 
 extern crate glob;
 
 use glob::glob;
-use std::unstable::finally::Finally;
-use std::{os, unstable};
+use std::os;
 use std::io;
 use std::io::TempDir;
 
@@ -29,9 +28,9 @@ macro_rules! assert_eq ( ($e1:expr, $e2:expr) => (
 pub fn main() {
     fn mk_file(path: &str, directory: bool) {
         if directory {
-            io::fs::mkdir(&Path::new(path), io::UserRWX);
+            io::fs::mkdir(&Path::new(path), io::UserRWX).unwrap();
         } else {
-            io::File::create(&Path::new(path));
+            io::File::create(&Path::new(path)).unwrap();
         }
     }
 
@@ -72,8 +71,8 @@ pub fn main() {
     mk_file("xyz/z", false);
 
     assert_eq!(glob_vec(""), Vec::new());
-    assert_eq!(glob_vec("."), Vec::new());
-    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")));
@@ -131,6 +130,15 @@ pub fn main() {
         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")));