diff options
author | Toby Vincent <tobyv13@gmail.com> | 2022-11-01 13:00:07 -0500 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2022-11-01 13:02:02 -0500 |
commit | 698d811059ebb6787305e8293a77837323fc9311 (patch) | |
tree | 218818bf54373848d49ec3e888e9d8c51bace845 /src | |
parent | 61e7c46e5b805c2fda1b4724fb8389a187c75987 (diff) |
feat: impl cli, config, and logging
via clap, figment, and tracing, respectively
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 81 | ||||
-rw-r--r-- | src/config.rs | 114 | ||||
-rw-r--r-- | src/error.rs | 10 | ||||
-rw-r--r-- | src/lib.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 13 |
5 files changed, 228 insertions, 2 deletions
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..5da1036 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,81 @@ +use crate::{Config, Result}; +use clap::{Args, Parser}; +use std::fs::File; +use std::sync::Arc; +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 { + #[command(flatten)] + pub verbose: Verbosity, + + /// Connect to ssh host + #[arg(short, long)] + pub ssh: Option<String>, + + #[command(flatten)] + pub config: Config, +} + +impl Cli { + pub fn as_layer(&self) -> Result<Vec<Box<dyn Layer<Registry> + Send + Sync>>> { + let mut layers = Vec::new(); + + let fmt_layer = tracing_subscriber::fmt::layer() + .pretty() + .with_filter(self.verbose.into_filter()) + .boxed(); + + 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) + } +} + +#[derive(Debug, Default, Args)] +pub struct Verbosity { + /// Print additional information per occurrence + #[arg(short, long, action = clap::ArgAction::Count, conflicts_with = "quiet")] + pub verbose: u8, + + /// Suppress all output + #[arg(short, long, global = true, conflicts_with = "verbose")] + pub quiet: bool, +} + +impl Verbosity { + pub fn into_filter(&self) -> LevelFilter { + self.into() + } +} + +impl From<&Verbosity> for LevelFilter { + fn from(value: &Verbosity) -> Self { + match value.verbose + 1 - u8::from(value.quiet) { + 0 => LevelFilter::OFF, + 1 => LevelFilter::ERROR, + 2 => LevelFilter::WARN, + 3 => LevelFilter::INFO, + 4 => LevelFilter::DEBUG, + _ => LevelFilter::TRACE, + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_start() { + assert_eq!(1, 1); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..42614ec --- /dev/null +++ b/src/config.rs @@ -0,0 +1,114 @@ +use crate::{Error, Result}; +use clap::{Args, Parser}; +use figment::{ + providers::{Env, Format, Serialized, Toml}, + Figment, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// Simple program to manage projects and ssh hosts using tmux +#[derive(Debug, Parser, Serialize, Deserialize)] +#[command(author, version, about)] +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) finder: Finder, + + #[arg(skip)] + pub(crate) enable_logging: bool, + + #[arg(skip)] + pub(crate) log_file: PathBuf, +} + +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) + } +} + +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, Args, 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![ + "--".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(), + ], + } + } +} + +#[derive(Debug, Args, Serialize, Deserialize)] +pub(crate) struct Directory { + 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 + }) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_start() { + assert_eq!(1, 1); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e423493 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("IO error: {0:?}")] + IO(#[from] std::io::Error), + + #[error("Config error: {0:?}")] + Config(#[from] figment::error::Error), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..872ca50 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +pub use crate::cli::Cli; +pub use crate::config::Config; +pub use crate::error::{Error, Result}; + +mod cli; +mod config; +mod error; + +#[tracing::instrument()] +pub fn run(config: &Config) -> Result<()> { + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..40fccd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,12 @@ -fn main() { - println!("Hello, world!"); +use clap::Parser; +use tmuxr::{Cli, Result}; +use tracing_subscriber::prelude::*; + +fn main() -> Result<()> { + let cli = Cli::parse(); + let config = cli.config.extract()?; + + tracing_subscriber::registry().with(cli.as_layer()?).init(); + + tmuxr::run(&config) } |