parent
2f185cf454
commit
05dbc6a354
@ -1,3 +1,3 @@
|
|||||||
/target
|
/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