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!("rmdupe version {}", env!("CARGO_PKG_VERSION")); println!(" by {}", env!("CARGO_PKG_AUTHORS")); println!(" (licensed with GNU GPL v3)\n"); println!("Usage: {} [OPTIONS] [--] ", program()); println!("Usage: {} --rebase []", 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!(" --debug -V\t\tPrint very 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!(" --recurse |inf\tRecursive mode, give max depth or infinite."); #[cfg(feature="threads")] println!(" --threads |inf\tMax number of threads to run at once."); #[cfg(feature="threads")] println!(" -U\t\tUlimited max threads."); #[cfg(feature="threads")] println!(" --sync -S\t\tRun only one file at a time."); println!(" --\t\t\tStop reading args"); println!("Other:"); println!(" --help -h:\t\tPrint this message"); println!(" --rebase:\t\tRebuild hash stores created by `--save`. If no files are provided, use default."); #[cfg(feature="threads")] println!("Compiled with threading support"); 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)) } #[derive(Debug)] pub enum Output { Normal(config::Config), Help, Rebase(config::Rebase), } /// Arg parse for rebase fn parse_rebase(args: I) -> Result where I: IntoIterator { let mut files = Vec::new(); for file in args.into_iter().map(|path| validate_path(path, Ensure::File, true)) { files.push(file?); } if files.len() < 1 { files.push(validate_path(config::DEFAULT_HASHNAME.to_string(), Ensure::File, false)?.to_owned()); } Ok(config::Rebase{ save: files.clone(), //TODO: Seperate save+loads load: files, #[cfg(feature="threads")] max_threads: config::MAX_THREADS, }) } /// 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 delete = false; let mut mode_er = error::Mode::Cancel; let mut mode_rec = config::RecursionMode::None; let mut mode_log = log::Mode::Warn; #[cfg(feature="threads")] let mut threads = config::MAX_THREADS; 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[..] { "--help" => return Ok(Output::Help), "--rebase" => return Ok(Output::Rebase(parse_rebase(args)?)), "--" => reading = false, #[cfg(feature="threads")] "--threads" if take_one!() => { if one.to_lowercase().trim() == "inf" { threads = None; } else { threads = Some(one.as_str().parse::().or_else(|e| Err(Error::BadNumber(e)))?); } }, #[cfg(feature="threads")] "--sync" => threads = Some(unsafe{std::num::NonZeroUsize::new_unchecked(1)}), "--load" => { load.push(validate_path(config::DEFAULT_HASHNAME.to_string(), Ensure::File, false)?.to_owned()); }, "--save" => { save.push(validate_path(config::DEFAULT_HASHNAME.to_string(), 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" => mode_log = log::Mode::Verbose, "--debug" => mode_log = log::Mode::Debug, "--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)), }, "--recurse" if take_one!() => { if one.to_lowercase().trim() == "inf" { mode_rec = config::RecursionMode::All; } else if let Ok(sz) = one.parse::() { mode_rec = if sz == 0 {config::RecursionMode::None} else {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.to_owned(), Ensure::File, false)?.to_owned()), 's' => save.push(validate_path(config::DEFAULT_HASHNAME.to_owned(), Ensure::File, false)?.to_owned()), 'd' => delete = true, 'v' => mode_log = log::Mode::Verbose, 'V' => mode_log = log::Mode::Debug, 'W' => mode_er = error::Mode::Terminate, 'w' => mode_er = error::Mode::Cancel, 'i' => mode_er = error::Mode::Warn, 'q' => mode_er = error::Mode::Ignore, 'h' => return Ok(Output::Help), #[cfg(feature="threads")] 'U' => threads = None, #[cfg(feature="threads")] 'S' => threads = Some(unsafe{std::num::NonZeroUsize::new_unchecked(1)}), 'r' => mode_rec = config::RecursionMode::All, _ => return Err(Error::UnknownArgChar(argchar)), } } }, _ => push!(arg), }; } else { push!(arg); } } // Remove dupes & keeps ordering load.dedup_full(); save.dedup_full(); paths.dedup_full(); if paths.len() < 1 { return Err(Error::NoInput); } Ok(Output::Normal(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 }, logging_mode: mode_log, }, save, load, #[cfg(feature="threads")] max_threads: threads, })) } #[derive(Debug)] pub enum Error { NoInput, Parse(String, &'static str), UnknownArg(String), UnknownArgChar(char), FileNotFound(PathBuf), ExpectedFile(PathBuf), ExpectedDirectory(PathBuf), UnknownErrorMode(String), BadNumber(std::num::ParseIntError), 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::BadNumber(num) => write!(f, "{}", num), Error::NoInput => write!(f, "need at least one input"), 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"), } } }