new: Do argparsing using clap-rs instead of handwritten parser

This commit is contained in:
Sayantan Santra 2024-03-06 02:56:48 -06:00
parent b62bffd340
commit 67dcd9e378
Signed by: SinTan1729
GPG key ID: EB3E68BFBA25C85F
4 changed files with 139 additions and 93 deletions

94
Cargo.lock generated
View file

@ -26,6 +26,54 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.77" version = "0.1.77"
@ -104,6 +152,39 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "clap"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -468,6 +549,7 @@ dependencies = [
name = "movie-rename" name = "movie-rename"
version = "2.2.2" version = "2.2.2"
dependencies = [ dependencies = [
"clap",
"inquire", "inquire",
"load_file", "load_file",
"tmdb-api", "tmdb-api",
@ -845,6 +927,12 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.52" version = "2.0.52"
@ -1074,6 +1162,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"

View file

@ -20,6 +20,7 @@ tmdb-api = "0.5.0"
inquire = "0.6.2" inquire = "0.6.2"
load_file = "1.0.1" load_file = "1.0.1"
tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] }
clap = { version = "4.5.1", features = ["cargo"] }
[profile.release] [profile.release]
strip = true strip = true

View file

@ -1,8 +1,9 @@
use clap::{arg, command, ArgAction};
use inquire::{ use inquire::{
ui::{Color, IndexPrefix, RenderConfig, Styled}, ui::{Color, IndexPrefix, RenderConfig, Styled},
InquireError, Select, InquireError, Select,
}; };
use std::{collections::HashMap, fs, path::Path, process::exit}; use std::{collections::HashMap, fs, path::Path};
use tmdb_api::{ use tmdb_api::{
movie::{credits::MovieCredits, search::MovieSearch}, movie::{credits::MovieCredits, search::MovieSearch},
prelude::Command, prelude::Command,
@ -12,9 +13,6 @@ use torrent_name_parser::Metadata;
use crate::structs::{get_long_lang, Language, MovieEntry}; use crate::structs::{get_long_lang, Language, MovieEntry};
// Get the version from Cargo.toml
const VERSION: &str = env!("CARGO_PKG_VERSION");
// Function to process movie entries // Function to process movie entries
pub async fn process_file( pub async fn process_file(
filename: &String, filename: &String,
@ -209,80 +207,41 @@ pub async fn process_file(
} }
// Function to process the passed arguments // Function to process the passed arguments
pub fn process_args(mut args: Vec<String>) -> (Vec<String>, HashMap<&'static str, bool>) { pub fn process_args() -> (Vec<String>, HashMap<String, bool>) {
// Remove the entry corresponding to the running process let matches = command!()
args.remove(0); .name("movie-rename")
let mut entries = Vec::new(); .author("Sayantan Santra <sayantan.santra@gmail.com>")
let mut settings = HashMap::from([("dry_run", false), ("directory", false), ("lucky", false)]); .about("A simple tool to rename movies, written in Rust.")
for arg in args { .arg(arg!(-d --directory "Run in directory mode").action(ArgAction::SetTrue))
match arg.as_str() { .arg(arg!(-n --"dry-run" "Do a dry run").action(ArgAction::SetTrue))
"--help" | "-h" => { .arg(arg!(-l --"i-feel-lucky" "Always choose the first option").action(ArgAction::SetTrue))
println!(" The expected syntax is:"); .arg(
println!( arg!([entries] "The files/directories to be processed")
" movie-rename <filename(s)> [-n|--dry-run] [-d|--directory] [-l|--i-feel-lucky] [-v|--version]" .trailing_var_arg(true)
); .num_args(1..)
println!( .required(true),
" There needs to be a config file named config in the $XDG_CONFIG_HOME/movie-rename/ directory." )
); // Use -v instead of -V for version
println!(" It should consist of two lines. The first line should have your TMDb API key."); .disable_version_flag(true)
println!( .arg(arg!(-v --version "Print version").action(ArgAction::Version))
" The second line should have a pattern, that will be used for the rename." .arg_required_else_help(true)
); .get_matches();
println!(" In the pattern, the variables need to be enclosed in {{}}, the supported variables are `title`, `year` and `director`.");
println!( // Generate the settings HashMap from read flags
" Default pattern is `{{title}} ({{year}}) - {{director}}`. Extension is always kept." let mut settings = HashMap::new();
); for id in matches.ids().map(|x| x.as_str()) {
println!(" Passing --directory or -d assumes that the arguments are directory names, which contain exactly one movie and optionally subtitles."); if id != "entries" {
println!(" Passing --dry-run or -n does a dry tun and only prints out the new names, without actually doing anything."); settings.insert(id.to_string(), matches.get_flag(id));
println!(
" You can join the short flags -d, -n and -l together (e.g. -dn or -dln)."
);
println!(" Passing --version or -v shows version and exits.");
println!(" Pass --help to get this again.");
exit(0);
}
"--version" | "-v" => {
println!("movie-rename {VERSION}");
exit(0);
}
"--dry-run" => {
println!("Doing a dry run...");
settings.entry("dry_run").and_modify(|x| *x = true);
}
"--i-feel-lucky" => {
println!("Choosing the first option automatically...");
settings.entry("lucky").and_modify(|x| *x = true);
}
"--directory" => {
println!("Running in directory mode...");
settings.entry("directory").and_modify(|x| *x = true);
}
other => {
if other.starts_with('-') {
if !other.starts_with("--") && other.len() < 5 {
// Process short args
if other.contains('l') {
println!("Choosing the first option automatically...");
settings.entry("lucky").and_modify(|x| *x = true);
}
if other.contains('d') {
println!("Running in directory mode...");
settings.entry("directory").and_modify(|x| *x = true);
}
if other.contains('n') {
println!("Doing a dry run...");
settings.entry("dry_run").and_modify(|x| *x = true);
}
} else {
eprintln!("Unknown argument passed: {other}");
exit(1);
}
} else {
entries.push(arg);
}
}
} }
} }
// Every unmatched argument should be treated as a file entry
let entries: Vec<String> = matches
.get_many::<String>("entries")
.expect("No entries provided!")
.cloned()
.collect();
(entries, settings) (entries, settings)
} }

View file

@ -9,11 +9,11 @@ mod structs;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Read arguments from commandline
let args: Vec<String> = env::args().collect();
// Process the passed arguments // Process the passed arguments
let (entries, settings) = process_args(args); let (entries, settings) = process_args();
let flag_dry_run = *settings.get("dry-run").unwrap_or(&false);
let flag_directory = *settings.get("directory").unwrap_or(&false);
let flag_lucky = *settings.get("i-feel-lucky").unwrap_or(&false);
// Try to read config file, or display error // Try to read config file, or display error
let mut config_file = env::var("XDG_CONFIG_HOME").unwrap_or(String::from("$HOME")); let mut config_file = env::var("XDG_CONFIG_HOME").unwrap_or(String::from("$HOME"));
@ -43,20 +43,12 @@ async fn main() {
// Iterate over entries // Iterate over entries
for entry in entries { for entry in entries {
// Check if the file/directory exists on disk and run necessary commands // Check if the file/directory exists on disk and run necessary commands
match settings["directory"] { match flag_directory {
// Normal file // Normal file
false => { false => {
if Path::new(entry.as_str()).is_file() { if Path::new(entry.as_str()).is_file() {
// Process the filename for movie entries // Process the filename for movie entries
process_file( process_file(&entry, &tmdb, pattern, flag_dry_run, flag_lucky, None).await;
&entry,
&tmdb,
pattern,
settings["dry_run"],
settings["lucky"],
None,
)
.await;
} else { } else {
eprintln!("The file {entry} wasn't found on disk, skipping..."); eprintln!("The file {entry} wasn't found on disk, skipping...");
continue; continue;
@ -77,8 +69,8 @@ async fn main() {
&filename, &filename,
&tmdb, &tmdb,
pattern, pattern,
settings["dry_run"], flag_dry_run,
settings["lucky"], flag_lucky,
Some(&movie_list), Some(&movie_list),
) )
.await; .await;
@ -109,7 +101,7 @@ async fn main() {
); );
} else { } else {
println!("[directory] '{entry_clean}' -> '{name}'",); println!("[directory] '{entry_clean}' -> '{name}'",);
if !settings["dry_run"] { if !flag_dry_run {
if !Path::new(name.as_str()).is_dir() { if !Path::new(name.as_str()).is_dir() {
fs::rename(entry, name) fs::rename(entry, name)
.expect("Unable to rename directory!"); .expect("Unable to rename directory!");