From 05dbc6a35445eb5c947bc769deadab9b2e175bc4 Mon Sep 17 00:00:00 2001 From: Avril Date: Thu, 9 Jul 2020 03:42:39 +0100 Subject: [PATCH] arg parse okay --- .gitignore | 2 +- Cargo.lock | 1 + Cargo.toml | 3 +- src/arg.rs | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 20 ++++ src/main.rs | 17 ++++ 6 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 src/arg.rs diff --git a/.gitignore b/.gitignore index 7f7d777..2649b43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target *~ -test-input +test-inpu* diff --git a/Cargo.lock b/Cargo.lock index 52544c0..1e6f2ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,7 @@ name = "rmdupe" version = "0.1.0" dependencies = [ "futures", + "lazy_static", "sha2", "tokio", "tokio-test", diff --git a/Cargo.toml b/Cargo.toml index 7ae51cf..d8c7e78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ tokio-test = "0.2" [dependencies] tokio = { version = "0.2", features = ["full"], optional = true } sha2 = "0.9" -futures = { version = "0.3", optional = true} \ No newline at end of file +futures = { version = "0.3", optional = true } +lazy_static = "1.4" \ No newline at end of file diff --git a/src/arg.rs b/src/arg.rs new file mode 100644 index 0000000..3eec42e --- /dev/null +++ b/src/arg.rs @@ -0,0 +1,248 @@ +use super::*; +use std::{ + fmt, + path::{ + Path,PathBuf, + }, +}; +use lazy_static::lazy_static; + +/// Get the program name +pub fn program() -> &'static str +{ + lazy_static!{ + static ref NAME: &'static str = Box::leak(std::env::args().next().unwrap().into_boxed_str()); + } + + &NAME +} + +pub fn usage() -> ! +{ + println!("Usage: {} [OPTIONS] [--] ", program()); + println!("Usage: {} --help", program()); + println!("OPTIONS:"); + println!(" --load -l:\t\tLoad the hashes from `load-file` if possible."); + println!(" --save -s:\t\tSave the hashes to `save-file` if possible."); + println!(" --load-file \tSpecify file for `--load`"); + println!(" --save-file \tSpecify file for `--save`"); + println!(" --load-save \tSpecify file for `--save` and `--load`"); + println!(" --delete -d\t\tDelete dupes instead of printing"); + println!(" --verbose -v\t\tPrint verbose info"); + println!(" --error-mode \tSpecify error handling mode, one of: [IGNORE|WARN|CANCEL|TERMINATE]. Default is `WARN`"); + println!(" --quiet -q\t\tAlias for `--error-mode IGNORE`"); + println!(" --warn -i\t\tAlias for `--error-mode WARN`"); + println!(" --cancel -w\t\tAlias for `--error-mode CANCEL`"); + println!(" --error -W\t\tAlias for `--error-mode TERMINATE`"); + println!(" --recursive |inf\t\tRecursive mode."); + println!(" --\t\t\tStop reading args"); + println!("Other:"); + println!(" --help:\t\tPrint this message"); + std::process::exit(1) +} + +enum Ensure +{ + None, + Dir, + File, +} + +impl Default for Ensure +{ + fn default() -> Self { + Self::None + } +} + +fn validate_path

(path: P, ensure: Ensure, must_exist: bool) -> Result +where P: AsRef +{ + if !must_exist || path.as_ref().exists() { + if !must_exist && !path.as_ref().exists() {return Ok(path);} + match ensure { + Ensure::File => { + if !path.as_ref().is_file() { + Err(Error::ExpectedFile(path.as_ref().to_owned())) + } else { + Ok(path) + } + }, + Ensure::Dir => { + if !path.as_ref().is_dir() { + Err(Error::ExpectedDirectory(path.as_ref().to_owned())) + } else { + Ok(path) + } + }, + _ => Ok(path), + } + } else { + Err(Error::FileNotFound(path.as_ref().to_owned())) + } +} + +/// Try to parse `std::env::args()` +#[inline] +pub fn parse_args() -> Result +{ + parse(std::env::args().skip(1)) +} + +/// Try to parse args +pub fn parse(args: I) -> Result +where I: IntoIterator +{ + let mut args = args.into_iter(); + let mut reading = true; + + let mut paths = Vec::new(); + let mut load = Vec::new(); + let mut save = Vec::new(); + let mut verbose = false; + let mut delete = false; + let mut mode_er = error::Mode::Cancel; + let mut mode_rec = config::RecursionMode::None; + + macro_rules! push { + ($arg:expr) => { + { + let arg = $arg; + paths.push(validate_path(&arg, Ensure::None, true)?.to_owned()) + } + }; + } + let mut one = String::new(); + macro_rules! take_one { + () => { + { + match args.next() { + Some(a) => {one = a; true}, + _ => false, + } + } + } + } + while let Some(arg) = args.next() + { + if reading && arg.chars().next().unwrap_or('\0') == '-' { + match &arg[..] { + "--" => reading = false, + + "--load" => { + load.push(validate_path(config::DEFAULT_HASHNAME, Ensure::File, false)?.to_owned()); + }, + "--save" => { + save.push(validate_path(config::DEFAULT_HASHNAME, Ensure::File, false)?.to_owned()); + }, + "--save-file" if take_one!() => { + save.push(validate_path(&one, Ensure::File, false)?.to_owned()); + }, + "--load-file" if take_one!() => { + load.push(validate_path(&one, Ensure::Dir, false)?.to_owned()); + }, + "--load-save" if take_one!() => { + load.push(validate_path(&one, Ensure::Dir, false)?.to_owned()); + save.push(validate_path(&one, Ensure::Dir, false)?.to_owned()); + }, + + "--verbose" => verbose = true, + "--delete" => delete = true, + + "--error" => mode_er = error::Mode::Terminate, + "--cancel" => mode_er = error::Mode::Cancel, + "--warn" => mode_er = error::Mode::Warn, + "--quiet" => mode_er = error::Mode::Ignore, + "--error-mode" if take_one!() => mode_er = match one.to_lowercase().trim() { + "terminate" => error::Mode::Terminate, + "cancel" => error::Mode::Cancel, + "warn" => error::Mode::Warn, + "ignore" => error::Mode::Ignore, + _ => return Err(Error::UnknownErrorMode(one)), + }, + "--recursive" if take_one!() => { + if one.to_lowercase().trim() == "inf" { + mode_rec = config::RecursionMode::All; + } else if let Ok(sz) = one.parse::() { + mode_rec = config::RecursionMode::N(sz); + } else { + return Err(Error::Parse(one, "number")); + } + }, + _ if arg.len() > 1 => { + // Parse small ones + for argchar in arg.chars().skip(1) { + match argchar { + 'l' => load.push(validate_path(config::DEFAULT_HASHNAME, Ensure::File, false)?.to_owned()), + 's' => save.push(validate_path(config::DEFAULT_HASHNAME, Ensure::File, false)?.to_owned()), + + 'd' => delete = true, + 'v' => verbose = true, + + 'W' => mode_er = error::Mode::Terminate, + 'w' => mode_er = error::Mode::Cancel, + 'i' => mode_er = error::Mode::Warn, + 'q' => mode_er = error::Mode::Ignore, + + 'r' => mode_rec = config::RecursionMode::All, + _ => return Err(Error::UnknownArgChar(argchar)), + } + } + }, + _ => push!(arg), + }; + } else { + push!(arg); + } + } + + // Remove dupes. Load order doesn't really matter + load.sort_unstable(); + load.dedup(); + save.sort_unstable(); + save.dedup(); + + Ok(config::Config{ + paths: paths, + mode: config::Mode{ + error_mode: mode_er, + recursion_mode: mode_rec, + operation_mode: if delete { config::OperationMode::Delete } else { config::OperationMode::Print }, + }, + save, + load, + verbose, + }) +} + +#[derive(Debug)] +pub enum Error +{ + Parse(String, &'static str), + UnknownArg(String), + UnknownArgChar(char), + FileNotFound(PathBuf), + ExpectedFile(PathBuf), + ExpectedDirectory(PathBuf), + UnknownErrorMode(String), + Unknown, +} + +impl std::error::Error for Error{} +impl fmt::Display for Error +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "failed to parse args: ")?; + match self { + Error::Parse(value, typ) => write!(f, "expected a {}, got `{}'", typ, value), + Error::UnknownArg(arg) => write!(f, "i don't know how to `{}'", arg), + Error::UnknownArgChar(arg) => write!(f, "i don't know how to `-{}'", arg), + Error::UnknownErrorMode(error) => write!(f, "unknown error mode `{}'", error), + Error::ExpectedDirectory(path) => write!(f, "expected directory, got file: {:?}", path), + Error::ExpectedFile(path) => write!(f, "expected file, got directory: {:?}", path), + Error::FileNotFound(path) => write!(f, "file not found: {:?}", path), + _ => write!(f, "unknown error"), + } + } +} diff --git a/src/config.rs b/src/config.rs index d04006a..95c8b4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,7 @@ use super::*; +use std::{ + +}; #[derive(Debug, Clone)] pub enum RecursionMode @@ -34,3 +37,20 @@ impl Default for Mode } } } + +pub const DEFAULT_HASHNAME: &'static str = ".rmdupe"; + +#[derive(Debug)] +pub struct Config +{ + /// Paths to dupde-check + pub paths: Vec, + /// operation mode + pub mode: Mode, + /// Save hashes to + pub save: Vec, + /// Load hashes from + pub load: Vec, + /// Print verbose info + pub verbose: bool, +} diff --git a/src/main.rs b/src/main.rs index 7f41513..07d5090 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,14 +7,31 @@ mod error; mod hash; mod container; mod config; +mod arg; mod proc; + #[cfg(test)] mod test { use super::*; use std::{ path::Path, }; + + #[test] + pub fn args() -> Result<(), arg::Error> + { + macro_rules! string_literal { + ($($strings:expr),*) => { + vec![$( + format!($strings), + )*] + } + } + let args = string_literal!["-lsd", "--load-file", "hello", "--load-save", "load-save!", "--", "test-input", "test-input", "test-input-3", "test-input-2"]; + println!("{:?}", arg::parse(args)?); + Ok(()) + } #[test] pub fn test() -> Result<(), error::Error>