summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2022-11-28 16:09:29 -0600
committerToby Vincent <tobyv13@gmail.com>2022-11-28 16:09:29 -0600
commitda884530e2b3e0b9a5bef9abcf683a970b93bd6b (patch)
tree84653f0fc4e643bb2d2c6d6a1eff79fdd94fbf07
parentf7eeef26d5a251c2a925d18d288fec2fa205f59d (diff)
feat: add git and preview (default) features
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml9
-rw-r--r--src/lib.rs7
-rw-r--r--src/main.rs4
-rw-r--r--src/project.rs55
-rw-r--r--src/project/error.rs1
-rw-r--r--src/project/git.rs101
-rw-r--r--src/project/path.rs78
-rw-r--r--src/search.rs2
-rw-r--r--src/search/entry.rs75
-rw-r--r--src/search/entry/config.rs14
11 files changed, 188 insertions, 170 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4fb9173..97198bc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2405,7 +2405,6 @@ dependencies = [
"tmux_interface",
"tracing",
"tracing-subscriber",
- "typed-builder",
]
[[package]]
@@ -3094,17 +3093,6 @@ dependencies = [
]
[[package]]
-name = "typed-builder"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47a126a40dbff39e8320900cd61b8de053a2706e1f782cd27145792feb8fd41e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 422dc61..f77632f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,9 +14,9 @@ anyhow = "1.0.66"
clap = { version = "4.0.18", features = ["derive", "env"] }
dirs = "4.0.0"
figment = { version = "0.10.8", features = ["toml", "env", "test"] }
-git2 = { version = "0.15.0", default-features = false }
+git2 = { version = "0.15.0", default-features = false, optional = true }
ignore = "0.4.18"
-onefetch = "2.14.2"
+onefetch = { version = "2.14.2", optional = true }
pretty_assertions = "1.3.0"
serde = { version = "1.0.147", features = ["derive"] }
sled = "0.34.7"
@@ -30,3 +30,8 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
[dev-dependencies]
tempfile = "3.3.0"
+
+[features]
+default = ["git", "preview"]
+git = ["dep:git2"]
+preview = ["dep:onefetch"]
diff --git a/src/lib.rs b/src/lib.rs
index 87cc348..687cb44 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,11 +1,10 @@
pub use crate::cli::Cli;
pub use crate::config::Config;
pub use crate::error::{Error, Result};
-pub use crate::project::Project;
-pub use crate::search::{entry, Search};
+
+pub mod project;
+pub mod search;
mod cli;
mod config;
mod error;
-mod project;
-mod search;
diff --git a/src/main.rs b/src/main.rs
index a8bba5f..877cfaa 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, Search};
+use projectr::{search::Search, Cli, Config};
#[tracing::instrument]
fn main() -> Result<()> {
@@ -29,7 +29,7 @@ pub fn run(config: &Config) -> Result<()> {
.context("Failed to extract paths config")?
.collect();
- projects.sort_unstable_by_key(|p| p.timestamp().unwrap_or_default());
+ projects.sort_unstable_by_key(|p| *p.timestamp());
for project in projects {
println!("{}", project.to_path_buf().to_string_lossy())
diff --git a/src/project.rs b/src/project.rs
index e6faf66..5486dc1 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -1,19 +1,15 @@
-use std::{
- io::Write,
- path::PathBuf,
- process::{Command, Stdio},
- time::{Duration, SystemTime},
-};
+use std::{path::PathBuf, time::Duration};
pub use self::error::Error;
-pub use self::git::GitProject;
-pub use self::path::PathProject;
mod error;
-mod git;
-mod path;
+pub mod path;
-pub type ProjectItem = Box<dyn Project<Error = Error>>;
+#[cfg(feature = "git")]
+pub mod git;
+
+
+pub type ProjectItem = Box<dyn Project>;
pub trait Project: Timestamp {
fn to_path_buf(&self) -> &PathBuf;
@@ -29,47 +25,26 @@ where
}
}
-pub trait Timestamp {
- type Error;
+pub trait ProjectParser {
+ fn parse_project(&self, path_buf: PathBuf) -> Option<ProjectItem>;
+}
- fn timestamp(&self) -> Result<Duration, Self::Error>;
+pub trait Timestamp {
+ fn timestamp(&self) -> &Duration;
}
impl<T> Timestamp for T
where
- T: AsRef<PathBuf>,
+ T: AsRef<Duration>,
{
- type Error = Error;
-
- fn timestamp(&self) -> Result<Duration, Self::Error> {
+ fn timestamp(&self) -> &Duration {
self.as_ref()
- .metadata()?
- .modified()?
- .duration_since(SystemTime::UNIX_EPOCH)
- .map_err(Into::into)
}
}
+#[cfg(feature = "preview")]
pub trait Preview {
type Error;
fn preview(&self) -> Result<(), Self::Error>;
}
-
-impl<T> Preview for T
-where
- T: AsRef<PathBuf>,
-{
- type Error = std::io::Error;
-
- fn preview(&self) -> Result<(), Self::Error> {
- let output = Command::new("ls")
- .arg("-l")
- .arg("-a")
- .arg(self.to_path_buf())
- .stdout(Stdio::piped())
- .output()?;
-
- std::io::stdout().write_all(&output.stdout)
- }
-}
diff --git a/src/project/error.rs b/src/project/error.rs
index ddfcb2a..2e9f4a3 100644
--- a/src/project/error.rs
+++ b/src/project/error.rs
@@ -3,6 +3,7 @@ pub enum Error {
#[error(transparent)]
IO(#[from] std::io::Error),
+ #[cfg(feature = "git")]
#[error(transparent)]
Git(#[from] git2::Error),
diff --git a/src/project/git.rs b/src/project/git.rs
index 876af1c..efe4ade 100644
--- a/src/project/git.rs
+++ b/src/project/git.rs
@@ -2,71 +2,84 @@ use git2::{BranchType, Repository};
use ignore::DirEntry;
use onefetch::ui::printer::Printer;
use std::io;
-use std::{cmp::Ordering, path::PathBuf, time::Duration};
+use std::{path::PathBuf, time::Duration};
+use tracing::{debug, warn};
-use crate::project::Error;
-use crate::project::Preview;
-use crate::project::Timestamp;
-use crate::Project;
+use super::{Error, Preview, ProjectParser, Timestamp};
+
+#[derive(Debug, Clone)]
+pub struct GitMatcher;
+
+impl ProjectParser for GitMatcher {
+ #[tracing::instrument]
+ fn parse_project(&self, path_buf: PathBuf) -> Option<super::ProjectItem> {
+ match GitProject::new(path_buf) {
+ Ok(g) => Some(Box::new(g)),
+ Err(err) => {
+ debug!(%err, "Failed to create git project");
+ None
+ }
+ }
+ }
+}
#[derive(Debug, Clone)]
pub struct GitProject {
path_buf: PathBuf,
- latest_commit: Option<Duration>,
+ latest_commit: Duration,
}
impl GitProject {
fn new(path_buf: PathBuf) -> Result<Self, Error> {
let repo = Repository::open(&path_buf)?;
+ let latest_commit = Self::get_timestamp(&repo);
Ok(Self {
path_buf,
- latest_commit: Self::latest_commit(&repo).ok(),
+ latest_commit,
})
}
- fn latest_commit(repository: &Repository) -> Result<Duration, Error> {
- let mut branches = repository.branches(Some(BranchType::Local))?;
- branches
- .try_fold(0, |latest, branch| {
- let (branch, _) = branch?;
-
- let name = branch
- .name()?
- .ok_or_else(|| git2::Error::from_str("Failed to find branch"))?;
-
- repository
- .revparse_single(name)?
- .peel_to_commit()
- .map(|c| (c.time().seconds() as u64).max(latest))
- })
- .map(Duration::from_secs)
- .map_err(Into::into)
+ fn get_timestamp(repo: &Repository) -> Duration {
+ match Self::latest_commit(repo) {
+ Ok(s) => Duration::from_secs(s),
+ Err(err) => {
+ warn!(%err, "Failed to get latest commit from repository");
+ Duration::default()
+ }
+ }
}
-}
-impl Timestamp for GitProject {
- type Error = Error;
+ fn latest_commit(repository: &Repository) -> Result<u64, git2::Error> {
+ let mut branches = repository.branches(Some(BranchType::Local))?;
+ branches.try_fold(0, |latest, branch| {
+ let (branch, _) = branch?;
- fn timestamp(&self) -> Result<Duration, Self::Error> {
- self.latest_commit.ok_or(Error::Git(git2::Error::from_str(
- "Failed to get latest commit",
- )))
+ let name = branch
+ .name()?
+ .ok_or_else(|| git2::Error::from_str("Failed to find branch"))?;
+
+ repository
+ .revparse_single(name)?
+ .peel_to_commit()
+ .map(|c| (c.time().seconds() as u64).max(latest))
+ })
}
}
-impl Project for GitProject {
- fn to_path_buf(&self) -> &PathBuf {
- &self.path_buf
+impl Timestamp for GitProject {
+ fn timestamp(&self) -> &Duration {
+ &self.latest_commit
}
}
+#[cfg(feature = "preview")]
impl Preview for GitProject {
type Error = Error;
fn preview(&self) -> Result<(), Self::Error> {
- // onefetch --include-hidden --show-logo=auto
let config = onefetch::cli::Config {
input: self.path_buf.to_owned(),
+ include_hidden: true,
..Default::default()
};
@@ -77,21 +90,9 @@ impl Preview for GitProject {
}
}
-impl PartialEq for GitProject {
- fn eq(&self, other: &Self) -> bool {
- match (self.latest_commit, other.latest_commit) {
- (Some(time), Some(other_time)) => time.eq(&other_time),
- _ => false,
- }
- }
-}
-
-impl PartialOrd for GitProject {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- match (self.latest_commit, other.latest_commit) {
- (Some(time), Some(other_time)) => time.partial_cmp(&other_time),
- _ => None,
- }
+impl AsRef<PathBuf> for GitProject {
+ fn as_ref(&self) -> &PathBuf {
+ &self.path_buf
}
}
diff --git a/src/project/path.rs b/src/project/path.rs
index 14ce308..03bcac6 100644
--- a/src/project/path.rs
+++ b/src/project/path.rs
@@ -1,12 +1,39 @@
-use ignore::DirEntry;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
+use std::time::{Duration, SystemTime};
+use tracing::debug;
+
+use super::{ProjectItem, ProjectParser};
+
+#[derive(Debug, Clone)]
+pub struct PathMatcher(pub String);
+
+impl ProjectParser for PathMatcher {
+ #[tracing::instrument]
+ fn parse_project(&self, path_buf: PathBuf) -> Option<ProjectItem> {
+ if path_buf.join(&self.0).exists() {
+ Some(Box::new(PathProject::new(path_buf)))
+ } else {
+ debug!("Failed to match pattern in directory");
+ None
+ }
+ }
+}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct PathProject(PathBuf);
+pub struct PathProject(PathBuf, Duration);
impl PathProject {
- fn new(path_buf: PathBuf) -> Self {
- Self(path_buf)
+ pub fn new(path_buf: PathBuf) -> Self {
+ let modified = Self::get_modified(&path_buf).unwrap_or_default();
+ Self(path_buf, modified)
+ }
+
+ fn get_modified(path_buf: &Path) -> Result<Duration, std::io::Error> {
+ path_buf
+ .metadata()?
+ .modified()?
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string()))
}
}
@@ -16,32 +43,27 @@ impl AsRef<PathBuf> for PathProject {
}
}
-impl TryFrom<(&String, DirEntry)> for PathProject {
- type Error = String;
-
- fn try_from((pattern, dir_entry): (&String, DirEntry)) -> Result<Self, Self::Error> {
- dir_entry
- .path()
- .join(pattern)
- .exists()
- .then(|| Self::from(dir_entry.to_owned()))
- .ok_or_else(|| {
- format!(
- "Pattern `{:?}` not found in path: `{:?}`",
- pattern, dir_entry
- )
- })
+impl AsRef<Duration> for PathProject {
+ fn as_ref(&self) -> &Duration {
+ &self.1
}
}
-impl From<DirEntry> for PathProject {
- fn from(value: DirEntry) -> Self {
- Self::new(value.into_path())
- }
-}
+#[cfg(feature = "preview")]
+impl super::Preview for PathProject {
+ type Error = std::io::Error;
+
+ fn preview(&self) -> Result<(), Self::Error> {
+ use std::io::Write;
+ use std::process::{Command, Stdio};
+
+ let output = Command::new("ls")
+ .arg("-l")
+ .arg("-a")
+ .arg(&self.0)
+ .stdout(Stdio::piped())
+ .output()?;
-impl From<PathProject> for PathBuf {
- fn from(value: PathProject) -> Self {
- value.0
+ std::io::stdout().write_all(&output.stdout)
}
}
diff --git a/src/search.rs b/src/search.rs
index 2aff5ae..b2db0e3 100644
--- a/src/search.rs
+++ b/src/search.rs
@@ -109,7 +109,7 @@ mod tests {
let mut results = paths.into_iter().collect::<Vec<ProjectItem>>();
- results.sort_unstable_by_key(|p| p.timestamp().unwrap_or_default());
+ results.sort_unstable_by_key(|p| *p.timestamp());
let results = results
.into_iter()
diff --git a/src/search/entry.rs b/src/search/entry.rs
index 778c75c..1e49a67 100644
--- a/src/search/entry.rs
+++ b/src/search/entry.rs
@@ -1,49 +1,90 @@
-use ignore::{DirEntry, Walk};
-use tracing::error;
+use ignore::{Walk, WalkBuilder};
+use tracing::{error, warn};
-use crate::project::{GitProject, PathProject, ProjectItem};
+use crate::{
+ project::{path::PathMatcher, ProjectParser},
+ search::ProjectItem,
+};
pub use config::Config;
mod config;
pub struct Entry {
- config: Config,
+ path_parser: Option<PathMatcher>,
+
+ #[cfg(feature = "git")]
+ git_parser: Option<crate::project::git::GitMatcher>,
+
iter: Walk,
}
impl std::fmt::Debug for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("SearchPath")
- .field("config", &self.config)
- .finish()
+ let mut debug = f.debug_struct("Entry");
+ debug.field("path_matcher", &self.path_parser);
+
+ #[cfg(feature = "git")]
+ debug.field("git_matcher", &self.git_parser);
+
+ debug.finish()
}
}
impl Entry {
- pub fn parse_dir_entry(&self, dir_entry: DirEntry) -> Option<ProjectItem> {
- if self.config.git {
- if let Ok(git) = GitProject::try_from(dir_entry.to_owned()) {
- return Some(Box::new(git));
- };
+ fn new(config: &Config) -> Self {
+ let iter = WalkBuilder::new(&config.path_buf)
+ .standard_filters(true)
+ .max_depth(config.max_depth)
+ .hidden(!config.hidden)
+ .build();
+
+ Self {
+ iter,
+ path_parser: config.pattern.as_ref().map(|s| PathMatcher(s.to_owned())),
+
+ #[cfg(feature = "git")]
+ git_parser: config.git.then_some(crate::project::git::GitMatcher),
+ }
+ }
+}
+
+impl ProjectParser for Entry {
+ #[tracing::instrument]
+ fn parse_project(&self, path_buf: std::path::PathBuf) -> Option<ProjectItem> {
+ #[cfg(feature = "git")]
+ if let Some(p) = self
+ .git_parser
+ .as_ref()
+ .and_then(|m| m.parse_project(path_buf.to_owned()))
+ {
+ return Some(p);
};
- if let Some(pattern) = &self.config.pattern {
- if let Ok(proj) = PathProject::try_from((pattern, dir_entry)) {
- return Some(Box::new(proj));
- };
+ if let Some(p) = self
+ .path_parser
+ .as_ref()
+ .and_then(|m| m.parse_project(path_buf))
+ {
+ return Some(p);
};
None
}
}
+impl From<Config> for Entry {
+ fn from(config: Config) -> Self {
+ Self::new(&config)
+ }
+}
+
impl Iterator for Entry {
type Item = ProjectItem;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next()? {
- Ok(dir_entry) => self.parse_dir_entry(dir_entry),
+ Ok(dir_entry) => self.parse_project(dir_entry.into_path()),
Err(err) => {
error!(%err, "Ignoring errored path");
None
diff --git a/src/search/entry/config.rs b/src/search/entry/config.rs
index d325b58..24c7971 100644
--- a/src/search/entry/config.rs
+++ b/src/search/entry/config.rs
@@ -1,9 +1,6 @@
-use ignore::WalkBuilder;
use serde::{Deserialize, Deserializer, Serialize};
use std::{convert::Infallible, path::PathBuf, str::FromStr};
-use super::Entry;
-
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize)]
#[serde(default)]
pub struct Config {
@@ -14,17 +11,6 @@ pub struct Config {
pub pattern: Option<String>,
}
-impl From<Config> for Entry {
- fn from(config: Config) -> Self {
- let iter = WalkBuilder::new(&config.path_buf)
- .standard_filters(true)
- .max_depth(config.max_depth)
- .hidden(!config.hidden)
- .build();
- Self { iter, config }
- }
-}
-
impl From<PathBuf> for Config {
fn from(path_buf: PathBuf) -> Self {
Self {