From 96f8182c58539a82d01aae9619b7974f14c54ecb Mon Sep 17 00:00:00 2001 From: obvMellow Date: Thu, 1 Aug 2024 01:01:37 +0300 Subject: [PATCH] Using `clap` for parsing arguments --- Cargo.lock | 53 +++++++++++++++++++++ Cargo.toml | 1 + src/commands.rs | 74 +++++++++-------------------- src/main.rs | 120 +++++++++++++++++++++++------------------------- src/test.rs | 52 ++++++++------------- 5 files changed, 151 insertions(+), 149 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38d9587..8549aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,46 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "colog" version = "1.3.0" @@ -218,6 +258,7 @@ dependencies = [ name = "hardcpy" version = "0.1.0" dependencies = [ + "clap", "colog", "colored", "dirs", @@ -235,6 +276,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "humantime" version = "2.1.0" @@ -453,6 +500,12 @@ dependencies = [ "trim-in-place", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.70" diff --git a/Cargo.toml b/Cargo.toml index da9caa0..8f1bd1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ indicatif-log-bridge = "0.2.2" colog = "1.3.0" log = "0.4.22" rand = "0.9.0-alpha.1" +clap = { version = "4.5.11", features = ["derive"] } diff --git a/src/commands.rs b/src/commands.rs index 04a8951..bf488fe 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,53 +4,35 @@ use ini::Ini; use std::fs; use std::path::PathBuf; -pub fn revert(config_dir: PathBuf, mut config: Ini, args: &Vec) { - let i = args - .iter() - .position(|v| v == &"revert".to_string()) - .unwrap(); - let hash = match args.get(i + 1) { - Some(v) => v, - None => { - eprintln!("Please enter a valid ID."); - return; - } - }; - +pub fn revert(config_dir: PathBuf, mut config: Ini, id: String, multithread: bool) { let mut setter = config.with_section(Some("Backups")); - let mut vec = match setter.get(hash) { + let mut vec = match setter.get(&id) { Some(v) => v.split(SEPARATOR), None => { - eprintln!("Couldn't find \"{}\".", hash); + eprintln!("Couldn't find \"{}\".", id); return; } }; let mut dest_str = PathBuf::from(vec.nth(0).unwrap().to_string()); dest_str.pop(); let dest_str = dest_str.to_str().unwrap().to_string(); - let mut source_str = vec.nth(0).unwrap().to_string(); // Getting the first element again because .nth() consumes all preceding and the returned element + let source_str = vec.nth(0).unwrap().to_string(); // Getting the first element again because .nth() consumes all preceding and the returned element - _copy(config_dir, &mut config, args, &mut source_str, dest_str); + _copy( + config_dir, + &mut config, + multithread, + source_str.into(), + dest_str.into(), + ); } -pub fn delete(mut config_dir: PathBuf, mut config: Ini, args: &Vec) { - let i = args - .iter() - .position(|v| v == &"delete".to_string()) - .unwrap(); - let hash = match args.get(i + 1) { - Some(v) => v, - None => { - eprintln!("Please enter a valid ID."); - return; - } - }; - +pub fn delete(mut config_dir: PathBuf, mut config: Ini, id: String) { let mut setter = config.with_section(Some("Backups")); - let dest = match setter.get(hash) { + let dest = match setter.get(&id) { Some(v) => v.split(SEPARATOR).collect::>()[1], None => { - eprintln!("Couldn't find \"{}\".", hash); + eprintln!("Couldn't find \"{}\".", id); return; } }; @@ -62,27 +44,15 @@ pub fn delete(mut config_dir: PathBuf, mut config: Ini, args: &Vec) { } }; println!("Deleted {}", dest); - _delete_entry(&mut config_dir, &mut config, hash); + _delete_entry(&mut config_dir, &mut config, &id); } -pub fn soft_delete(mut config_dir: PathBuf, mut config: Ini, args: &Vec) { - let i = args - .iter() - .position(|v| v == &"soft-delete".to_string()) - .unwrap(); - let hash = match args.get(i + 1) { - Some(v) => v, - None => { - eprintln!("Please enter a valid ID."); - return; - } - }; - - if _delete_entry(&mut config_dir, &mut config, hash) { - println!("Deleted {}", hash); +pub fn soft_delete(mut config_dir: PathBuf, mut config: Ini, id: String) { + if _delete_entry(&mut config_dir, &mut config, &id) { + println!("Deleted {}", id); return; } - eprintln!("Couldn't find \"{}\".", hash); + eprintln!("Couldn't find \"{}\".", id); } pub fn list(config: Ini) { @@ -103,9 +73,9 @@ pub fn list(config: Ini) { } } -fn _delete_entry(config_dir: &mut PathBuf, config: &mut Ini, hash: &String) -> bool { - if config.with_section(Some("Backups")).get(hash).is_some() { - config.with_section(Some("Backups")).delete(hash); +fn _delete_entry(config_dir: &mut PathBuf, config: &mut Ini, id: &String) -> bool { + if config.with_section(Some("Backups")).get(id).is_some() { + config.with_section(Some("Backups")).delete(id); } else { return false; } diff --git a/src/main.rs b/src/main.rs index 9e15ec2..9a8e4da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![feature(test)] - mod commands; mod test; @@ -7,6 +5,7 @@ mod test; use indicatif_log_bridge::LogWrapper; use crate::commands::*; +use clap::{Parser, Subcommand}; use colored::Colorize; use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle}; use ini::Ini; @@ -89,17 +88,39 @@ impl From for FileSize { } } -const HELP_STR: &str = "\ -Usage: hardcpy [SOURCE] [DESTINATION] [OPTIONS] - hardcpy [COMMAND] - Commands: - help: Prints this message. - list: Lists all the backups you have created so far. - soft-delete: Deletes the entry for a backup. This does not delete any of the files. - delete: Deletes a backup. - revert: Copies a backup to it's original source. Note that this creates a new backup entry. - Options: - --multi-thread: Utilize multi-threading. Can be faster on very large folders."; +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + List, + SoftDelete { + #[arg(short, long)] + id: String, + }, + Delete { + #[arg(short, long)] + id: String, + }, + Revert { + #[arg(short, long)] + id: String, + + #[arg(short, long)] + multithread: bool + }, + Create { + source: PathBuf, + dest: PathBuf, + + #[arg(short, long)] + multithread: bool + }, +} const SEPARATOR: char = '┇'; @@ -116,48 +137,15 @@ fn main() { let mut config = Ini::load_from_file(config_dir.join("config.ini")).unwrap_or(Ini::new()); - let args: Vec = std::env::args().collect(); - - if args.len() < 2 { - println!("{}", HELP_STR); - return; - } - - let cmd = &args[1]; - - match cmd.as_str() { - "help" => println!("{}", HELP_STR), - "list" => list(config), - "soft-delete" => soft_delete(config_dir, config, &args), - "delete" => delete(config_dir, config, &args), - "revert" => revert(config_dir, config, &args), - _ => { - let mut source_str = "".to_string(); - let mut dest_str = "".to_string(); - for arg in args.clone() { - if arg.starts_with("--") - || std::env::current_exe() - .unwrap() - .to_str() - .unwrap() - .contains(&arg) - { - continue; - } - - if source_str.is_empty() { - source_str = arg; - } else { - dest_str = arg; - } - } - - if source_str.is_empty() || dest_str.is_empty() { - println!("{}", HELP_STR); - return; - } + let args = Args::parse(); - _copy(config_dir, &mut config, &args, &mut source_str, dest_str); + match args.command { + Commands::List => list(config), + Commands::SoftDelete { id } => soft_delete(config_dir, config, id), + Commands::Delete { id } => delete(config_dir, config, id), + Commands::Revert { id, multithread } => revert(config_dir, config, id, multithread), + Commands::Create { source, dest, multithread } => { + _copy(config_dir, &mut config, multithread, source, dest); } } } @@ -165,16 +153,16 @@ fn main() { fn _copy( config_dir: PathBuf, config: &mut Ini, - args: &Vec, - source_str: &mut String, - dest_str: String, + is_multithread: bool, + source_str: PathBuf, + dest_str: PathBuf, ) -> bool { - let source_name = PathBuf::from(&source_str).iter().last().unwrap().to_owned(); + let source_name = source_str.iter().last().unwrap().to_owned(); let source = match fs::read_dir(&source_str) { Ok(d) => d, Err(e) => { - eprintln!("Error: {} (\"{}\")", e, source_str); + eprintln!("Error: {} (\"{}\")", e, source_str.display()); return true; } }; @@ -182,7 +170,12 @@ fn _copy( match fs::create_dir_all(&dest_str) { Ok(_) => {} Err(e) => { - eprintln!("{} {} (\"{}\")", "Error:".red().bold(), e, dest_str); + eprintln!( + "{} {} (\"{}\")", + "Error:".red().bold(), + e, + dest_str.display() + ); return true; } } @@ -190,15 +183,16 @@ fn _copy( let timer = Instant::now(); let conclusion; - if args.contains(&"--multi-thread".to_string()) { + if is_multithread { conclusion = multithread(source, PathBuf::from(&dest_str), source_name.clone()); } else { conclusion = singlethread(source, PathBuf::from(&dest_str), source_name.clone()); } let v = format!( - "{source_str}{SEPARATOR}{}", - PathBuf::from(&dest_str).join(source_name).to_str().unwrap() + "{}{SEPARATOR}{}", + source_str.display(), + dest_str.join(source_name).to_str().unwrap() ); let mut hasher = fnv::FnvHasher::default(); v.hash(&mut hasher); diff --git a/src/test.rs b/src/test.rs index ea21701..851f711 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,7 +1,5 @@ #[cfg(test)] mod tests { - extern crate test; - use crate::_copy; use colored::Colorize; use ini::Ini; @@ -9,12 +7,11 @@ mod tests { use std::fs; use std::fs::File; use std::io::Write; - use test::Bencher; const FILE_SIZE: usize = 1024 * 1024 * 16; const FILE_SIZE_S: usize = 1024 * 1024; - #[bench] - fn create_backup_singlethread(b: &mut Bencher) { + #[test] + fn create_backup_singlethread() { let mut config_dir = dirs::config_dir().unwrap_or_else(|| { println!( "{} Couldn't get a config directory, using current directory.", @@ -29,10 +26,6 @@ mod tests { fs::create_dir_all("test/test_singlethread/source").unwrap(); fs::create_dir_all("test/test_singlethread/dest").unwrap(); - let args = vec![ - "test/test_singlethread/source".to_string(), - "test/test_singlethread/dest".to_string(), - ]; let mut f = File::create("test/test_singlethread/source/big_file").unwrap(); let mut rng = rand::thread_rng(); @@ -43,19 +36,17 @@ mod tests { f.write_all(&*buf).unwrap(); f.flush().unwrap(); - b.iter(|| { - _copy( - config_dir.clone(), - &mut config, - &args, - &mut "test/test_singlethread/source".to_string(), - "test/test_singlethread/dest".to_string(), - ); - }); + _copy( + config_dir.clone(), + &mut config, + false, + "test/test_singlethread/source".into(), + "test/test_singlethread/dest".into(), + ); } - #[bench] - fn create_backup_multithread(b: &mut Bencher) { + #[test] + fn create_backup_multithread() { let mut config_dir = dirs::config_dir().unwrap_or_else(|| { println!( "{} Couldn't get a config directory, using current directory.", @@ -70,11 +61,6 @@ mod tests { fs::create_dir_all("test/test_multithread/source").unwrap(); fs::create_dir_all("test/test_multithread/dest").unwrap(); - let args = vec![ - "test/test_multithread/source".to_string(), - "test/test_multithread/dest".to_string(), - "--multi-thread".to_string(), - ]; for i in 1..=16 { let mut f = @@ -88,14 +74,12 @@ mod tests { f.flush().unwrap(); } - b.iter(|| { - _copy( - config_dir.clone(), - &mut config, - &args, - &mut "test/test_multithread/source".to_string(), - "test/test_multithread/dest".to_string(), - ); - }); + _copy( + config_dir.clone(), + &mut config, + true, + "test/test_multithread/source".into(), + "test/test_multithread/dest".into(), + ); } }