aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs41
-rw-r--r--src/config.rs91
-rw-r--r--src/directories.rs52
-rw-r--r--src/directories/config.rs26
-rw-r--r--src/error.rs2
-rw-r--r--src/finder/config.rs61
-rw-r--r--src/finder/error.rs1
-rw-r--r--src/lib.rs4
-rw-r--r--src/main.rs11
-rw-r--r--src/paths.rs113
-rw-r--r--src/paths/config.rs107
-rw-r--r--src/paths/error.rs (renamed from src/directories/error.rs)2
12 files changed, 317 insertions, 194 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 77d9ec5..521809e 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,24 +2,32 @@ use clap::{Args, Parser};
use std::path::PathBuf;
use tracing_subscriber::{filter::LevelFilter, Layer, Registry};
+use crate::paths::PathEntry;
+
/// Simple program to manage projects and ssh hosts using tmux
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct Cli {
- /// Path to search recursively for directories
+ /// Path to directories
pub(crate) path: Vec<PathBuf>,
- /// Add additional directory to search results. Can be specified multiple times
- #[arg(short, long)]
- pub(crate) directory: Vec<PathBuf>,
-
- #[command(flatten)]
- pub verbose: Verbosity,
+ /// Max depth to recurse.
+ ///
+ /// By default, no limit is set. Setting to 0 will only use the supplied directory.
+ #[arg(short = 'd', long)]
+ pub(crate) max_depth: Option<usize>,
- /// Allows traversal into hidden directories when searching
+ /// Recurse into hidden directories.
+ ///
+ /// Include hidden directories when traversing directories. (default: hidden directories
+ /// are skipped). A Directory is considered to be hidden if its name starts with a `.`
+ /// sign (dot). If `max-depth` is set to 0, this has no effect (As no recursion happens).
#[arg(long)]
pub(crate) hidden: bool,
+ #[command(flatten)]
+ pub verbose: Verbosity,
+
/// Connect to ssh host
#[arg(short, long)]
pub ssh: Option<String>,
@@ -36,11 +44,18 @@ impl Cli {
}
// TODO: replace this with `impl Figment for Cli`
- pub fn as_config(&self) -> crate::directories::Config {
- crate::directories::Config {
- search: self.path.to_owned(),
- add: self.directory.to_owned(),
- hidden: self.hidden,
+ pub fn as_config(&self) -> crate::paths::Config {
+ crate::paths::Config {
+ paths: self
+ .path
+ .to_owned()
+ .into_iter()
+ .map(|p| PathEntry {
+ path: p,
+ hidden: self.hidden,
+ recurse: self.max_depth,
+ })
+ .collect(),
}
}
}
diff --git a/src/config.rs b/src/config.rs
index a2305f1..4ba59b4 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -8,7 +8,7 @@ use tracing_subscriber::{Layer, Registry};
pub struct Config {
pub log_enabled: bool,
pub log_file: PathBuf,
- pub paths: crate::directories::Config,
+ pub paths: crate::paths::Config,
pub finder: crate::finder::Config,
}
@@ -63,92 +63,3 @@ impl TryFrom<Figment> for Config {
value.extract().map_err(Into::into)
}
}
-
-#[cfg(test)]
-mod tests {
- use crate::{finder, directories};
-
- use super::*;
- use figment::providers::{Format, Serialized, Toml};
-
- #[test]
- fn defaults() {
- figment::Jail::expect_with(|jail| {
- jail.create_file(
- "tmuxr.toml",
- r#"
- log_enabled = false
-
- [paths]
- search = []
- add = []
- hidden = false
-
- [finder]
- program = "fzf-tmux"
- args = [
- "--",
- "--multi",
- "--print-query",
- "-d/",
- "--preview-window='right,75%,<80(up,75%,border-bottom)'",
- "--preview='sel={}; less ${sel:-{q}} 2>/dev/null'",
- ]
- "#,
- )?;
-
- let config: Config = Figment::from(Serialized::defaults(Config::default()))
- .merge(Toml::file("tmuxr.toml"))
- .extract()?;
-
- assert_eq!(config, Config::default());
-
- Ok(())
- });
- }
-
- #[test]
- fn custom() {
- figment::Jail::expect_with(|jail| {
- jail.create_file(
- "tmuxr.toml",
- r#"
- log_enabled = true
- log_file = "/tmp/tmuxr/test/tmuxr.log"
-
- [paths]
- search = [ "/tmp/tmuxr/test/projects" ]
- add = [ "/tmp/tmuxr/test/other_projects" ]
- hidden = true
-
- [finder]
- program = "fzf"
- args = ["-0", "-1", "--preview='cat'"]
- "#,
- )?;
-
- let config: Config = Figment::from(Serialized::defaults(Config::default()))
- .merge(Toml::file("tmuxr.toml"))
- .extract()?;
-
- assert_eq!(
- config,
- Config {
- paths: directories::Config {
- search: vec!["/tmp/tmuxr/test/projects".into()],
- add: vec!["/tmp/tmuxr/test/extra_project".into()],
- hidden: true,
- },
- finder: finder::Config {
- program: "fzf".into(),
- args: vec!["-0".into(), "-1".into(), "--preview='cat'".into()],
- },
- log_enabled: true,
- log_file: "/tmp/tmuxr/test/tmuxr.log".into()
- }
- );
-
- Ok(())
- });
- }
-}
diff --git a/src/directories.rs b/src/directories.rs
deleted file mode 100644
index 62a566f..0000000
--- a/src/directories.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use ignore::WalkBuilder;
-use std::{ops::Deref, path::PathBuf};
-
-pub use config::Config;
-pub use error::{Error, Result};
-
-mod config;
-mod error;
-
-#[derive(Debug, PartialEq, Eq, Default)]
-pub struct Directories {
- config: Config,
- pub directories: Vec<PathBuf>,
-}
-
-impl Directories {
- pub fn new(config: &Config) -> Directories {
- Directories {
- config: config.to_owned(),
- directories: config.add.to_owned(),
- }
- }
-
- pub fn walk(&mut self) -> Result<&mut Directories> {
- let mut dirs = self.config.search.iter().cloned();
-
- // Taking first element is neccissary due to requirement of an initial item
- // in the WalkBuilder API
- //
- // See: https://github.com/BurntSushi/ripgrep/issues/1761
- let first = dirs.next().unwrap_or_default();
- let mut walk = WalkBuilder::new(first);
- let walk = walk.standard_filters(true).max_depth(Some(1));
-
- let results = dirs
- .fold(walk, |walk, dir| walk.add(dir))
- .build()
- .map(|r| r.map(|d| d.into_path()))
- .collect::<std::result::Result<Vec<_>, _>>();
-
- self.directories.extend(results?);
- Ok(self)
- }
-}
-
-impl Deref for Directories {
- type Target = Vec<PathBuf>;
-
- fn deref(&self) -> &Self::Target {
- &self.directories
- }
-}
diff --git a/src/directories/config.rs b/src/directories/config.rs
deleted file mode 100644
index ec66d0c..0000000
--- a/src/directories/config.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider};
-use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
-pub struct Config {
- pub(crate) search: Vec<PathBuf>,
- pub(crate) add: Vec<PathBuf>,
- pub(crate) hidden: bool,
-}
-
-impl Config {
- pub fn from<T: Provider>(provider: T) -> figment::error::Result<Config> {
- Figment::from(provider).extract()
- }
-}
-
-impl Provider for Config {
- fn metadata(&self) -> Metadata {
- Metadata::named("Tmuxr directory config")
- }
-
- fn data(&self) -> figment::error::Result<value::Map<Profile, value::Dict>> {
- Serialized::defaults(Config::default()).data()
- }
-}
diff --git a/src/error.rs b/src/error.rs
index fe94978..efd7193 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,7 +9,7 @@ pub enum Error {
Config(#[from] figment::error::Error),
#[error("Directories error: {0:?}")]
- Directories(#[from] crate::directories::Error),
+ Directories(#[from] crate::paths::Error),
#[error("Finder error: {0:?}")]
Finder(#[from] crate::finder::Error),
diff --git a/src/finder/config.rs b/src/finder/config.rs
index bc39556..d0a0570 100644
--- a/src/finder/config.rs
+++ b/src/finder/config.rs
@@ -41,3 +41,64 @@ impl Provider for Config {
Serialized::defaults(Config::default()).data()
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use figment::providers::{Format, Serialized, Toml};
+
+ #[test]
+ fn defaults() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "tmuxr.toml",
+ r#"
+ program = "fzf-tmux"
+ args = [
+ "--",
+ "--multi",
+ "--print-query",
+ "-d/",
+ "--preview-window='right,75%,<80(up,75%,border-bottom)'",
+ "--preview='sel={}; less ${sel:-{q}} 2>/dev/null'",
+ ]
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(config, Config::default());
+
+ Ok(())
+ });
+ }
+
+ #[test]
+ fn custom() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "tmuxr.toml",
+ r#"
+ program = "fzf"
+ args = ["-0", "-1", "--preview='cat'"]
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(
+ config,
+ Config {
+ program: "fzf".into(),
+ args: vec!["-0".into(), "-1".into(), "--preview='cat'".into()],
+ }
+ );
+
+ Ok(())
+ });
+ }
+}
diff --git a/src/finder/error.rs b/src/finder/error.rs
index 46d39c4..181da21 100644
--- a/src/finder/error.rs
+++ b/src/finder/error.rs
@@ -8,4 +8,3 @@ pub enum Error {
#[error("Stdin error: Failed to get finder's stdin")]
Stdin,
}
-
diff --git a/src/lib.rs b/src/lib.rs
index d5e43db..66f5acb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,11 +1,11 @@
pub use crate::cli::Cli;
pub use crate::config::Config;
-pub use crate::directories::Directories;
pub use crate::error::{Error, Result};
pub use crate::finder::Finder;
+pub use crate::paths::Paths;
mod cli;
mod config;
-mod directories;
mod error;
mod finder;
+mod paths;
diff --git a/src/main.rs b/src/main.rs
index 5a6679e..ff29b97 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,10 @@
-use std::{fs::File, sync::Arc};
-
use clap::Parser;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
-use tmuxr::{Cli, Config, Directories, Finder, Result};
+use std::{fs::File, sync::Arc};
+use tmuxr::{Cli, Config, Finder, Paths, Result};
use tracing::info;
use tracing_subscriber::prelude::*;
@@ -50,12 +49,10 @@ fn init_subscriber(cli: &Cli, config: &Config) -> Result<()> {
#[tracing::instrument()]
pub fn run(config: &Config) -> Result<()> {
- let mut directories = Directories::new(&config.paths);
- directories.walk()?;
-
+ let directories = Paths::from(&config.paths);
let mut finder = Finder::new(&config.finder)?;
- finder.write_path_buf_vectored(directories.directories)?;
+ finder.write_path_buf_vectored(directories)?;
let output = finder.wait_with_output()?;
diff --git a/src/paths.rs b/src/paths.rs
new file mode 100644
index 0000000..de6a2dd
--- /dev/null
+++ b/src/paths.rs
@@ -0,0 +1,113 @@
+use ignore::{Walk, WalkBuilder};
+use std::{path::PathBuf, vec::IntoIter};
+use tracing::warn;
+
+pub use config::{Config, PathEntry};
+pub use error::Error;
+
+mod config;
+mod error;
+
+#[derive(Default)]
+pub struct Paths {
+ path_entries: Vec<PathEntry>,
+ paths_iter: Option<IntoIter<PathEntry>>,
+ iter: Option<Walk>,
+}
+
+impl Paths {
+ pub fn new(path_entries: Vec<PathEntry>) -> Self {
+ Self {
+ path_entries,
+ ..Default::default()
+ }
+ }
+}
+
+impl From<&Config> for Paths {
+ fn from(value: &Config) -> Self {
+ Paths {
+ path_entries: value.paths.to_owned(),
+ ..Default::default()
+ }
+ }
+}
+
+impl Iterator for Paths {
+ type Item = PathBuf;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.iter.as_mut().and_then(|iter| iter.next()) {
+ Some(Ok(d)) => return Some(d.into_path()),
+ Some(Err(err)) => warn!("{:?}", err),
+ None => match self.paths_iter.as_mut() {
+ Some(paths_iter) => {
+ let next = paths_iter.next()?;
+ self.iter = Some(
+ WalkBuilder::new(next.path)
+ .standard_filters(true)
+ .max_depth(next.recurse)
+ .hidden(next.hidden)
+ .build(),
+ );
+ }
+ None => self.paths_iter = Some(self.path_entries.to_owned().into_iter()),
+ },
+ };
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs;
+
+ use super::*;
+
+ #[test]
+ fn test_iteration() {
+ let test_dir = tempfile::Builder::new()
+ .tempdir()
+ .expect("Failed to create tmp directory");
+
+ let test_path = test_dir.path().to_owned();
+
+ let mut projects = Vec::from([
+ test_path.join("projects"),
+ test_path.join("projects/project1"),
+ test_path.join("projects/project2"),
+ test_path.join("project3"),
+ test_path.join("other_projects/project3"),
+ ]);
+
+ projects.iter().for_each(|project| {
+ fs::create_dir_all(project).expect("Failed to create test project directory");
+ });
+
+ let directories = Paths::new(Vec::from([
+ PathEntry {
+ path: test_path.join("projects"),
+ hidden: false,
+ recurse: Some(1),
+ },
+ PathEntry {
+ path: test_path.join("project3"),
+ hidden: false,
+ recurse: Some(0),
+ },
+ PathEntry {
+ path: test_path.join("other_projects/project3"),
+ hidden: false,
+ recurse: Some(0),
+ },
+ ]));
+
+ let mut actual = directories.into_iter().collect::<Vec<PathBuf>>();
+
+ projects.sort();
+ actual.sort();
+
+ assert_eq!(projects, actual);
+ }
+}
diff --git a/src/paths/config.rs b/src/paths/config.rs
new file mode 100644
index 0000000..72c5da7
--- /dev/null
+++ b/src/paths/config.rs
@@ -0,0 +1,107 @@
+use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider};
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ pub(crate) paths: Vec<PathEntry>,
+}
+
+impl Config {
+ pub fn from<T: Provider>(provider: T) -> figment::error::Result<Config> {
+ Figment::from(provider).extract()
+ }
+}
+
+impl Provider for Config {
+ fn metadata(&self) -> Metadata {
+ Metadata::named("Tmuxr path config")
+ }
+
+ fn data(&self) -> figment::error::Result<value::Map<Profile, value::Dict>> {
+ Serialized::defaults(Config::default()).data()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+pub struct PathEntry {
+ pub path: PathBuf,
+ pub hidden: bool,
+ pub recurse: Option<usize>,
+}
+
+impl std::str::FromStr for PathEntry {
+ type Err = std::convert::Infallible;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(PathEntry {
+ path: s.to_string().into(),
+ ..Default::default()
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use figment::providers::{Format, Serialized, Toml};
+
+ #[test]
+ fn defaults() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "tmuxr.toml",
+ r#"
+ paths = []
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(config, Config::default());
+
+ Ok(())
+ });
+ }
+
+ #[test]
+ fn custom() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "tmuxr.toml",
+ r#"
+ paths = [
+ "/tmp/projects",
+ { path = "/tmp/tmuxr/test/other_projects", recursive = false, hidden = true },
+ ]
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(
+ config,
+ Config {
+ paths: Vec::from([
+ PathEntry {
+ path: "/tmp/tmuxr/test/project_1".into(),
+ hidden: false,
+ recurse: None,
+ },
+ PathEntry {
+ path: "/tmp/tmuxr/test/projects".into(),
+ hidden: false,
+ recurse: Some(1),
+ }
+ ]),
+ }
+ );
+
+ Ok(())
+ });
+ }
+}
diff --git a/src/directories/error.rs b/src/paths/error.rs
index 8509b08..13a69ea 100644
--- a/src/directories/error.rs
+++ b/src/paths/error.rs
@@ -1,5 +1,3 @@
-pub type Result<T> = std::result::Result<T, Error>;
-
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Ignore error: {0:?}")]