Skip to content

Commit 4172399

Browse files
authored
Merge pull request #135 from Kobzol/cache-dir
Cache information about file type
2 parents 7b29204 + e1981f2 commit 4172399

File tree

2 files changed

+98
-19
lines changed

2 files changed

+98
-19
lines changed

src/lib.rs

+69-19
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ use std::cmp;
7373
use std::error::Error;
7474
use std::fmt;
7575
use std::fs;
76+
use std::fs::DirEntry;
7677
use std::io;
78+
use std::ops::Deref;
7779
use std::path::{self, Component, Path, PathBuf};
7880
use std::str::FromStr;
7981

@@ -96,8 +98,8 @@ pub struct Paths {
9698
dir_patterns: Vec<Pattern>,
9799
require_dir: bool,
98100
options: MatchOptions,
99-
todo: Vec<Result<(PathBuf, usize), GlobError>>,
100-
scope: Option<PathBuf>,
101+
todo: Vec<Result<(PathWrapper, usize), GlobError>>,
102+
scope: Option<PathWrapper>,
101103
}
102104

103105
/// 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<Paths, PatternE
242244
}
243245

244246
let scope = root.map_or_else(|| PathBuf::from("."), to_scope);
247+
let scope = PathWrapper::from_path(scope);
245248

246249
let mut dir_patterns = Vec::new();
247250
let components =
@@ -323,8 +326,52 @@ impl fmt::Display for GlobError {
323326
}
324327
}
325328

326-
fn is_dir(p: &Path) -> bool {
327-
fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
329+
#[derive(Debug)]
330+
struct PathWrapper {
331+
path: PathBuf,
332+
is_directory: bool,
333+
}
334+
335+
impl PathWrapper {
336+
fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self {
337+
let is_directory = e
338+
.file_type()
339+
.ok()
340+
.and_then(|file_type| {
341+
// We need to use fs::metadata to resolve the actual path
342+
// if it's a symlink.
343+
if file_type.is_symlink() {
344+
None
345+
} else {
346+
Some(file_type.is_dir())
347+
}
348+
})
349+
.or_else(|| fs::metadata(&path).map(|m| m.is_dir()).ok())
350+
.unwrap_or(false);
351+
Self { path, is_directory }
352+
}
353+
fn from_path(path: PathBuf) -> Self {
354+
let is_directory = fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false);
355+
Self { path, is_directory }
356+
}
357+
358+
fn into_path(self) -> PathBuf {
359+
self.path
360+
}
361+
}
362+
363+
impl Deref for PathWrapper {
364+
type Target = Path;
365+
366+
fn deref(&self) -> &Self::Target {
367+
self.path.deref()
368+
}
369+
}
370+
371+
impl AsRef<Path> for PathWrapper {
372+
fn as_ref(&self) -> &Path {
373+
self.path.as_ref()
374+
}
328375
}
329376

