summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2023-05-04 19:43:08 -0500
committerToby Vincent <tobyv13@gmail.com>2023-05-04 19:43:08 -0500
commit5da4fb25f9bb72771c7f2798daa6339be0bc3900 (patch)
treec30639c7a95e1d94baff498d247eed43fdb3728e /src
parent589cd987217755e1da7acbc304c373a75a9f7db5 (diff)
refactor: join iterators into a single walk
Diffstat (limited to 'src')
-rw-r--r--src/config.rs32
-rw-r--r--src/git.rs (renamed from src/project/git.rs)8
-rw-r--r--src/lib.rs8
-rw-r--r--src/main.rs38
-rw-r--r--src/parser.rs35
-rw-r--r--src/path.rs (renamed from src/project/path.rs)4
-rw-r--r--src/project.rs57
-rw-r--r--src/search.rs101
-rw-r--r--src/search/entry.rs73
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
diff --git a/src/lib.rs b/src/lib.rs
index 8948cf0..830c912 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())
- }
-}