diff options
author | Toby Vincent <tobyv13@gmail.com> | 2023-05-04 19:43:08 -0500 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2023-05-04 19:43:08 -0500 |
commit | 5da4fb25f9bb72771c7f2798daa6339be0bc3900 (patch) | |
tree | c30639c7a95e1d94baff498d247eed43fdb3728e /src | |
parent | 589cd987217755e1da7acbc304c373a75a9f7db5 (diff) |
refactor: join iterators into a single walk
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 32 | ||||
-rw-r--r-- | src/git.rs (renamed from src/project/git.rs) | 8 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/main.rs | 38 | ||||
-rw-r--r-- | src/parser.rs | 35 | ||||
-rw-r--r-- | src/path.rs (renamed from src/project/path.rs) | 4 | ||||
-rw-r--r-- | src/project.rs | 57 | ||||
-rw-r--r-- | src/search.rs | 101 | ||||
-rw-r--r-- | src/search/entry.rs | 73 |
9 files changed, 164 insertions, 192 deletions
diff --git a/src/config.rs b/src/config.rs index 61a0778..b87f88d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,21 +12,24 @@ pub struct Config { pub paths: Vec<PathBuf>, /// (UNIMPLEMENTED) Additional directories to add to the sorted output. - #[arg(long)] - pub add: Vec<PathBuf>, + #[arg(long = "project", short = 'P')] + pub projects: Vec<PathBuf>, #[command(flatten)] - pub filters: Filters, + pub search: Search, #[command(flatten)] pub verbosity: Verbosity, } #[derive(Debug, Default, Clone, Args)] -pub struct Filters { - /// Match all child directories. - #[arg(short, long)] - pub all: bool, +pub struct Search { + /// Recurse into hidden directories. + /// + /// Traverse into hidden directories while searching. A directory is considered hidden + /// if its name starts with a `.` sign (dot). If `--max-depth` is 0, this has no effect. + #[arg(long)] + pub hidden: bool, /// Max depth to recurse. /// @@ -34,12 +37,15 @@ pub struct Filters { #[arg(short = 'd', long, default_value = "1")] pub max_depth: Option<usize>, - /// Recurse into hidden directories. - /// - /// Traverse into hidden directories while searching. A directory is considered hidden - /// if its name starts with a `.` sign (dot). If `--max-depth` is 0, this has no effect. - #[arg(long)] - pub hidden: bool, + #[command(flatten)] + pub parsers: Parsers, +} + +#[derive(Debug, Default, Clone, Args)] +pub struct Parsers { + /// Match all child directories. + #[arg(short, long)] + pub all: bool, /// Match directories containing <PATTERN>. /// diff --git a/src/project/git.rs b/src/git.rs index e2067ec..afa4a9a 100644 --- a/src/project/git.rs +++ b/src/git.rs @@ -1,19 +1,19 @@ use git2::{BranchType, Repository}; use std::{path::PathBuf, time::Duration}; -use super::{Project, ProjectParser}; +use crate::{parser::Parser, project::Project}; #[derive(Debug, Clone)] -pub struct GitMatcher; +pub struct Git; -impl ProjectParser for GitMatcher { +impl Parser for Git { #[tracing::instrument] fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>> { Repository::open(&path_buf)?.parse(path_buf) } } -impl ProjectParser for Repository { +impl Parser for Repository { fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>> { let mut branches = self.branches(Some(BranchType::Local))?; let timestamp = branches @@ -1,6 +1,12 @@ pub use crate::config::Config; +pub use crate::search::SearchBuilder; +pub mod parser; pub mod project; pub mod search; -mod config; +#[cfg(feature = "git")] +pub mod git; +pub mod path; + +pub mod config; diff --git a/src/main.rs b/src/main.rs index 7fbed15..33b170d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use projectr::{project::Project, search::Search, Config}; +use projectr::{config::Config, path::PathMatcher, project::Project, search::SearchBuilder}; fn main() -> Result<()> { let config = Config::parse(); @@ -11,7 +11,9 @@ fn main() -> Result<()> { .with_max_level(&config.verbosity) .init(); - let mut projects: Vec<Project> = Search::new(config).into_iter().collect(); + let mut projects: Vec<Project> = build_search(&config) + .filter(|p| !config.paths.contains(&p.worktree)) + .collect(); projects.sort_unstable_by_key(|p| p.timestamp); @@ -21,3 +23,35 @@ fn main() -> Result<()> { Ok(()) } + +fn build_search(config: &Config) -> impl Iterator<Item = Project> { + let (init, paths) = config.paths.split_first().unwrap(); + let mut builder = SearchBuilder::new(init); + + for path in paths { + builder.add(path); + } + + for path in &config.projects { + builder.project(path); + } + + builder.max_depth(config.search.max_depth); + + builder.hidden(!config.search.hidden); + + if config.search.parsers.all { + builder.parser(PathMatcher::All); + } + + if let Some(pattern) = &config.search.parsers.pattern { + builder.parser(PathMatcher::Pattern(pattern.to_owned())); + } + + #[cfg(feature = "git")] + if config.search.parsers.git { + builder.parser(projectr::git::Git); + } + + builder.build() +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..1d3f159 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,35 @@ +use std::{fmt::Display, path::PathBuf}; + +use tracing::warn; + +use crate::project::Project; + +#[derive(Debug)] +pub struct NotMatched; + +impl Display for NotMatched { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to parse path") + } +} + +impl std::error::Error for NotMatched {} + +pub trait Parser { + fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>>; +} + +impl Parser for Vec<Box<dyn Parser>> { + fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>> { + self.iter() + .map(|p| p.parse(path_buf.to_owned())) + .inspect(|res| { + if let Err(err) = res { + warn!(%err, "Parser failed to match"); + } + }) + .flatten() + .reduce(|max, p| p.max(max)) + .ok_or(Box::new(NotMatched)) + } +} diff --git a/src/project/path.rs b/src/path.rs index 0e38990..05a9caa 100644 --- a/src/project/path.rs +++ b/src/path.rs @@ -1,6 +1,6 @@ use std::{io::ErrorKind, path::PathBuf}; -use super::{Project, ProjectParser}; +use crate::{parser::Parser, project::Project}; #[derive(Debug, Clone)] pub enum PathMatcher { @@ -8,7 +8,7 @@ pub enum PathMatcher { Pattern(String), } -impl ProjectParser for PathMatcher { +impl Parser for PathMatcher { #[tracing::instrument] fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>> { let project = match self { diff --git a/src/project.rs b/src/project.rs index 2b3e028..b71a3b8 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,58 +1,23 @@ use std::{ - ops::{Deref, DerefMut}, + convert::Infallible, path::PathBuf, time::{Duration, SystemTime}, }; -use tracing::warn; +pub trait Generator { + type Error: std::error::Error; -pub mod path; - -#[cfg(feature = "git")] -pub mod git; - -pub trait ProjectParser { - fn parse(&self, path_buf: PathBuf) -> Result<Project, Box<dyn std::error::Error>>; -} - -#[derive(Default)] -pub struct ProjectParserGroup { - pub parsers: Vec<Box<dyn ProjectParser>>, + fn generate(self) -> Result<Vec<Project>, Self::Error>; } -impl ProjectParserGroup { - pub fn new() -> Self { - Self::default() - } - - pub fn parse(&self, path_buf: std::path::PathBuf) -> Option<Project> { - if self.parsers.is_empty() { - return path_buf.try_into().ok(); - } - - self.iter() - .map(|p| p.parse(path_buf.to_owned())) - .inspect(|res| { - if let Err(err) = res { - warn!(%err, "Parser failed to match"); - } - }) - .flatten() - .reduce(|max, p| p.max(max)) - } -} - -impl Deref for ProjectParserGroup { - type Target = Vec<Box<dyn ProjectParser>>; - - fn deref(&self) -> &Self::Target { - &self.parsers - } -} +impl<T> Generator for T +where + T: IntoIterator<Item = Project>, +{ + type Error = Infallible; -impl DerefMut for ProjectParserGroup { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.parsers + fn generate(self) -> Result<Vec<Project>, Self::Error> { + Ok(self.into_iter().collect()) } } diff --git a/src/search.rs b/src/search.rs index 81049e9..27495b0 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,69 +1,68 @@ -use std::path::PathBuf; +use std::{ + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; -use crate::{config::Filters, project::Project, Config}; +use ignore::{DirEntry, WalkBuilder}; +use tracing::{debug, error}; -use self::entry::SearchPath; +use crate::{parser::Parser, project::Project}; -pub mod entry; - -type EntryIter = std::vec::IntoIter<PathBuf>; - -#[derive(Debug, Default, Clone)] -pub struct Search { - pub paths: Vec<PathBuf>, - pub add: Vec<PathBuf>, - pub filters: Filters, +pub struct SearchBuilder { + walk_builder: WalkBuilder, + projects: Vec<PathBuf>, + parsers: Vec<Box<dyn Parser>>, } -impl Search { - pub fn new( - Config { - paths, - add, - filters, - verbosity: _, - }: Config, - ) -> Self { +impl SearchBuilder { + pub fn new<P: AsRef<Path>>(path: P) -> Self { Self { - paths, - add, - filters, + walk_builder: WalkBuilder::new(&path), + projects: Default::default(), + parsers: Default::default(), } } -} -impl IntoIterator for Search { - type Item = Project; + pub fn project<P: AsRef<Path>>(&mut self, path: P) { + self.projects.push(path.as_ref().to_path_buf()) + } - type IntoIter = SearchIter; + pub fn parser(&mut self, parser: impl Parser + 'static) { + self.parsers.push(Box::new(parser)) + } - fn into_iter(self) -> Self::IntoIter { - SearchIter { - iter: self.paths.into_iter(), - config: self.filters, - curr: None, - } + pub fn build(mut self) -> impl Iterator<Item = Project> { + self.walk_builder + .standard_filters(true) + .build() + .inspect(|res| { + if let Err(err) = res { + error!(%err, "Ignoring errored path"); + } + }) + .flatten() + .map(DirEntry::into_path) + .chain(self.projects.into_iter()) + .map(move |p| self.parsers.parse(p)) + .inspect(|res| { + if let Err(err) = res { + debug!(%err, "Failed to match"); + } + }) + .flatten() } } -#[derive(Debug)] -pub struct SearchIter { - iter: EntryIter, - config: Filters, - curr: Option<SearchPath>, -} +impl Deref for SearchBuilder { + type Target = WalkBuilder; -impl Iterator for SearchIter { - type Item = Project; + fn deref(&self) -> &Self::Target { + &self.walk_builder + } +} - #[tracing::instrument] - fn next(&mut self) -> Option<Self::Item> { - match self.curr.as_mut().and_then(|c| c.next()) { - Some(proj) => Some(proj), - None => { - self.curr = Some(SearchPath::new(self.iter.next()?, &self.config)); - self.next() - } - } +impl DerefMut for SearchBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.walk_builder } } diff --git a/src/search/entry.rs b/src/search/entry.rs deleted file mode 100644 index efd287b..0000000 --- a/src/search/entry.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::path::PathBuf; - -use ignore::{Walk, WalkBuilder}; -use tracing::{debug, error}; - -use crate::{ - config::Filters, - project::{path::PathMatcher, Project, ProjectParserGroup}, -}; - -pub struct SearchPath { - path_buf: PathBuf, - parsers: ProjectParserGroup, - iter: Walk, -} - -impl std::fmt::Debug for SearchPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SearchPath") - .field("path_buf", &self.path_buf) - .finish() - } -} - -impl SearchPath { - pub fn new(path_buf: PathBuf, config: &Filters) -> Self { - let mut parsers = ProjectParserGroup::new(); - - if config.all { - parsers.push(Box::new(PathMatcher::All)) - } - - if let Some(s) = config.pattern.as_ref() { - parsers.push(Box::new(PathMatcher::Pattern(s.to_owned()))); - }; - - #[cfg(feature = "git")] - if config.git { - parsers.push(Box::new(crate::project::git::GitMatcher)); - }; - - let iter = WalkBuilder::new(&path_buf) - .standard_filters(true) - .max_depth(config.max_depth) - .hidden(!config.hidden) - .build(); - - Self { - path_buf, - parsers, - iter, - } - } -} - -impl Iterator for SearchPath { - type Item = Project; - - fn next(&mut self) -> Option<Self::Item> { - match self.iter.next()? { - Ok(dir_entry) if dir_entry.path() == self.path_buf => { - debug!("Ignoring parent directory"); - None - } - Ok(dir_entry) => self.parsers.parse(dir_entry.into_path()), - Err(err) => { - error!(%err, "Ignoring errored path"); - None - } - } - .or_else(|| self.next()) - } -} |