153 lines
4.9 KiB
Rust
153 lines
4.9 KiB
Rust
use clap::Parser;
|
|
use glob::glob;
|
|
use regex::Regex;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(author, version, about)]
|
|
struct Args {
|
|
/// Path to the directory where the files to be renamed are located.
|
|
#[arg(short = 'i', long)]
|
|
path: String,
|
|
|
|
/// Regular expression pattern to match against the filenames in the directory.
|
|
///
|
|
/// The pattern should contain match groups (expressed in parentheses).
|
|
///
|
|
/// Example: "S(\d+)E(\d+)"
|
|
#[arg(short = 's', long)]
|
|
pattern: String,
|
|
|
|
/// Replacement pattern for the matched filenames.
|
|
///
|
|
/// You can use `\1`, `\2`, etc. to reference the match groups in the pattern.
|
|
///
|
|
/// There are also the following tags available:
|
|
///
|
|
/// * {full_name} - file name with extension
|
|
///
|
|
/// * {name} - filename without extension
|
|
///
|
|
/// * {ext} - extension
|
|
///
|
|
/// Example: "Season \1 Episode \2.{ext}"
|
|
#[arg(short = 'r', long)]
|
|
replacement: String,
|
|
|
|
/// Filename wildcard to filter the files to be renamed in the directory.
|
|
///
|
|
/// For example, "*.mkv" will match all files with the ".mkv" extension.
|
|
#[arg(short = 'w', long, default_value = "*")]
|
|
wildcard: String,
|
|
}
|
|
|
|
fn get_file_list(dir_path: &str, wildcard: &str) -> Vec<String> {
|
|
let pattern = Path::new(dir_path).join(wildcard);
|
|
let dir_entries: Vec<Result<PathBuf, _>> = glob(&pattern.to_string_lossy()).unwrap().collect();
|
|
dir_entries
|
|
.into_iter()
|
|
.filter_map(|entry| entry.ok())
|
|
.filter(|entry: &PathBuf| entry.is_file())
|
|
.map(|entry| entry.file_name().unwrap().to_string_lossy().to_string())
|
|
.collect()
|
|
}
|
|
|
|
fn replace_extra_tags(original_file_name: &str, new_file_name: &str) -> String {
|
|
let path = Path::new(original_file_name);
|
|
let name = path.file_stem().unwrap().to_str().unwrap();
|
|
let extension = path.extension().unwrap().to_str().unwrap();
|
|
|
|
let re = Regex::new(r"(\{[^{}]+})").unwrap();
|
|
re.find_iter(new_file_name)
|
|
.map(|m| {
|
|
let tag = m.as_str();
|
|
match tag {
|
|
"{ext}" => new_file_name.replace(tag, extension),
|
|
"{full_name}" => new_file_name.replace(tag, original_file_name),
|
|
"{name}" => new_file_name.replace(tag, name),
|
|
_ => panic!("Invalid tag: {}", tag),
|
|
}
|
|
})
|
|
.fold(String::new(), |result, s| result + &s)
|
|
}
|
|
|
|
fn replace_name(file_name: &str, pattern: &str, replacement: &str) -> String {
|
|
let mut new_file_name = String::from(file_name);
|
|
let re = Regex::new(pattern).unwrap();
|
|
for captures in re.captures_iter(file_name) {
|
|
let mut replaced_text = String::from(replacement);
|
|
for (i, capture) in captures.iter().enumerate() {
|
|
if let Some(capture) = capture {
|
|
let capture_text = capture.as_str().to_string();
|
|
let replace_key = format!("\\{}", i);
|
|
replaced_text = replaced_text.replace(&replace_key, &capture_text);
|
|
}
|
|
}
|
|
new_file_name = replaced_text
|
|
}
|
|
replace_extra_tags(file_name, &new_file_name)
|
|
}
|
|
|
|
fn main() {
|
|
let args = Args::parse();
|
|
let dir_path = &args.path;
|
|
let pattern = &args.pattern;
|
|
let replacement = &args.replacement;
|
|
let wildcard = &args.wildcard;
|
|
|
|
let files = get_file_list(dir_path, wildcard);
|
|
for file_name in files {
|
|
let replaced_name = replace_name(&file_name, pattern, replacement);
|
|
println!("mv \"{}\" \"{}\"", file_name, replaced_name);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn groups_and_ext_test() {
|
|
const INPUT: &str = "The Best Show - S01E16 - Bla Bla Bla HDTV-720p.mkv";
|
|
const RESULT: &str = "Season 1 - Episode 16 - The Best Show - Bla Bla Bla - 720p-HDTV.mkv";
|
|
|
|
const PATTERN: &str = r"(.*) - S0?(\d+)E0?(\d+) - (.*) (HDTV|WEBDL)-(720p|1080p)";
|
|
const REPLACEMENT: &str = r"Season \2 - Episode \3 - \1 - \4 - \6-\5.{ext}";
|
|
|
|
assert_eq!(replace_name(INPUT, PATTERN, REPLACEMENT), RESULT);
|
|
}
|
|
|
|
#[test]
|
|
fn name_test() {
|
|
const INPUT: &str = "The Best Show - S01E16 - Bla Bla Bla HDTV-720p.mkv";
|
|
const RESULT: &str = "The Best Show - S01E16 - Bla Bla Bla HDTV-720p.crap";
|
|
|
|
const PATTERN: &str = r"";
|
|
const REPLACEMENT: &str = r"{name}.crap";
|
|
|
|
assert_eq!(replace_name(INPUT, PATTERN, REPLACEMENT), RESULT);
|
|
}
|
|
|
|
#[test]
|
|
fn full_name_test() {
|
|
const INPUT: &str = "The Best Show - S01E16 - Bla Bla Bla HDTV-720p.mkv";
|
|
const RESULT: &str = "REVIEW - The Best Show - S01E16 - Bla Bla Bla HDTV-720p.mkv";
|
|
|
|
const PATTERN: &str = r"";
|
|
const REPLACEMENT: &str = r"REVIEW - {full_name}";
|
|
|
|
assert_eq!(replace_name(INPUT, PATTERN, REPLACEMENT), RESULT);
|
|
}
|
|
|
|
#[test]
|
|
fn file_list_test() {
|
|
const DIRECTORY: &str = ".";
|
|
const WILDCARD: &str = "*.toml";
|
|
|
|
assert_eq!(get_file_list(DIRECTORY, WILDCARD), vec!["Cargo.toml"]);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_file_list_test() {
|
|
const DIRECTORY: &str = "src";
|
|
const WILDCARD: &str = "*.exe";
|
|
|
|
assert_eq!(get_file_list(DIRECTORY, WILDCARD), Vec::<String>::new());
|
|
}
|