Skip to content

Commit b628096

Browse files
committed
Cache information about file type
This commit adds a cache that remembers whether a given path is a file or a directory, based on the results of `std::fs::read_dir`. This reduces the number of executed syscalls and improves the performance of the library.
1 parent 7b29204 commit b628096

File tree

1 file changed

+58
-15
lines changed

1 file changed

+58
-15
lines changed

src/lib.rs

+58-15
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,45 @@ 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_dir: Option<bool>,
333+
}
334+
335+
impl PathWrapper {
336+
fn from_dir_entry(path: PathBuf, e: DirEntry) -> Self {
337+
Self {
338+
path,
339+
is_dir: e.file_type().ok().map(|file_type| file_type.is_dir()),
340+
}
341+
}
342+
fn from_path(path: PathBuf) -> Self {
343+
Self { path, is_dir: None }
344+
}
345+
346+
fn into_path(self) -> PathBuf {
347+
self.path
348+
}
349+
}
350+
351+
impl Deref for PathWrapper {
352+
type Target = Path;
353+
354+
fn deref(&self) -> &Self::Target {
355+
self.path.deref()
356+
}
357+
}
358+
359+
impl AsRef<Path> for PathWrapper {
360+
fn as_ref(&self) -> &Path {
361+
self.path.as_ref()
362+
}
363+
}
364+
365+
fn is_dir(p: &PathWrapper) -> bool {
366+
p.is_dir
367+
.unwrap_or_else(|| fs::metadata(&p.path).map(|m| m.is_dir()).unwrap_or(false))
328368
}
329369

330370
/// An alias for a glob iteration result.
@@ -366,7 +406,7 @@ impl Iterator for Paths {
366406
if self.require_dir && !is_dir(&path) {
367407
continue;
368408
}
369-
return Some(Ok(path));
409+
return Some(Ok(path.into_path()));
370410
}
371411

372412
if self.dir_patterns[idx].is_recursive {
@@ -394,7 +434,7 @@ impl Iterator for Paths {
394434
if next == self.dir_patterns.len() - 1 {
395435
// pattern ends in recursive pattern, so return this
396436
// directory as a result
397-
return Some(Ok(path));
437+
return Some(Ok(path.into_path()));
398438
} else {
399439
// advanced to the next pattern for this path
400440
idx = next + 1;
@@ -428,7 +468,7 @@ impl Iterator for Paths {
428468
// children
429469

430470
if !self.require_dir || is_dir(&path) {
431-
return Some(Ok(path));
471+
return Some(Ok(path.into_path()));
432472
}
433473
} else {
434474
fill_todo(
@@ -817,10 +857,10 @@ impl Pattern {
817857
// special-casing patterns to match `.` and `..`, and avoiding `readdir()`
818858
// calls when there are no metacharacters in the pattern.
819859
fn fill_todo(
820-
todo: &mut Vec<Result<(PathBuf, usize), GlobError>>,
860+
todo: &mut Vec<Result<(PathWrapper, usize), GlobError>>,
821861
patterns: &[Pattern],
822862
idx: usize,
823-
path: &Path,
863+
path: &PathWrapper,
824864
options: MatchOptions,
825865
) {
826866
// convert a pattern that's just many Char(_) to a string
@@ -836,7 +876,7 @@ fn fill_todo(
836876
Some(s)
837877
}
838878

839-
let add = |todo: &mut Vec<_>, next_path: PathBuf| {
879+
let add = |todo: &mut Vec<_>, next_path: PathWrapper| {
840880
if idx + 1 == patterns.len() {
841881
// We know it's good, so don't make the iterator match this path
842882
// against the pattern again. In particular, it can't match
@@ -849,7 +889,7 @@ fn fill_todo(
849889

850890
let pattern = &patterns[idx];
851891
let is_dir = is_dir(path);
852-
let curdir = path == Path::new(".");
892+
let curdir = path.as_ref() == Path::new(".");
853893
match pattern_as_str(pattern) {
854894
Some(s) => {
855895
// This pattern component doesn't have any metacharacters, so we
@@ -863,6 +903,7 @@ fn fill_todo(
863903
} else {
864904
path.join(&s)
865905
};
906+
let next_path = PathWrapper::from_path(next_path);
866907
if (special && is_dir)
867908
|| (!special
868909
&& (fs::metadata(&next_path).is_ok()
@@ -875,19 +916,21 @@ fn fill_todo(
875916
let dirs = fs::read_dir(path).and_then(|d| {
876917
d.map(|e| {
877918
e.map(|e| {
878-
if curdir {
919+
let path = if curdir {
879920
PathBuf::from(e.path().file_name().unwrap())
880921
} else {
881922
e.path()
882-
}
923+
};
924+
PathWrapper::from_dir_entry(path, e)
883925
})
884926
})
885927
.collect::<Result<Vec<_>, _>>()
886928
});
887929
match dirs {
888930
Ok(mut children) => {
889931
if options.require_literal_leading_dot {
890-
children.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
932+
children
933+
.retain(|x| !x.file_name().unwrap().to_str().unwrap().starts_with("."));
891934
}
892935
children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name()));
893936
todo.extend(children.into_iter().map(|x| Ok((x, idx))));
@@ -900,7 +943,7 @@ fn fill_todo(
900943
if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') {
901944
for &special in &[".", ".."] {
902945
if pattern.matches_with(special, options) {
903-
add(todo, path.join(special));
946+
add(todo, PathWrapper::from_path(path.join(special)));
904947
}
905948
}
906949
}

0 commit comments

Comments
 (0)