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.
330 lines
9.5 KiB
330 lines
9.5 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!("rmdupe version {}", env!("CARGO_PKG_VERSION"));
|
|
println!(" by {}", env!("CARGO_PKG_AUTHORS"));
|
|
println!(" (licensed with GNU GPL v3)\n");
|
|
println!("Usage: {} [OPTIONS] [--] <dirs...>", program());
|
|
println!("Usage: {} --rebase [<files>]", 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 <file>\tSpecify file for `--load`");
|
|
println!(" --save-file <file>\tSpecify file for `--save`");
|
|
println!(" --load-save <file>\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 <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 <max>|inf\tRecursive mode, give max depth or infinite.");
|
|
#[cfg(feature="threads")]
|
|
println!(" --threads <max>|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<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<Output, Error>
|
|
{
|
|
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<I>(args: I) -> Result<config::Rebase, Error>
|
|
where I: IntoIterator<Item=String>
|
|
{
|
|
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<I>(args: I) -> Result<Output, 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 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::<std::num::NonZeroUsize>().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::<usize>() {
|
|
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"),
|
|
}
|
|
}
|
|
}
|