330377
/// An alias for a glob iteration result.
@@ -363,10 +410,10 @@ impl Iterator for Paths {
363410
// idx -1: was already checked by fill_todo, maybe path was '.' or
364411
// '..' that we can't match here because of normalization.
365412
if idx == !0 as usize {
366-
if self.require_dir && !is_dir(&path) {
413+
if self.require_dir && !path.is_directory {
367414
continue;
368415
}
369-
return Some(Ok(path));
416+
return Some(Ok(path.into_path()));
370417
}
371418

372419
if self.dir_patterns[idx].is_recursive {
@@ -379,7 +426,7 @@ impl Iterator for Paths {
379426
next += 1;
380427
}
381428

382-
if is_dir(&path) {
429+
if path.is_directory {
383430
// the path is a directory, so it's a match
384431

385432
// push this directory's contents
@@ -394,7 +441,7 @@ impl Iterator for Paths {
394441
if next == self.dir_patterns.len() - 1 {
395442
// pattern ends in recursive pattern, so return this
396443
// directory as a result
397-
return Some(Ok(path));
444+
return Some(Ok(path.into_path()));
398445
} else {
399446
// advanced to the next pattern for this path
400447
idx = next + 1;
@@ -427,8 +474,8 @@ impl Iterator for Paths {
427474
// *AND* its children so we don't need to check the
428475
// children
429476

430-
if !self.require_dir || is_dir(&path) {
431-
return Some(Ok(path));
477+
if !self.require_dir || path.is_directory {
478+
return Some(Ok(path.into_path()));
432479
}
433480
} else {
434481
fill_todo(
@@ -817,10 +864,10 @@ impl Pattern {
817864
// special-casing patterns to match `.` and `..`, and avoiding `readdir()`
818865
// calls when there are no metacharacters in the pattern.
819866
fn fill_todo(
820-
todo: &mut Vec<Result<(PathBuf, usize), GlobError>>,
867+
todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>,
821868
patterns: &[Pattern],
822869
idx: usize,
823-
path: &Path,
870+
path: &PathWrapper,
824871
options: MatchOptions,
825872
) {
826873
// convert a pattern that's just many Char(_) to a string
@@ -836,7 +883,7 @@ fn fill_todo(
836883
Some(s)
837884
}
838885

839-
let add = |todo: &mut Vec<_>, next_path: PathBuf| {
886+
let add = |todo: &mut Vec<_>, next_path: PathWrapper| {
840887
if idx + 1 == patterns.len() {
841888
// We know it's good, so don't make the iterator match this path
842889
// against the pattern again. In particular, it can't match
@@ -848,8 +895,8 @@ fn fill_todo(
848895
};
849896

850897
let pattern = &patterns[idx];
851-
let is_dir = is_dir(path);
852-
let curdir = path == Path::new(".");
898+
let is_dir = path.is_directory;
899+
let curdir = path.as_ref() == Path::new(".");
853900
match pattern_as_str(pattern) {
854901
Some(s) => {
855902
// This pattern component doesn't have any metacharacters, so we
@@ -863,6 +910,7 @@ fn fill_todo(
863910
} else {
864911
path.join(&s)
865912
};
913+
let next_path = PathWrapper::from_path(next_path);
866914
if (special && is_dir)
867915
|| (!special
868916
&& (fs::metadata(&next_path).is_ok()
@@ -875,19 +923,21 @@ fn fill_todo(
875923
let dirs = fs::read_dir(path).and_then(|d| {
876924
d.map(|e| {
877925
e.map(|e| {
878-
if curdir {
926+
let path = if curdir {
879927
PathBuf::from(e.path().file_name().unwrap())
880928
} else {
881929
e.path()
882-
}
930+
};
931+
PathWrapper::from_dir_entry(path, e)
883932
})
884933
})
885934
.collect::<Result<Vec<_>, _>>()
886935
});
887936
match dirs {
888937
Ok(mut children) => {
889938
if options.require_literal_leading_dot {
890-
children.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
939+
children
940+
.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
891941
}
892942
children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name()));
893943
todo.extend(children.into_iter().map(|x| Ok((x, idx))));
@@ -900,7 +950,7 @@ fn fill_todo(
900950
if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
901951
for &special in &[".", ".."] {
902952
if pattern.matches_with(special, options) {
903-
add(todo, path.join(special));
953+
add(todo, PathWrapper::from_path(path.join(special)));
904954
}
905955
}
906956
}

tests/glob-std.rs

+29
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ fn main() {
4444
}
4545
}
4646

47+
fn mk_symlink_dir(original: &str, link: &str) {
48+
#[cfg(unix)]
49+
{
50+
use std::os::unix::fs::symlink;
51+
symlink(original, link).unwrap();
52+
}
53+
#[cfg(windows)]
54+
{
55+
use std::os::windows::fs::symlink_dir;
56+
symlink_dir(original, link).unwrap();
57+
}
58+
}
59+
4760
fn glob_vec(pattern: &str) -> Vec<PathBuf> {
4861
glob(pattern).unwrap().map(|r| r.unwrap()).collect()
4962
}
@@ -99,6 +112,22 @@ fn main() {
99112
mk_file("r/three", true);
100113
mk_file("r/three/c.md", false);
101114

115+
mk_file("dirsym", true);
116+
mk_symlink_dir(root.path().join("r").to_str().unwrap(), "dirsym/link");
117+
118+
assert_eq!(
119+
glob_vec("dirsym/**/*.md"),
120+
vec!(
121+
PathBuf::from("dirsym/link/another/a.md"),
122+
PathBuf::from("dirsym/link/current_dir.md"),
123+
PathBuf::from("dirsym/link/one/a.md"),
124+
PathBuf::from("dirsym/link/one/another/a.md"),
125+
PathBuf::from("dirsym/link/one/another/deep/spelunking.md"),
126+
PathBuf::from("dirsym/link/three/c.md"),
127+
PathBuf::from("dirsym/link/two/b.md")
128+
)
129+
);
130+
102131
// all recursive entities
103132
assert_eq!(
104133
glob_vec("r/**"),

0 commit comments

Comments
 (0)