diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 5 | ||||
-rw-r--r-- | src/error.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/project.rs | 47 | ||||
-rw-r--r-- | src/project/error.rs | 11 | ||||
-rw-r--r-- | src/project/git.rs | 34 | ||||
-rw-r--r-- | src/project/path.rs | 41 | ||||
-rw-r--r-- | src/search.rs | 33 | ||||
-rw-r--r-- | src/search/entry.rs | 9 |
9 files changed, 108 insertions, 80 deletions
@@ -1,10 +1,11 @@ -use crate::{search, Config}; use clap::{Args, Parser}; use figment::{value, Metadata, Profile, Provider}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tracing::{metadata::LevelFilter, Level}; +use crate::{search, Config}; + /// Tool for listing project directories. #[derive(Debug, Clone, Default, Parser, Serialize, Deserialize)] #[command(author, version, about)] @@ -38,7 +39,7 @@ pub struct Projects { hidden: bool, /// Match git repositories - #[arg(long, short)] + #[arg(long, short, default_value_t = true)] git: bool, /// Match directories containing item named <PATTERN> diff --git a/src/error.rs b/src/error.rs index 5c0f26b..f22cff2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,7 +9,7 @@ pub enum Error { Ignore(#[from] ignore::Error), #[error(transparent)] - Git(#[from] git2::Error), + Project(#[from] crate::project::Error), #[error(transparent)] Other(#[from] anyhow::Error), diff --git a/src/main.rs b/src/main.rs index ca08fa4..a8bba5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use clap::Parser; use figment::providers::{Env, Format, Toml}; -use projectr::{Cli, Config, Project, Search}; +use projectr::{Cli, Config, Search}; #[tracing::instrument] fn main() -> Result<()> { @@ -25,11 +25,11 @@ fn main() -> Result<()> { #[tracing::instrument] pub fn run(config: &Config) -> Result<()> { - let mut projects: Vec<Box<dyn Project>> = Search::from_provider(config) + let mut projects: Vec<_> = Search::from_provider(config) .context("Failed to extract paths config")? .collect(); - projects.sort_unstable_by_key(|p| p.timestamp()); + projects.sort_unstable_by_key(|p| p.timestamp().unwrap_or_default()); for project in projects { println!("{}", project.to_path_buf().to_string_lossy()) diff --git a/src/project.rs b/src/project.rs index 1188c88..b13d4a7 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,12 +1,47 @@ -use std::{path::PathBuf, time::Duration}; +use std::{ + path::PathBuf, + time::{Duration, SystemTime}, +}; -pub use git::GitProject; -pub use path::PathProject; +pub use self::error::Error; +pub use self::git::GitProject; +pub use self::path::PathProject; +mod error; mod git; mod path; -pub trait Project { - fn timestamp(&self) -> Option<Duration>; - fn to_path_buf(&self) -> PathBuf; +pub trait Timestamp { + type Error; + + fn timestamp(&self) -> Result<Duration, Self::Error>; +} + +impl<T> Timestamp for T +where + T: AsRef<PathBuf>, +{ + type Error = Error; + + fn timestamp(&self) -> Result<Duration, Self::Error> { + self.as_ref() + .metadata()? + .modified()? + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(Into::into) + } +} + +pub trait Project: Timestamp { + fn to_path_buf(&self) -> &PathBuf; +} + +impl<T> Project for T +where + T: Timestamp, + T: AsRef<PathBuf>, +{ + fn to_path_buf(&self) -> &PathBuf { + self.as_ref() + } } diff --git a/src/project/error.rs b/src/project/error.rs new file mode 100644 index 0000000..7047533 --- /dev/null +++ b/src/project/error.rs @@ -0,0 +1,11 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error(transparent)] + Git(#[from] git2::Error), + + #[error(transparent)] + SystemTime(#[from] std::time::SystemTimeError), +} diff --git a/src/project/git.rs b/src/project/git.rs index f1c4d5a..00cd000 100644 --- a/src/project/git.rs +++ b/src/project/git.rs @@ -1,8 +1,11 @@ -use crate::{Error, Project}; use git2::{BranchType, Repository}; use ignore::DirEntry; use std::{cmp::Ordering, path::PathBuf, time::Duration}; +use crate::project::Error; +use crate::project::Timestamp; +use crate::Project; + #[derive(Debug, Clone)] pub struct GitProject { path_buf: PathBuf, @@ -11,19 +14,19 @@ pub struct GitProject { impl GitProject { fn new(path_buf: PathBuf) -> Result<Self, Error> { - let repository = Repository::open(&path_buf)?; - let latest_commit = Self::latest_commit(&repository); + let latest_commit = Self::latest_commit(&path_buf).ok(); Ok(Self { path_buf, latest_commit, }) } - fn latest_commit(repository: &Repository) -> Option<Duration> { - let mut branches = repository.branches(Some(BranchType::Local)).ok()?; + fn latest_commit(path_buf: &PathBuf) -> Result<Duration, Error> { + let repository = Repository::open(path_buf)?; + let mut branches = repository.branches(Some(BranchType::Local))?; branches .try_fold(0, |latest, branch| { - let branch = branch?.0; + let (branch, _) = branch?; let name = branch .name()? @@ -35,17 +38,24 @@ impl GitProject { .map(|c| (c.time().seconds() as u64).max(latest)) }) .map(Duration::from_secs) - .ok() + .map_err(Into::into) } } -impl Project for GitProject { - fn timestamp(&self) -> Option<Duration> { - self.latest_commit +impl Timestamp for GitProject { + type Error = Error; + + fn timestamp(&self) -> Result<Duration, Self::Error> { + match self.latest_commit { + Some(t) => Ok(t), + None => Self::latest_commit(&self.path_buf), + } } +} - fn to_path_buf(&self) -> PathBuf { - self.path_buf.to_owned() +impl Project for GitProject { + fn to_path_buf(&self) -> &PathBuf { + &self.path_buf } } diff --git a/src/project/path.rs b/src/project/path.rs index 24dad9d..14ce308 100644 --- a/src/project/path.rs +++ b/src/project/path.rs @@ -1,39 +1,18 @@ use ignore::DirEntry; -use std::{ - path::PathBuf, - time::{Duration, SystemTime}, -}; - -use crate::Project; +use std::path::PathBuf; #[derive(Debug, PartialEq, Eq, Clone, Default)] -pub struct PathProject { - path_buf: PathBuf, - timestamp: Option<Duration>, -} +pub struct PathProject(PathBuf); impl PathProject { fn new(path_buf: PathBuf) -> Self { - let timestamp = path_buf - .metadata() - .ok() - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok()); - - Self { - timestamp, - path_buf, - } + Self(path_buf) } } -impl Project for PathProject { - fn timestamp(&self) -> Option<Duration> { - self.timestamp - } - - fn to_path_buf(&self) -> PathBuf { - self.path_buf.to_owned() +impl AsRef<PathBuf> for PathProject { + fn as_ref(&self) -> &PathBuf { + &self.0 } } @@ -55,12 +34,6 @@ impl TryFrom<(&String, DirEntry)> for PathProject { } } -impl From<PathBuf> for PathProject { - fn from(value: PathBuf) -> Self { - Self::new(value) - } -} - impl From<DirEntry> for PathProject { fn from(value: DirEntry) -> Self { Self::new(value.into_path()) @@ -69,6 +42,6 @@ impl From<DirEntry> for PathProject { impl From<PathProject> for PathBuf { fn from(value: PathProject) -> Self { - value.path_buf + value.0 } } diff --git a/src/search.rs b/src/search.rs index e39d67b..65fd295 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,12 +1,14 @@ use figment::Provider; use std::vec::IntoIter; -use crate::{Config, Project, Result}; +use crate::{Config, Result}; pub use entry::Entry; pub mod entry; +type ProjectItem = Box<dyn crate::Project<Error = crate::project::Error>>; + pub struct Search { iter: IntoIter<entry::Config>, curr: Option<Entry>, @@ -33,20 +35,11 @@ impl Search { } } -// impl<T> From<T> for Search -// where -// T: IntoIterator<Item = entry::Config, IntoIter = IntoIter<entry::Config>>, -// { -// fn from(value: T) -> Self { -// Self { -// iter: value.into_iter(), -// curr: None, -// } -// } -// } - -impl From<Vec<entry::Config>> for Search { - fn from(value: Vec<entry::Config>) -> Self { +impl<T> From<T> for Search +where + T: IntoIterator<IntoIter = IntoIter<entry::Config>>, +{ + fn from(value: T) -> Self { Self { iter: value.into_iter(), curr: None, @@ -61,7 +54,7 @@ impl From<Config> for Search { } impl Iterator for Search { - type Item = Box<dyn Project>; + type Item = ProjectItem; #[tracing::instrument] fn next(&mut self) -> Option<Self::Item> { @@ -117,11 +110,13 @@ mod tests { path_bufs.iter().try_for_each(fs::create_dir_all).unwrap(); path_bufs.sort(); - let mut results = paths.into_iter().collect::<Vec<Box<dyn Project>>>(); - results.sort_unstable_by_key(|p| p.timestamp()); + let mut results = paths.into_iter().collect::<Vec<ProjectItem>>(); + + results.sort_unstable_by_key(|p| p.timestamp().unwrap_or_default()); + let results = results .into_iter() - .map(|p| p.to_path_buf()) + .map(|p| p.to_path_buf().to_owned()) .collect::<Vec<PathBuf>>(); assert_eq!(path_bufs, results); diff --git a/src/search/entry.rs b/src/search/entry.rs index 6e94b35..ab9da38 100644 --- a/src/search/entry.rs +++ b/src/search/entry.rs @@ -24,7 +24,10 @@ impl std::fmt::Debug for Entry { } impl Entry { - pub fn match_project(&self, dir_entry: DirEntry) -> Option<Box<dyn Project>> { + pub fn parse_dir_entry( + &self, + dir_entry: DirEntry, + ) -> Option<Box<dyn Project<Error = crate::project::Error>>> { if self.config.git { if let Ok(git) = GitProject::try_from(dir_entry.to_owned()) { return Some(Box::new(git)); @@ -42,12 +45,12 @@ impl Entry { } impl Iterator for Entry { - type Item = Box<dyn Project>; + type Item = Box<dyn Project<Error = crate::project::Error>>; #[tracing::instrument] fn next(&mut self) -> Option<Self::Item> { match self.iter.next()? { - Ok(dir_entry) => self.match_project(dir_entry), + Ok(dir_entry) => self.parse_dir_entry(dir_entry), Err(err) => { error!(%err, "Ignoring errored path"); self.next() |