diff --git a/src/walk/filter.rs b/src/walk/filter.rs index 1bdeaf4..2f515b8 100644 --- a/src/walk/filter.rs +++ b/src/walk/filter.rs @@ -142,23 +142,6 @@ impl Feed for (T, U) { type Residue = U; } -// TODO: Remove this? -pub trait Isomorphic: Feed -where - SeparationFiltrate: Isomer>, - for<'a> SeparationResidue: - 'a + Isomer, Substituent<'a> = SeparationSubstituent<'a, Self>>, -{ -} - -impl Isomorphic for S -where - F: Isomer, - for<'a> R: 'a + Isomer = >::Substituent<'a>>, - S: Feed, -{ -} - pub trait Isomer: Sized { type Substituent<'a> where @@ -180,9 +163,7 @@ impl Separation where S: Feed, { - // TODO: Provide a trait-based and intentionally very limited way to implement a base - // `Feed` that constructs a `Filtrate` from unfiltered `Iterator` items of `T`. - pub(in crate::walk) fn from_inner_filtrate(filtrate: S::Filtrate) -> Self { + fn from_inner_filtrate(filtrate: S::Filtrate) -> Self { Separation::Filtrate(Filtrate::new(filtrate)) } @@ -276,7 +257,11 @@ where } } - pub fn filter_isomer_tree(self, cancellation: WalkCancellation<'_, I>, f: F) -> Self + pub fn filter_tree_by_substituent( + self, + cancellation: WalkCancellation<'_, I>, + f: F, + ) -> Self where U: 'static + From, S::Filtrate: Isomer>, @@ -339,21 +324,27 @@ where } } -// TODO: Remove this? -impl Feed for Separation -where - S: Feed, -{ - type Filtrate = S::Filtrate; - type Residue = S::Residue; -} - pub trait SeparatingFilter { type Feed: Feed; fn feed(&mut self) -> Option>; } +impl SeparatingFilter for I +where + I: SeparatingFilterInput, +{ + type Feed = ::Feed; + + fn feed(&mut self) -> Option> { + self.next().map(Separation::from_inner_filtrate) + } +} + +pub trait SeparatingFilterInput: Iterator { + type Feed: Feed; +} + pub trait SkipTree { fn skip_tree(&mut self); } @@ -366,8 +357,9 @@ where I: SkipTree, { fn skip_tree(&mut self) { - // Client code is only able to move a `WalkCancellation` into `Filtered::filter_map_tree`, - // at which point the filtered item should be the current item of the iterator. + // Client code is only able to move a `WalkCancellation` into + // `Separation::filter_map_tree`, at which point the filtered item should be the current + // item of the iterator. self.0.skip_tree() } } diff --git a/src/walk/mod.rs b/src/walk/mod.rs index 2e832ba..f308083 100644 --- a/src/walk/mod.rs +++ b/src/walk/mod.rs @@ -9,7 +9,6 @@ mod filter; use itertools::Itertools as _; use regex::Regex; -use std::borrow::Cow; use std::fs::{FileType, Metadata}; use std::io; use std::path::{Component, Path, PathBuf}; @@ -20,7 +19,8 @@ use crate::capture::MatchedText; use crate::encode::CompileError; use crate::token::{self, Token, TokenTree}; use crate::walk::filter::{ - FilterMapTree, Isomer, SeparatingFilter, Separation, SkipTree, TreeIterator, + FilterMapTree, Isomer, SeparatingFilter, SeparatingFilterInput, Separation, SkipTree, + TreeIterator, }; use crate::{BuildError, CandidatePath, Combine, Glob}; @@ -124,7 +124,7 @@ impl<'t> Glob<'t> { pub fn walk( &self, directory: impl Into, - ) -> WalkGlob>> { + ) -> WalkGlob> { self.walk_with_behavior(directory, WalkBehavior::default()) } @@ -133,14 +133,14 @@ impl<'t> Glob<'t> { &self, directory: impl Into, behavior: impl Into, - ) -> WalkGlob>> { - let (root, prefix) = self.root_prefix_paths(directory); + ) -> WalkGlob> { + let Anchor { root, prefix } = self.anchor(directory); let component_patterns = WalkGlob::<()>::compile(self.tree.as_ref().tokens()) .expect("failed to compile glob sub-expressions"); let complete_pattern = self.pattern.clone(); - let input = WalkTree::build_with(root.clone().into(), move |builder| { + let input = WalkTree::from({ let WalkBehavior { depth, link } = behavior.into(); - builder + WalkDir::new(root.clone()) .follow_links(match link { LinkBehavior::ReadFile => false, LinkBehavior::ReadTarget => true, @@ -153,7 +153,11 @@ impl<'t> Glob<'t> { let filtrate = match separation.filtrate() { Some(filtrate) => match filtrate.transpose() { - Ok(entry) => entry, + Ok(filtrate) => filtrate.map(|entry| { + entry + .strip_path_prefix(&prefix) + .expect("path is not in tree") + }), Err(error) => { return Separation::from(error.map(|error| Err(error.into()))); }, @@ -161,10 +165,7 @@ impl<'t> Glob<'t> { _ => unreachable!(), }; let entry = filtrate.as_ref(); - let path = entry - .path() - .strip_prefix(&prefix) - .expect("path is not in tree"); + let path = entry.stripped_or_base_path(); let depth = entry.depth().saturating_sub(1); for (position, candidate) in path .components() @@ -186,9 +187,9 @@ impl<'t> Glob<'t> { }, (Last | Only, Both(candidate, pattern)) => { return if pattern.is_match(candidate.as_ref()) { - let path = CandidatePath::from(path); + let candidate = CandidatePath::from(path); if let Some(matched) = complete_pattern - .captures(path.as_ref()) + .captures(candidate.as_ref()) .map(MatchedText::from) .map(MatchedText::into_owned) { @@ -207,9 +208,9 @@ impl<'t> Glob<'t> { }; }, (_, Left(_candidate)) => { - let path = CandidatePath::from(path); + let candidate = CandidatePath::from(path); return if let Some(matched) = complete_pattern - .captures(path.as_ref()) + .captures(candidate.as_ref()) .map(MatchedText::from) .map(MatchedText::into_owned) { @@ -228,9 +229,9 @@ impl<'t> Glob<'t> { } // If the component loop is not entered, then check for a match. This may indicate that // the `Glob` is empty and a single invariant path may be matched. - let path = CandidatePath::from(path); + let candidate = CandidatePath::from(path); if let Some(matched) = complete_pattern - .captures(path.as_ref()) + .captures(candidate.as_ref()) .map(MatchedText::from) .map(MatchedText::into_owned) { @@ -243,7 +244,7 @@ impl<'t> Glob<'t> { WalkGlob { input, root } } - fn root_prefix_paths(&self, directory: impl Into) -> (PathBuf, PathBuf) { + fn anchor(&self, directory: impl Into) -> Anchor { fn invariant_path_prefix<'t, A, I>(tokens: I) -> Option where A: 't, @@ -262,7 +263,7 @@ impl<'t> Glob<'t> { // The directory tree is traversed from `root`, which may include an invariant prefix from // the glob pattern. Patterns are only applied to path components following this prefix in // `root`. - match invariant_path_prefix(self.tree.as_ref().tokens()) { + let (root, prefix) = match invariant_path_prefix(self.tree.as_ref().tokens()) { Some(prefix) => { let root = directory.join(&prefix); if prefix.is_absolute() { @@ -275,37 +276,18 @@ impl<'t> Glob<'t> { } }, _ => (directory.clone(), directory), - } + }; + Anchor { root, prefix } } } -//impl Isomer> for Result -//where -// T: AsRef, -//{ -// type Substituent<'a> = Result<&'a WalkEntry, &'a WalkError> -// where -// Self: 'a; -// -// fn substituent(&self) -> Self::Substituent<'_> { -// self.as_ref().map(|filtrate| filtrate.as_ref()) -// } -//} -// -//impl Isomer> for TreeResidue -//where -// T: AsRef, -//{ -// type Substituent<'a> = Result<&'a WalkEntry, &'a WalkError> -// where -// Self: 'a; -// -// fn substituent(&self) -> Self::Substituent<'_> { -// Ok(self.as_ref()) -// } -//} +#[derive(Clone, Debug)] +struct Anchor { + root: PathBuf, + prefix: PathBuf, +} -impl Isomer> for GlobEntry { +impl Isomer for GlobEntry { type Substituent<'a> = &'a WalkEntry where Self: 'a; @@ -315,7 +297,27 @@ impl Isomer> for GlobEntry { } } -impl Isomer for TreeResidue { +impl Isomer for FileResidue { + type Substituent<'a> = &'a WalkEntry + where + Self: 'a; + + fn substituent(&self) -> Self::Substituent<'_> { + self.as_ref() + } +} + +impl Isomer for WalkEntry { + type Substituent<'a> = &'a WalkEntry + where + Self: 'a; + + fn substituent(&self) -> Self::Substituent<'_> { + self + } +} + +impl Isomer for FileResidue { type Substituent<'a> = &'a WalkEntry where Self: 'a; @@ -325,10 +327,46 @@ impl Isomer for TreeResidue { } } -// TODO: #[derive(Clone, Debug)] pub struct WalkEntry { entry: DirEntry, + stripped: Option, +} + +impl WalkEntry { + pub fn strip_path_prefix(self, prefix: impl AsRef) -> Option { + self.path() + .strip_prefix(prefix) + .ok() + .map(PathBuf::from) + .map(|stripped| { + let WalkEntry { entry, .. } = self; + WalkEntry { + entry, + stripped: Some(stripped), + } + }) + } + + pub fn unstrip_path_prefix(self) -> Self { + let WalkEntry { entry, .. } = self; + WalkEntry { + entry, + stripped: None, + } + } + + pub fn stripped_or_base_path(&self) -> &Path { + self.stripped_path().unwrap_or_else(|| self.path()) + } + + pub fn stripped_path(&self) -> Option<&Path> { + self.stripped.as_ref().map(AsRef::as_ref) + } + + pub fn has_stripped_path(&self) -> bool { + self.stripped.is_some() + } } impl AsRef for WalkEntry { @@ -337,7 +375,7 @@ impl AsRef for WalkEntry { } } -// TODO: +// TODO: Don't do this! Delegate/forward instead. use std::ops::Deref; impl Deref for WalkEntry { type Target = DirEntry; @@ -349,7 +387,10 @@ impl Deref for WalkEntry { impl From for WalkEntry { fn from(entry: DirEntry) -> Self { - WalkEntry { entry } + WalkEntry { + entry, + stripped: None, + } } } @@ -362,26 +403,16 @@ impl From for WalkEntry { #[derive(Debug)] struct WalkTree { is_dir: bool, - root: PathBuf, input: walkdir::IntoIter, } -impl WalkTree { - fn build_with(root: Cow, f: F) -> Self - where - F: FnOnce(WalkDir) -> WalkDir, - { - let walkdir = f(WalkDir::new(root.clone())); +impl From for WalkTree { + fn from(walkdir: WalkDir) -> Self { WalkTree { is_dir: false, - root: root.into_owned(), input: walkdir.into_iter(), } } - - pub fn root(&self) -> &Path { - self.root.as_ref() - } } impl Iterator for WalkTree { @@ -400,12 +431,8 @@ impl Iterator for WalkTree { } } -impl SeparatingFilter for WalkTree { +impl SeparatingFilterInput for WalkTree { type Feed = (Result, TreeResidue); - - fn feed(&mut self) -> Option> { - self.next().map(Separation::from_inner_filtrate) - } } impl SkipTree for WalkTree { @@ -508,7 +535,7 @@ pub trait FileIterator: impl FileIterator for I where - T: AsRef, + //T: AsRef, I: Iterator> + TreeIterator>, { type Entry = T; @@ -627,12 +654,12 @@ impl WalkNegation { // } //} pub fn residue(&self, entry: &WalkEntry) -> Option> { - let path = CandidatePath::from(entry.path()); - if self.exhaustive.is_match(path.as_ref()) { + let candidate = CandidatePath::from(entry.stripped_or_base_path()); + if self.exhaustive.is_match(candidate.as_ref()) { // Do not descend into directories that match the exhaustive negation. Some(TreeResidue::Tree(())) } - else if self.nonexhaustive.is_match(path.as_ref()) { + else if self.nonexhaustive.is_match(candidate.as_ref()) { Some(TreeResidue::Node(())) } else { @@ -857,8 +884,10 @@ impl WalkGlob { /// [`Pattern::is_exhaustive`]: crate::Pattern::is_exhaustive /// [`WalkEntry`]: crate::WalkEntry /// [`WalkNegation`]: crate::WalkNegation - pub fn not<'t, J>(self, patterns: J) -> Result + pub fn not<'t, J>(self, patterns: J) -> Result, BuildError> where + // TODO: Generalize the `Isomer` implementation and `I::Entry` (do not rely on a particular + // type like `GlobEntry` from the interior `FileIterator`. I: FileIterator, J: IntoIterator, J::Item: Combine<'t>, @@ -867,8 +896,8 @@ impl WalkGlob { self.filter_tree(move |cancellation, separation| { match separation.transpose_filtrate() { Ok(separation) => separation - .filter_isomer_tree(cancellation, |constituent| { - negation.residue(constituent) + .filter_tree_by_substituent(cancellation, |substituent| { + negation.residue(substituent) }) .map_filtrate(Ok), Err(error) => error.map(Err).into(),