parent
2f185cf454
commit
05dbc6a354
@ -1,3 +1,3 @@
|
||||
/target
|
||||
*~
|
||||
test-input
|
||||
test-inpu*
|
||||
|
@ -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] [--] <dirs...>", 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 <filename>\tSpecify file for `--load`");
|
||||
println!(" --save-file <filename>\tSpecify file for `--save`");
|
||||
println!(" --load-save <filename>\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 <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 <max depth>|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<P>(path: P, ensure: Ensure, must_exist: bool) -> Result<P, Error>
|
||||
where P: AsRef<Path>
|
||||
{
|
||||
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<config::Config, Error>
|
||||
{
|
||||
parse(std::env::args().skip(1))
|
||||
}
|
||||
|
||||
/// Try to parse args
|
||||
pub fn parse<I>(args: I) -> Result<config::Config, Error>
|
||||
where I: IntoIterator<Item=String>
|
||||
{
|
||||
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::<usize>() {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue