aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cli.rs28
-rw-r--r--src/config.rs159
-rw-r--r--src/directories.rs61
-rw-r--r--src/directories/config.rs26
-rw-r--r--src/directories/error.rs17
-rw-r--r--src/error.rs12
-rw-r--r--src/finder.rs15
-rw-r--r--src/finder/config.rs43
-rw-r--r--src/lib.rs12
-rw-r--r--src/main.rs40
10 files changed, 271 insertions, 142 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 68bc665..77d9ec5 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,4 +1,3 @@
-use crate::{config::Paths, Config, Result};
use clap::{Args, Parser};
use std::path::PathBuf;
use tracing_subscriber::{filter::LevelFilter, Layer, Registry};
@@ -27,28 +26,21 @@ pub struct Cli {
}
impl Cli {
- pub fn as_layer(&self) -> Result<Vec<Box<dyn Layer<Registry> + Send + Sync>>> {
- let mut layers = Vec::new();
-
+ pub fn as_layer(&self) -> Vec<Box<dyn Layer<Registry> + Send + Sync>> {
let fmt_layer = tracing_subscriber::fmt::layer()
.pretty()
- .with_filter(self.verbose.into_filter())
+ .with_filter(self.verbose.as_filter())
.boxed();
- layers.push(fmt_layer);
-
- Ok(layers)
+ vec![fmt_layer]
}
- 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(),
+ // 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,
}
}
}
@@ -65,7 +57,7 @@ pub struct Verbosity {
}
impl Verbosity {
- pub fn into_filter(&self) -> LevelFilter {
+ pub fn as_filter(&self) -> LevelFilter {
self.into()
}
}
diff --git a/src/config.rs b/src/config.rs
index 2c61b88..a2305f1 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,35 +1,27 @@
use crate::{Error, Result};
-use figment::{
- providers::{Env, Format, Serialized, Toml},
- Figment,
-};
-use ignore::WalkBuilder;
+use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider};
use serde::{Deserialize, Serialize};
-use std::{fmt::Display, fs::File, path::PathBuf, sync::Arc};
-use tracing::{info, warn};
+use std::{fs::File, path::PathBuf, sync::Arc};
use tracing_subscriber::{Layer, Registry};
-#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Config {
- pub(crate) paths: Paths,
- pub(crate) finder: Finder,
- pub(crate) logging: Logging,
+ pub log_enabled: bool,
+ pub log_file: PathBuf,
+ pub paths: crate::directories::Config,
+ pub finder: crate::finder::Config,
}
impl 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 from<T: Provider>(provider: T) -> figment::error::Result<Config> {
+ Figment::from(provider).extract()
}
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)?;
+ if self.log_enabled {
+ let file = File::create(&self.log_file)?;
let log_layer = tracing_subscriber::fmt::layer()
.with_writer(Arc::new(file))
.boxed();
@@ -40,104 +32,42 @@ impl Config {
}
}
-#[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 Default for Config {
+ fn default() -> Self {
+ Self {
+ paths: Default::default(),
+ finder: Default::default(),
+ log_enabled: Default::default(),
+ log_file: dirs::cache_dir()
+ .map(|p| p.join("tmuxr"))
+ .unwrap_or_default()
+ .join("tmuxr.log"),
+ }
}
}
-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
+impl Provider for Config {
+ fn metadata(&self) -> Metadata {
+ Metadata::named("Tmuxr directory config")
}
-}
-
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub(crate) struct Finder {
- pub(crate) program: String,
- pub(crate) args: Vec<String>,
-}
-impl Default for Finder {
- fn default() -> Self {
- Self {
- program: "fzf-tmux".into(),
- args: vec![
- "--",
- "--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(),
- }
+ fn data(&self) -> figment::error::Result<value::Map<Profile, value::Dict>> {
+ Serialized::defaults(Config::default()).data()
}
}
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub(crate) struct Logging {
- pub(crate) enabled: bool,
- pub(crate) path: PathBuf,
-}
+impl TryFrom<Figment> for Config {
+ type Error = Error;
-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"),
- }
+ fn try_from(value: Figment) -> Result<Self> {
+ value.extract().map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
+ use crate::{finder, directories};
+
use super::*;
use figment::providers::{Format, Serialized, Toml};
@@ -147,6 +77,8 @@ mod tests {
jail.create_file(
"tmuxr.toml",
r#"
+ log_enabled = false
+
[paths]
search = []
add = []
@@ -162,10 +94,6 @@ mod tests {
"--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"
"#,
)?;
@@ -185,6 +113,9 @@ mod tests {
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" ]
@@ -193,10 +124,6 @@ mod tests {
[finder]
program = "fzf"
args = ["-0", "-1", "--preview='cat'"]
-
- [logging]
- enabled = true
- log_file = "/tmp/tmuxr/test/tmuxr.log"
"#,
)?;
@@ -207,19 +134,17 @@ mod tests {
assert_eq!(
config,
Config {
- paths: Paths {
+ paths: directories::Config {
search: vec!["/tmp/tmuxr/test/projects".into()],
add: vec!["/tmp/tmuxr/test/extra_project".into()],
hidden: true,
},
- finder: Finder {
+ finder: finder::Config {
program: "fzf".into(),
args: vec!["-0".into(), "-1".into(), "--preview='cat'".into()],
},
- logging: Logging {
- enabled: true,
- path: "/tmp/tmuxr/test/tmuxr.log".into()
- },
+ log_enabled: true,
+ log_file: "/tmp/tmuxr/test/tmuxr.log".into()
}
);
diff --git a/src/directories.rs b/src/directories.rs
new file mode 100644
index 0000000..ad3f3dc
--- /dev/null
+++ b/src/directories.rs
@@ -0,0 +1,61 @@
+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 {
+ directories: Vec<PathBuf>,
+ config: Config,
+}
+
+impl Directories {
+ pub(crate) fn new(config: &Config) -> Directories {
+ Directories {
+ directories: config.add.to_owned(),
+ config: config.to_owned(),
+ }
+ }
+
+ pub(crate) 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)
+ }
+
+ pub fn as_stdin(&self) -> String {
+ self.directories
+ .iter()
+ .cloned()
+ .map(|p: PathBuf| p.to_string_lossy().into())
+ .collect::<Vec<String>>()
+ .join("\n")
+ }
+}
+
+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
new file mode 100644
index 0000000..ec66d0c
--- /dev/null
+++ b/src/directories/config.rs
@@ -0,0 +1,26 @@
+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/directories/error.rs b/src/directories/error.rs
new file mode 100644
index 0000000..4e77fc1
--- /dev/null
+++ b/src/directories/error.rs
@@ -0,0 +1,17 @@
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("Ignore error: {0:?}")]
+ Ignore(#[from] ignore::Error),
+
+ #[error("Path error: {0:?}")]
+ Path(#[from] PathError),
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum PathError {
+ #[error("Path contains invalid unicode: {0:?}")]
+ Unicode(String),
+}
+
diff --git a/src/error.rs b/src/error.rs
index 5b0fea8..3b45fe4 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -8,12 +8,18 @@ pub enum Error {
#[error("Config error: {0:?}")]
Config(#[from] figment::error::Error),
- #[error("Ignore error: {0:?}")]
- Ignore(#[from] ignore::Error),
+ #[error("Directories error: {0:?}")]
+ Directories(#[from] crate::directories::Error),
#[error("Process error: {0:?}")]
Finder(String),
#[error("Path error: {0:?}")]
- Path(String),
+ Path(#[from] PathError),
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum PathError {
+ #[error("Path contains invalid unicode: {0:?}")]
+ Unicode(String),
}
diff --git a/src/finder.rs b/src/finder.rs
new file mode 100644
index 0000000..da88beb
--- /dev/null
+++ b/src/finder.rs
@@ -0,0 +1,15 @@
+use std::ops::Deref;
+
+pub use config::Config;
+
+mod config;
+
+pub struct Finder(Config);
+
+impl Deref for Finder {
+ type Target = Config;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/src/finder/config.rs b/src/finder/config.rs
new file mode 100644
index 0000000..cb24b5c
--- /dev/null
+++ b/src/finder/config.rs
@@ -0,0 +1,43 @@
+use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Config {
+ pub program: String,
+ pub args: Vec<String>,
+}
+
+impl Config {
+ pub fn from<T: Provider>(provider: T) -> figment::error::Result<Config> {
+ Figment::from(provider).extract()
+ }
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ program: "fzf-tmux".into(),
+ args: vec![
+ "--",
+ "--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(),
+ }
+ }
+}
+
+impl Provider for Config {
+ fn metadata(&self) -> Metadata {
+ Metadata::named("Tmuxr finder config")
+ }
+
+ fn data(&self) -> figment::error::Result<value::Map<Profile, value::Dict>> {
+ Serialized::defaults(Config::default()).data()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index acaa475..9e49b46 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,14 +6,22 @@ use tracing::info;
pub use crate::cli::Cli;
pub use crate::config::Config;
-pub use crate::error::{Error, Result};
+pub use crate::error::{Error, PathError, Result};
+pub use crate::finder::Finder;
+pub use crate::directories::Directories;
mod cli;
mod config;
mod error;
+mod finder;
+mod directories;
#[tracing::instrument()]
pub fn run(config: &Config) -> Result<()> {
+ let mut directories = Directories::new(&config.paths);
+
+ directories.walk()?;
+
let mut finder = Command::new(&config.finder.program)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@@ -24,7 +32,7 @@ pub fn run(config: &Config) -> Result<()> {
.stdin
.as_mut()
.ok_or_else(|| Error::Finder("Failed to get finder's stdin".to_string()))?
- .write_all(config.paths.to_string().as_bytes())?;
+ .write_all(directories.as_stdin().as_bytes())?;
let output = finder.wait_with_output()?;
diff --git a/src/main.rs b/src/main.rs
index c9b21fd..9b4f400 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,48 @@
+use std::{fs::File, sync::Arc};
+
use clap::Parser;
+use figment::{
+ providers::{Env, Format, Serialized, Toml},
+ Figment,
+};
use tmuxr::{Cli, Config, Result};
use tracing_subscriber::prelude::*;
fn main() -> Result<()> {
let cli = Cli::parse();
- let config = Config::extract(cli.as_config())?;
- tracing_subscriber::registry().with(cli.as_layer()?).init();
+ let figment = Figment::from(Serialized::defaults(Config::default()))
+ .merge(Toml::file("tmuxr.toml"))
+ .merge(Env::prefixed("TMUXR_"));
+
+ let config = Config::try_from(figment)?;
+
+ if let Err(err) = init_subscriber(&cli, &config) {
+ eprintln!("Failed to initialize logging: {:?}", err)
+ }
tmuxr::run(&config)
}
+
+fn init_subscriber(cli: &Cli, config: &Config) -> Result<()> {
+ let mut layers = Vec::new();
+
+ if config.log_enabled {
+ let file = File::create(&config.log_file)?;
+ let log_layer = tracing_subscriber::fmt::layer()
+ .with_writer(Arc::new(file))
+ .boxed();
+ layers.push(log_layer);
+ };
+
+ tracing_subscriber::registry()
+ .with(
+ tracing_subscriber::fmt::layer()
+ .pretty()
+ .with_filter(cli.verbose.as_filter()),
+ )
+ .with(layers)
+ .init();
+
+ Ok(())
+}