diff options
author | Toby Vincent <tobyv13@gmail.com> | 2022-11-02 17:48:10 -0500 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2022-11-02 17:48:10 -0500 |
commit | 53d0cefcb1ca3527935479cc07a6f5fe5b1f8051 (patch) | |
tree | 307ba4005e9ffe95396f07750871b6e44b4e502e /src | |
parent | 1334d9f1d08a27ea7fa4cd4228ac1fdf44bdd552 (diff) |
feat: finish implementing config and cli
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 28 | ||||
-rw-r--r-- | src/config.rs | 159 | ||||
-rw-r--r-- | src/directories.rs | 61 | ||||
-rw-r--r-- | src/directories/config.rs | 26 | ||||
-rw-r--r-- | src/directories/error.rs | 17 | ||||
-rw-r--r-- | src/error.rs | 12 | ||||
-rw-r--r-- | src/finder.rs | 15 | ||||
-rw-r--r-- | src/finder/config.rs | 43 | ||||
-rw-r--r-- | src/lib.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 40 |
10 files changed, 271 insertions, 142 deletions
@@ -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() + } +} @@ -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(()) +} |