use log::trace; use std::path::{Path, PathBuf}; use walkdir::WalkDir; #[derive(Debug, thiserror::Error)] pub enum SourceDirError { #[error("failed to list files in source directory: {0}")] WalkDir(PathBuf, #[source] walkdir::Error), } pub struct SourceDir { path: PathBuf, files: Vec, artificial: bool, } impl SourceDir { pub fn new(path: &Path) -> Self { Self { path: path.into(), files: vec![], artificial: false, } } pub fn insert_for_tests

(&mut self, path: P) where P: AsRef, { self.artificial = true; self.insert(path.as_ref()); } pub fn insert(&mut self, path: &Path) { trace!("Source Dir::insert: path={}", path.display()); self.files.push(path.into()); } pub fn scan(&mut self) -> Result<(), SourceDirError> { if self.artificial { trace!("SourceDir::scan: artificial mode, not actually scanning"); } else { trace!("SourceDir::scan: find files in {}", self.path.display()); for e in WalkDir::new(&self.path) { let e = e.map_err(|err| SourceDirError::WalkDir(self.path.clone(), err))?; let path = e.path(); trace!("SourceDir::scan: found {}", path.display()); self.insert(path); } } Ok(()) } pub fn files(&self) -> &[PathBuf] { &self.files } } #[derive(Default)] pub struct PathFilter { excluded_substrings: Vec<&'static str>, excluded_suffixes: Vec<&'static str>, } impl PathFilter { pub fn new(subs: &[&'static str], suffixes: &[&'static str]) -> Self { Self { excluded_substrings: subs.to_vec(), excluded_suffixes: suffixes.to_vec(), } } pub fn exclude_substring(&mut self, s: &'static str) { self.excluded_substrings.push(s); } pub fn exclude_suffix(&mut self, s: &'static str) { self.excluded_suffixes.push(s); } pub fn is_included

(&self, path: P) -> bool where P: AsRef, { let path = path.as_ref(); let include = { let path = path.to_string_lossy(); for pat in self.excluded_suffixes.iter() { if path.ends_with(pat) { return false; } } for pat in self.excluded_substrings.iter() { if path.contains(pat) { return false; } } true }; if include { trace!("include {}", path.display()); } else { trace!("exclude {}", path.display()); } include } } #[cfg(test)] mod test { use super::PathFilter; #[test] fn includes_dotgit_by_default() { let filter = PathFilter::default(); assert!(filter.is_included(".git")); } #[test] fn excludes_dotgit_if_requested() { let mut filter = PathFilter::default(); filter.exclude_substring(".git"); assert!(!filter.is_included(".git")); } #[test] fn includes_footilde_by_default() { let filter = PathFilter::default(); assert!(filter.is_included("foo~")); } #[test] fn includes_footildebar_if_tilde_suffix_is_excluded() { let mut filter = PathFilter::default(); filter.exclude_suffix("~"); assert!(filter.is_included("foo~bar")); } #[test] fn excludes_footilde_if_tilde_suffix_is_excluded() { let mut filter = PathFilter::default(); filter.exclude_suffix("~"); assert!(!filter.is_included("foo~")); } }