summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs5
-rw-r--r--src/error.rs2
-rw-r--r--src/main.rs6
-rw-r--r--src/project.rs47
-rw-r--r--src/project/error.rs11
-rw-r--r--src/project/git.rs34
-rw-r--r--src/project/path.rs41
-rw-r--r--src/search.rs33
-rw-r--r--src/search/entry.rs9
9 files changed, 108 insertions, 80 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 666a223..42f9a59 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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()