Join replace_extra_tags with replace_name to create replace_name_with_tags.

Removed every unwrap() functions now return a Result.
Used chatgpt to help me with the to_str() function.
This commit is contained in:
Pedro de Oliveira 2023-05-12 00:07:02 +01:00
parent 184ff2b8f7
commit 3c083334a8
2 changed files with 124 additions and 63 deletions

View File

@ -1,123 +1,177 @@
use glob::glob;
//! The main functions that `rmv` uses.
use glob::{glob_with, MatchOptions};
use regex::Regex;
use std::path::{Path, PathBuf};
/// A struct representing a custom error for glob-related operations.
#[derive(Debug)]
pub struct Error(String);
impl From<glob::GlobError> for Error {
/// Converts a `GlobError` into a `Error`.
fn from(e: glob::GlobError) -> Self {
Error(format!("Failed to read glob pattern: {}", e))
}
}
impl From<glob::PatternError> for Error {
/// Converts a `PatternError` into a `Error`.
fn from(e: glob::PatternError) -> Self {
Error(format!("Failed to parse glob pattern: {}", e))
}
}
/// Converts an optional OsStr to a string slice (&str).
///
/// # Arguments
///
/// * `s` - An optional OsStr reference.
///
/// # Returns
///
/// Returns a Result containing a string slice (&str) if the conversion succeeds, otherwise an
/// Error is returned with a message indicating the reason for failure.
fn to_str(s: Option<&std::ffi::OsStr>) -> Result<&str, Error> {
s.ok_or_else(|| Error(String::from("Failed to convert OsStr to str")))
.and_then(|os_str| {
os_str
.to_str()
.ok_or_else(|| Error(String::from("Failed to convert OsStr to str")))
})
}
/// Returns a vector of file names that match the given `wildcard` pattern in the directory specified by `path`.
///
/// # Arguments
///
/// * `path` - A reference to a PathBuf representing the path to the directory.
/// * `path` - A reference to a `PathBuf` representing the path to the directory.
/// * `wildcard` - The wildcard pattern to match against file names.
pub fn get_file_list(path: &PathBuf, wildcard: &str) -> Vec<String> {
#[inline]
pub fn get_file_list(path: &PathBuf, wildcard: &str) -> Result<Vec<String>, Error> {
let pattern = Path::new(path).join(wildcard);
let dir_entries: Vec<Result<PathBuf, _>> = glob(&pattern.to_string_lossy()).unwrap().collect();
dir_entries
.into_iter()
.filter_map(Result::ok)
.filter(|entry: &PathBuf| entry.is_file())
.map(|entry| entry.file_name().unwrap().to_string_lossy().to_string())
.collect()
}
/// Replaces extra tags in the `new file_name` with corresponding values from the `original_file_name`.
///
/// Extra tags:
/// * {full_name} - file name with extension
/// * {name} - file name without extension
/// * {ext} - extension
///
/// # Arguments
///
/// * `original_file_name` - The original name of the file.
/// * `new_file_name` - The new name of the file with tags to replace.
pub 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),
let options = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
};
let mut file_names = vec![];
for entry in glob_with(&pattern.to_string_lossy(), options)? {
let path = entry?;
if path.is_file() {
if let Some(file_name) = path.file_name() {
if let Some(name) = file_name.to_str() {
file_names.push(name.to_string());
}
}
})
.fold(String::new(), |result, s| result + &s)
}
}
Ok(file_names)
}
/// Replaces parts of the file name that match a regular expression pattern with a replacement string.
///
/// # Arguments
///
/// * `file_name` - The original name of the file.
/// * `original_file_name` - The original name of the file.
/// * `pattern` - A regular expression pattern to match against the file name.
/// * `replacement` - The replacement text for the matched pattern.
pub 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);
#[inline]
pub fn replace_name_with_tags(
original_file_name: &str,
pattern: &str,
replacement: &str,
) -> Result<String, Error> {
let path = Path::new(original_file_name);
let name = to_str(path.file_stem())?;
let extension = match path.extension() {
Some(ext) => to_str(Some(ext))?,
None => "",
};
let re = Regex::new(pattern).map_err(|e| Error(format!("Failed to create regex: {}", e)))?;
let mut new_file_name = String::from(replacement);
for captures in re.captures_iter(original_file_name) {
for (i, capture) in captures.iter().enumerate() {
if let Some(capture) = capture {
let capture_text = capture.as_str().to_owned();
let replace_key = format!("{{{i}}}");
replaced_text = replaced_text.replace(&replace_key, &capture_text);
let replace_key = format!("{{{}}}", i);
new_file_name = new_file_name.replace(&replace_key, &capture_text);
}
}
new_file_name = replaced_text;
}
replace_extra_tags(file_name, &new_file_name)
let result = [
("{ext}", extension),
("{full_name}", original_file_name),
("{name}", name),
]
.iter()
.fold(new_file_name, |acc, (tag, val)| acc.replace(tag, val));
Ok(result)
}
#[test]
fn groups_and_ext_test() {
fn groups_and_ext_test() -> Result<(), Error> {
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);
let output = replace_name_with_tags(INPUT, PATTERN, REPLACEMENT)?;
assert_eq!(output, RESULT);
Ok(())
}
#[test]
fn name_test() {
fn name_test() -> Result<(), Error> {
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);
let output = replace_name_with_tags(INPUT, PATTERN, REPLACEMENT)?;
assert_eq!(output, RESULT);
Ok(())
}
#[test]
fn full_name_test() {
fn full_name_test() -> Result<(), Error> {
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);
let output = replace_name_with_tags(INPUT, PATTERN, REPLACEMENT)?;
assert_eq!(output, RESULT);
Ok(())
}
#[test]
fn file_list_test() {
fn file_list_test() -> Result<(), Error> {
let directory = PathBuf::from(".");
const WILDCARD: &str = "*.toml";
assert_eq!(get_file_list(&directory, WILDCARD), vec!["Cargo.toml"]);
let result = get_file_list(&directory, WILDCARD)?;
assert_eq!(result, vec!["Cargo.toml"]);
Ok(())
}
#[test]
fn empty_file_list_test() {
fn empty_file_list_test() -> Result<(), Error> {
let directory = PathBuf::from("src");
const WILDCARD: &str = "*.exe";
assert_eq!(get_file_list(&directory, WILDCARD), Vec::<String>::new());
let result = get_file_list(&directory, WILDCARD)?;
assert_eq!(result, Vec::<String>::new());
Ok(())
}

View File

@ -7,7 +7,7 @@
//! More information on README.md
use clap::Parser;
use rmv::{get_file_list, replace_name};
use rmv::{get_file_list, replace_name_with_tags};
use std::path::PathBuf;
/// Command line parser with clap
@ -51,8 +51,15 @@ fn main() {
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);
match files {
Ok(file_names) => {
for file_name in file_names {
match replace_name_with_tags(&file_name, pattern, replacement) {
Ok(replaced_name) => println!("mv \"{}\" \"{}\"", file_name, replaced_name),
Err(e) => println!("Error: {:?}", e),
}
}
}
Err(e) => println!("Error: {:?}", e),
}
}