diff --git a/src/args/arg.rs b/src/args/arg.rs new file mode 100644 index 0000000..bf68316 --- /dev/null +++ b/src/args/arg.rs @@ -0,0 +1,227 @@ +use std::path::PathBuf; +use std::collections::BTreeMap; +use std::error::Error; +use std::fmt; + +use clap::{Arg, ArgAction, ArgMatches, ArgGroup, Command}; + + + +pub struct Args { + pub copy_to_sys: bool, + pub dry_run: bool, + pub override_manager_dir: Option, + pub override_device: Option, +} + +impl Args { + pub fn parse_args() -> Self { + + let cli = Args::get_cli(); + + let matches = cli.get_matches(); + let values = Value::from_matches(&matches); + + let (flags, unprocessed) = Args::process_flags(values); + let (args, _) = Args::process_args(unprocessed); + + let or_mgr_dir = match &args[0] { + Some(path) => Some(PathBuf::from(path)), + None => None, + }; + + Args { + copy_to_sys: flags[0], + dry_run: flags[1], + override_manager_dir: or_mgr_dir, + override_device: args[1].clone(), + } + } + + + fn process_flags(values: Vec<(clap::Id, Value)> ) -> (Vec, Vec<(clap::Id, Value)>) { + + let (copy_to_sys, unmatched) = Args::get_arg(values, "from-git"); + let (dry_run, unmatched) = Args::get_arg(unmatched, "dry-run"); + + let copy_to_sys = if let Value::Bool(val) = copy_to_sys.unwrap_or(Value::None) { val } + else { false }; + + let dry_run = if let Value::Bool(val) = dry_run.unwrap_or(Value::None) { val } + else { false }; + + + (vec![copy_to_sys, dry_run], unmatched) + } + + + fn process_args(values: Vec<(clap::Id, Value)>) -> (Vec>, Vec<(clap::Id, Value)>) { + + let (override_manager_dir, unmatched) = Args::get_arg(values, "manager-dir"); + let (override_device, unmatched) = Args::get_arg(unmatched, "device"); + + + let or_mngr_dir = if let Value::String(val) = override_manager_dir.unwrap_or(Value::None) { Some(String::from(val)) } + else { None }; + + let or_device = if let Value::String(val) = override_device.unwrap_or(Value::None) { Some(String::from(val))} + else { None }; + + + (vec![or_mngr_dir, or_device], unmatched) + } + + + + fn get_cli() -> Command { + + let from_git = Arg::new("from-git") + .short('f') + .long("from-git") + .action(ArgAction::SetTrue); + + let override_manager_dir = Arg::new("manager-dir") + .long("manager-dir") + .action(ArgAction::Append); + + let override_device = Arg::new("device") + .short('d') + .long("device") + .action(ArgAction::Append); + + let dry_run = Arg::new("dry-run") + .short('n') + .long("dry") + .action(ArgAction::SetTrue); + + let cli = Command::new("dotfiles") + .group(ArgGroup::new("flags").multiple(true)) + .next_help_heading("FLAGS") + .args([ + from_git, + dry_run, + ]) + .group(ArgGroup::new("overrides").multiple(true)) + .next_help_heading("OVERRIDES") + .args([ + override_manager_dir, + override_device, + ]); + + cli + } + + fn get_arg(args: Vec<(clap::Id, Value)>, id: &str) -> (Box>, Vec<(clap::Id, Value)>) { + + let (matches, non_matches): (Vec<_>, Vec<_>) = args.into_iter().partition(|value| value.0 == id); + + let arg_match = match matches.len() { + 0 => Box::new(None), + 1 => Box::new(Some(matches[0].1.clone())), + _ => unreachable!(), + }; + + (arg_match, non_matches) + } + + +} + + + + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum Value { + Bool(bool), + String(String), + None, +} + + +impl Value { + fn from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)> { + + let mut values = BTreeMap::new(); + + let _ = matches.ids().into_iter().for_each(|id| { + let source = matches + .value_source(id.as_str()) + .expect("id came from matches"); + + if matches.try_get_many::(id.as_str()).is_ok() { return () } + if source != clap::parser::ValueSource::CommandLine { return () } + if Self::extract::(matches, id, &mut values) { return () } + if Self::extract::(matches, id, &mut values) { return () } + }); + + values.into_values().collect() + + } + + + fn extract + Send + Sync + 'static>( + matches: &ArgMatches, + id: &clap::Id, + output: &mut BTreeMap, + ) -> bool { + + match matches.try_get_many::(id.as_str()) { + Ok(Some(values)) => { + values.zip( + matches + .indices_of(id.as_str()) + .expect("id came from matches") + ) + .for_each(|(value, index)| { + output.insert(index, (id.clone(), value.clone().into())); + }); + + true + }, + Err(clap::parser::MatchesError::Downcast { .. }) => false, + Ok(None) => { + unreachable!("ids only reports what is present") + }, + Err(_) => { + unreachable!("id came from matches") + }, + } + } +} + +impl From for Value { + fn from(other: String) -> Value { + Value::String(other) + } +} + +impl From for Value { + fn from(other: bool) -> Value { + Value::Bool(other) + } +} + +impl Default for Value { + fn default() -> Value { + Value::None + } +} + + + +#[derive(Debug)] +pub enum ArgError { + ArgParseError +} + +impl Error for ArgError {} + +impl fmt::Display for ArgError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ArgError::ArgParseError => { + write!(f, "Error parsing arguments") + } + } + } +} diff --git a/src/args/mod.rs b/src/args/mod.rs index ea86848..85c04f9 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -1 +1,2 @@ pub mod parse; +pub mod arg; diff --git a/src/args/parse.rs b/src/args/parse.rs index b2b0799..fd40910 100644 --- a/src/args/parse.rs +++ b/src/args/parse.rs @@ -1,20 +1,4 @@ -use clap::{Arg, Command, ArgAction, ArgMatches}; -pub fn parse_args() -> ArgMatches { - - let matches = Command::new("dotfiles") - .version("0.1") - .author("Ethan Simmons") - .about("Manages dotfiles") - .arg(Arg::new("from-git") - .short('f') - .long("from-git") - .action(ArgAction::SetTrue) - ) - .get_matches(); - - matches -} diff --git a/src/config/cfg.rs b/src/config/cfg.rs index 78d6575..f570a9b 100644 --- a/src/config/cfg.rs +++ b/src/config/cfg.rs @@ -1,6 +1,6 @@ use std::fs; use std::env; -use std::path::{PathBuf, Path}; +use std::path::PathBuf; use std::error::Error; use std::fmt; diff --git a/src/lib.rs b/src/lib.rs index d660371..e4c922b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,9 @@ use std::error::Error; use std::fmt; -use clap::ArgMatches; - use crate::config::cfg; use crate::dotfile::dot; +use crate::args::arg; pub mod config; pub mod dotfile; @@ -14,9 +13,9 @@ pub mod fs; -pub fn run(args: ArgMatches, config: cfg::Config) -> Result<(), ManagerError> { +pub fn run(args: arg::Args, config: cfg::Config) -> Result<(), ManagerError> { - let copy_to_sys = args.get_flag("from-git"); + let copy_to_sys = args.copy_to_sys; let dotfiles = config.dotfiles; diff --git a/src/main.rs b/src/main.rs index 1a8825d..9d6238d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use dotfiles_manager::args::parse; +use dotfiles_manager::args::arg; use dotfiles_manager::config::cfg; @@ -8,7 +8,7 @@ use dotfiles_manager::config::cfg; fn main() -> Result<(), dotfiles_manager::ManagerError> { - let args = parse::parse_args(); + let args = arg::Args::parse_args(); let program_config = cfg::Config::parse(PathBuf::from("/home/eesim/.config/dotfiles/config"))?;