arg parse okay

master
Avril 4 years ago
parent 2f185cf454
commit 05dbc6a354
Signed by: flanchan
GPG Key ID: 284488987C31F630

2
.gitignore vendored

@ -1,3 +1,3 @@
/target
*~
test-input
test-inpu*

1
Cargo.lock generated

@ -433,6 +433,7 @@ name = "rmdupe"
version = "0.1.0"
dependencies = [
"futures",
"lazy_static",
"sha2",
"tokio",
"tokio-test",

@ -21,3 +21,4 @@ tokio-test = "0.2"
tokio = { version = "0.2", features = ["full"], optional = true }
sha2 = "0.9"
futures = { version = "0.3", optional = true }
lazy_static = "1.4"

@ -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"),
}
}
}

@ -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<String>,
/// operation mode
pub mode: Mode,
/// Save hashes to
pub save: Vec<String>,
/// Load hashes from
pub load: Vec<String>,
/// Print verbose info
pub verbose: bool,
}

@ -7,8 +7,10 @@ mod error;
mod hash;
mod container;
mod config;
mod arg;
mod proc;
#[cfg(test)]
mod test {
use super::*;
@ -16,6 +18,21 @@ mod test {
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>
{

Loading…
Cancel
Save