You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.9 KiB
249 lines
6.9 KiB
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"),
|
|
}
|
|
}
|
|
}
|