diff --git a/src/lib.rs b/src/lib.rs index f3c72f8..a28a710 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,9 @@ use std::cmp; use std::error::Error; use std::fmt; use std::fs; +use std::fs::DirEntry; use std::io; +use std::ops::Deref; use std::path::{self, Component, Path, PathBuf}; use std::str::FromStr; @@ -96,8 +98,8 @@ pub struct Paths { dir_patterns: Vec, require_dir: bool, options: MatchOptions, - todo: Vec>, - scope: Option, + todo: Vec>, + scope: Option, } /// Return an iterator that produces all the `Path`s that match the given @@ -242,6 +244,7 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Result bool { - fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false) +#[derive(Debug)] +struct PathWrapper { + path: PathBuf, + is_directory: bool, +} + +impl PathWrapper { + fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self { + let is_directory = e + .file_type() + .ok() + .and_then(|file_type| { + // We need to use fs::metadata to resolve the actual path + // if it's a symlink. + if file_type.is_symlink() { + None + } else { + Some(file_type.is_dir()) + } + }) + .or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok()) + .unwrap_or(false); + Self { path, is_directory } + } + fn from_path(path: PathBuf) -> Self { + let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false); + Self { path, is_directory } + } + + fn into_path(self) -> PathBuf { + self.path + } +} + +impl Deref for PathWrapper { + type Target = Path; + + fn deref(&self) -> &Self::Target { + self.path.deref() + } +} + +impl AsRef for PathWrapper { + fn as_ref(&self) -> &Path { + self.path.as_ref() + } } /// An alias for a glob iteration result. @@ -363,10 +410,10 @@ impl Iterator for Paths { // idx -1: was already checked by fill_todo, maybe path was '.' or // '..' that we can't match here because of normalization. if idx == !0 as usize { - if self.require_dir && !is_dir(&path) { + if self.require_dir && !path.is_directory { continue; } - return Some(Ok(path)); + return Some(Ok(path.into_path())); } if self.dir_patterns[idx].is_recursive { @@ -379,7 +426,7 @@ impl Iterator for Paths { next += 1; } - if is_dir(&path) { + if path.is_directory { // the path is a directory, so it's a match // push this directory's contents @@ -394,7 +441,7 @@ impl Iterator for Paths { if next == self.dir_patterns.len() - 1 { // pattern ends in recursive pattern, so return this // directory as a result - return Some(Ok(path)); + return Some(Ok(path.into_path())); } else { // advanced to the next pattern for this path idx = next + 1; @@ -427,8 +474,8 @@ impl Iterator for Paths { // *AND* its children so we don't need to check the // children - if !self.require_dir || is_dir(&path) { - return Some(Ok(path)); + if !self.require_dir || path.is_directory { + return Some(Ok(path.into_path())); } } else { fill_todo( @@ -817,10 +864,10 @@ impl Pattern { // special-casing patterns to match `.` and `..`, and avoiding `readdir()` // calls when there are no metacharacters in the pattern. fn fill_todo( - todo: &mut Vec>, + todo: &mut Vec>, patterns: &[Pattern], idx: usize, - path: &Path, + path: &PathWrapper, options: MatchOptions, ) { // convert a pattern that's just many Char(_) to a string @@ -836,7 +883,7 @@ fn fill_todo( Some(s) } - let add = |todo: &mut Vec<_>, next_path: PathBuf| { + let add = |todo: &mut Vec<_>, next_path: PathWrapper| { 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 @@ -848,8 +895,8 @@ fn fill_todo( }; let pattern = &patterns[idx]; - let is_dir = is_dir(path); - let curdir = path == Path::new("."); + let is_dir = path.is_directory; + let curdir = path.as_ref() == Path::new("."); match pattern_as_str(pattern) { Some(s) => { // This pattern component doesn't have any metacharacters, so we @@ -863,6 +910,7 @@ fn fill_todo( } else { path.join(&s) }; + let next_path = PathWrapper::from_path(next_path); if (special && is_dir) || (!special && (fs::metadata(&next_path).is_ok() @@ -875,11 +923,12 @@ fn fill_todo( let dirs = fs::read_dir(path).and_then(|d| { d.map(|e| { e.map(|e| { - if curdir { + let path = if curdir { PathBuf::from(e.path().file_name().unwrap()) } else { e.path() - } + }; + PathWrapper::from_dir_entry(path, e) }) }) .collect::, _>>() @@ -887,7 +936,8 @@ fn fill_todo( match dirs { Ok(mut children) => { if options.require_literal_leading_dot { - children.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with(".")); + children + .retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with(".")); } children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name())); todo.extend(children.into_iter().map(|x| Ok((x, idx)))); @@ -900,7 +950,7 @@ fn fill_todo( if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') { for &special in &[".", ".."] { if pattern.matches_with(special, options) { - add(todo, path.join(special)); + add(todo, PathWrapper::from_path(path.join(special))); } } } diff --git a/tests/glob-std.rs b/tests/glob-std.rs index 3d5e4cc..4466413 100644 --- a/tests/glob-std.rs +++ b/tests/glob-std.rs @@ -44,6 +44,19 @@ fn main() { } } + fn mk_symlink_dir(original: &str, link: &str) { + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + symlink(original, link).unwrap(); + } + #[cfg(windows)] + { + use std::os::windows::fs::symlink_dir; + symlink_dir(original, link).unwrap(); + } + } + fn glob_vec(pattern: &str) -> Vec { glob(pattern).unwrap().map(|r| r.unwrap()).collect() } @@ -99,6 +112,22 @@ fn main() { mk_file("r/three", true); mk_file("r/three/c.md", false); + mk_file("dirsym", true); + mk_symlink_dir(root.path().join("r").to_str().unwrap(), "dirsym/link"); + + assert_eq!( + glob_vec("dirsym/**/*.md"), + vec!( + PathBuf::from("dirsym/link/another/a.md"), + PathBuf::from("dirsym/link/current_dir.md"), + PathBuf::from("dirsym/link/one/a.md"), + PathBuf::from("dirsym/link/one/another/a.md"), + PathBuf::from("dirsym/link/one/another/deep/spelunking.md"), + PathBuf::from("dirsym/link/three/c.md"), + PathBuf::from("dirsym/link/two/b.md") + ) + ); + // all recursive entities assert_eq!( glob_vec("r/**"),