summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs39
-rw-r--r--src/config.rs247
-rw-r--r--src/error.rs9
-rw-r--r--src/lib.rs22
-rw-r--r--src/main.rs4
5 files changed, 239 insertions, 82 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 5da1036..68bc665 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,22 +1,29 @@
-use crate::{Config, Result};
+use crate::{config::Paths, Config, Result};
use clap::{Args, Parser};
-use std::fs::File;
-use std::sync::Arc;
+use std::path::PathBuf;
use tracing_subscriber::{filter::LevelFilter, Layer, Registry};
/// 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
+ 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,
+ /// Allows traversal into hidden directories when searching
+ #[arg(long)]
+ pub(crate) hidden: bool,
+
/// Connect to ssh host
#[arg(short, long)]
pub ssh: Option<String>,
-
- #[command(flatten)]
- pub config: Config,
}
impl Cli {
@@ -30,16 +37,20 @@ impl Cli {
layers.push(fmt_layer);
- if self.config.enable_logging {
- let file = File::create(&self.config.log_file)?;
- let log_layer = tracing_subscriber::fmt::layer()
- .with_writer(Arc::new(file))
- .boxed();
- layers.push(log_layer);
- };
-
Ok(layers)
}
+
+ pub fn as_config(&self) -> Config {
+ Config {
+ paths: Paths {
+ search: self.path.to_owned(),
+ add: self.directory.to_owned(),
+ hidden: self.hidden,
+ },
+ finder: Default::default(),
+ logging: Default::default(),
+ }
+ }
}
#[derive(Debug, Default, Args)]
diff --git a/src/config.rs b/src/config.rs
index 42614ec..2c61b88 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,64 +1,99 @@
use crate::{Error, Result};
-use clap::{Args, Parser};
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
+use ignore::WalkBuilder;
use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
+use std::{fmt::Display, fs::File, path::PathBuf, sync::Arc};
+use tracing::{info, warn};
+use tracing_subscriber::{Layer, Registry};
-/// Simple program to manage projects and ssh hosts using tmux
-#[derive(Debug, Parser, Serialize, Deserialize)]
-#[command(author, version, about)]
+#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Config {
- /// Path to search for directories
- pub(crate) path: Vec<PathBuf>,
-
- /// Stand alone directory. Can be specified multiple times
- #[arg(short, long)]
- pub(crate) project: Vec<PathBuf>,
-
- /// Allows traversal into hidden directories when searching
- #[arg(long)]
- pub(crate) hidden: bool,
-
- #[arg(skip)]
+ pub(crate) paths: Paths,
pub(crate) finder: Finder,
-
- #[arg(skip)]
- pub(crate) enable_logging: bool,
-
- #[arg(skip)]
- pub(crate) log_file: PathBuf,
+ pub(crate) logging: Logging,
}
impl Config {
- pub fn extract(&self) -> Result<Config> {
+ pub fn extract(self) -> Result<Config> {
Figment::from(Serialized::defaults(self))
.merge(Toml::file("tmuxr.toml"))
.merge(Env::prefixed("TMUXR_"))
.extract()
.map_err(Error::from)
}
+
+ pub fn as_layer(&self) -> Result<Vec<Box<dyn Layer<Registry> + Send + Sync>>> {
+ let mut layers = Vec::new();
+
+ if self.logging.enabled {
+ let file = File::create(&self.logging.path)?;
+ let log_layer = tracing_subscriber::fmt::layer()
+ .with_writer(Arc::new(file))
+ .boxed();
+ layers.push(log_layer);
+ };
+
+ Ok(layers)
+ }
}
-impl Default for Config {
- fn default() -> Self {
- Self {
- path: Default::default(),
- project: Default::default(),
- hidden: Default::default(),
- finder: Default::default(),
- enable_logging: Default::default(),
- log_file: dirs::cache_dir()
- .map(|p| p.join("tmuxr"))
- .unwrap_or_default()
- .join("tmuxr.log"),
- }
+#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+pub(crate) struct Paths {
+ pub(crate) search: Vec<PathBuf>,
+ pub(crate) add: Vec<PathBuf>,
+ pub(crate) hidden: bool,
+}
+
+impl Display for Paths {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Vec::<PathBuf>::from(self)
+ .into_iter()
+ .filter_map(|p: PathBuf| {
+ p.to_str().map(|s| s.to_owned()).or_else(|| {
+ warn!("Failed to convert path to str");
+ None
+ })
+ })
+ .map(|s| writeln!(f, "{}", s))
+ .collect::<std::fmt::Result>()
+ }
+}
+
+impl From<&Paths> for Vec<PathBuf> {
+ fn from(value: &Paths) -> Self {
+ let mut dirs = value.search.to_owned().into_iter();
+ let first = dirs.nth(0).unwrap_or_default();
+
+ let mut path_bufs = dirs
+ .fold(
+ WalkBuilder::new(first)
+ .standard_filters(true)
+ .max_depth(Some(1)),
+ |walk, dir| walk.add(dir),
+ )
+ .build()
+ .filter_map(|res| {
+ res.map_err(|err| {
+ warn!("Error while walking directory: {:?}", err);
+ err
+ })
+ .ok()
+ })
+ .map(|entry| entry.into_path())
+ .collect::<Vec<PathBuf>>();
+
+ info!("{:?}", value.add);
+ info!("{:?}", path_bufs);
+
+ path_bufs.extend(value.add.to_owned());
+ path_bufs
}
}
-#[derive(Debug, Args, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct Finder {
pub(crate) program: String,
pub(crate) args: Vec<String>,
@@ -69,46 +104,126 @@ impl Default for Finder {
Self {
program: "fzf-tmux".into(),
args: vec![
- "--".into(),
- "--multi".into(),
- "--print-query".into(),
- "-d/".into(),
- "--preview-window='right,75%,<80(up,75%,border-bottom)'".into(),
- "--preview='sel={}; less ${sel:-{q}} 2>/dev/null'".into(),
- ],
+ "--",
+ "--multi",
+ "--print-query",
+ "-d/",
+ "--preview-window='right,75%,<80(up,75%,border-bottom)'",
+ "--preview='sel={}; less ${sel:-{q}} 2>/dev/null'",
+ ]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
}
}
}
-#[derive(Debug, Args, Serialize, Deserialize)]
-pub(crate) struct Directory {
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub(crate) struct Logging {
+ pub(crate) enabled: bool,
pub(crate) path: PathBuf,
- pub(crate) root: bool,
- pub(crate) hidden: bool,
-}
-
-#[derive(Debug, Default)]
-pub(crate) struct Directories {
- search_dirs: Vec<PathBuf>,
- root_dirs: Vec<PathBuf>,
}
-impl From<Vec<Directory>> for Directories {
- fn from(value: Vec<Directory>) -> Self {
- value.iter().fold(Directories::default(), |mut acc, d| {
- match d.root {
- true => acc.root_dirs.push(d.path.to_owned()),
- false => acc.search_dirs.push(d.path.to_owned()),
- };
- acc
- })
+impl Default for Logging {
+ fn default() -> Self {
+ Self {
+ enabled: Default::default(),
+ path: dirs::cache_dir()
+ .map(|p| p.join("tmuxr"))
+ .unwrap_or_default()
+ .join("tmuxr.log"),
+ }
}
}
#[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]
+ 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'",
+ ]
+
+ [logging]
+ enabled = true
+ path = "/tmp/tmuxr/test/tmuxr.log"
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(config, Config::default());
+
+ Ok(())
+ });
+ }
+
#[test]
- fn test_start() {
- assert_eq!(1, 1);
+ fn custom() {
+ figment::Jail::expect_with(|jail| {
+ jail.create_file(
+ "tmuxr.toml",
+ r#"
+ [paths]
+ search = [ "/tmp/tmuxr/test/projects" ]
+ add = [ "/tmp/tmuxr/test/other_projects" ]
+ hidden = true
+
+ [finder]
+ program = "fzf"
+ args = ["-0", "-1", "--preview='cat'"]
+
+ [logging]
+ enabled = true
+ log_file = "/tmp/tmuxr/test/tmuxr.log"
+ "#,
+ )?;
+
+ let config: Config = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .extract()?;
+
+ assert_eq!(
+ config,
+ Config {
+ paths: Paths {
+ search: vec!["/tmp/tmuxr/test/projects".into()],
+ add: vec!["/tmp/tmuxr/test/extra_project".into()],
+ hidden: true,
+ },
+ finder: Finder {
+ program: "fzf".into(),
+ args: vec!["-0".into(), "-1".into(), "--preview='cat'".into()],
+ },
+ logging: Logging {
+ enabled: true,
+ path: "/tmp/tmuxr/test/tmuxr.log".into()
+ },
+ }
+ );
+
+ Ok(())
+ });
}
}
diff --git a/src/error.rs b/src/error.rs
index e423493..5b0fea8 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -7,4 +7,13 @@ pub enum Error {
#[error("Config error: {0:?}")]
Config(#[from] figment::error::Error),
+
+ #[error("Ignore error: {0:?}")]
+ Ignore(#[from] ignore::Error),
+
+ #[error("Process error: {0:?}")]
+ Finder(String),
+
+ #[error("Path error: {0:?}")]
+ Path(String),
}
diff --git a/src/lib.rs b/src/lib.rs
index 872ca50..acaa475 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,9 @@
+use std::{
+ io::Write,
+ process::{Command, Stdio},
+};
+use tracing::info;
+
pub use crate::cli::Cli;
pub use crate::config::Config;
pub use crate::error::{Error, Result};
@@ -8,5 +14,21 @@ mod error;
#[tracing::instrument()]
pub fn run(config: &Config) -> Result<()> {
+ let mut finder = Command::new(&config.finder.program)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .args(&config.finder.args)
+ .spawn()?;
+
+ finder
+ .stdin
+ .as_mut()
+ .ok_or_else(|| Error::Finder("Failed to get finder's stdin".to_string()))?
+ .write_all(config.paths.to_string().as_bytes())?;
+
+ let output = finder.wait_with_output()?;
+
+ info!("{:?}", output);
+
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index 40fccd6..c9b21fd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,10 @@
use clap::Parser;
-use tmuxr::{Cli, Result};
+use tmuxr::{Cli, Config, Result};
use tracing_subscriber::prelude::*;
fn main() -> Result<()> {
let cli = Cli::parse();
- let config = cli.config.extract()?;
+ let config = Config::extract(cli.as_config())?;
tracing_subscriber::registry().with(cli.as_layer()?).init();