From 87a95da99f76aaeabc974e2a86fea6fbaf836a73 Mon Sep 17 00:00:00 2001 From: Akemi Izuko Date: Sat, 23 Dec 2023 20:14:06 -0700 Subject: [PATCH] Rewrite: bug fixes --- .../src/prettify_bash_history.rs | 2 +- bin/rewritten_in_rust/src/rename_for_unix.rs | 249 +++++++++++++++--- 2 files changed, 209 insertions(+), 42 deletions(-) diff --git a/bin/rewritten_in_rust/src/prettify_bash_history.rs b/bin/rewritten_in_rust/src/prettify_bash_history.rs index f7fcca2..c12a75c 100644 --- a/bin/rewritten_in_rust/src/prettify_bash_history.rs +++ b/bin/rewritten_in_rust/src/prettify_bash_history.rs @@ -43,7 +43,7 @@ fn main() -> Result<(), Box> { for line in lines { if timestamp.is_match(&line) { let time = NaiveDateTime::from_timestamp_opt(line[1..].parse()?, 0).unwrap(); - let fmttime = format!("{}", time); + let fmttime = time.format("%a %b %e %T %Y").to_string(); let start_line = starting_for_command_line(args.style); let time_line = format_time_line(&fmttime, args.style); diff --git a/bin/rewritten_in_rust/src/rename_for_unix.rs b/bin/rewritten_in_rust/src/rename_for_unix.rs index f06f88d..d3afede 100644 --- a/bin/rewritten_in_rust/src/rename_for_unix.rs +++ b/bin/rewritten_in_rust/src/rename_for_unix.rs @@ -58,53 +58,72 @@ fn main() -> Try<()> { } }); - for original_relative_name in to_rename { - let original_name = fs::canonicalize(original_relative_name)?; - let res_rename = fix_name(&original_name, !args.is_no_caps); + // Create a vector of valid rename mappings + let rename_pairs = match get_renamed_pairs(&to_rename, !args.is_no_caps) { + Ok(rp) => rp, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; - let og_base_name = original_name.file_name() - .unwrap() - .to_str() - .unwrap_or(""); + for (og_full_name, new_full_name) in rename_pairs { + let og_base_name = og_full_name.file_name().unwrap().to_str().unwrap(); + let new_base_name = new_full_name.file_name().unwrap().to_str().unwrap(); - if let Ok(new_name) = res_rename { - let new_base_name = new_name.file_name().unwrap(); - - if new_name == original_name { - continue; - } else if new_name.exists() { - eprintln!("New file name {:?} already exists.", new_name); - eprintln!("{:?}", original_name); - eprintln!("{:?}", new_name); - std::process::exit(1); - } - - if args.is_dry_run { - println!("{}\"{}\"{} -> {}{:?}{}", - tc::Fg(tc::Red), og_base_name, tc::Fg(tc::Reset), - tc::Fg(tc::Green), new_base_name, tc::Fg(tc::Reset)); - } else if !args.is_silent { - println!("{}", new_name.to_str().unwrap()); - } - - if !args.is_dry_run { - fs::rename(original_name, new_name)?; - } + if args.is_dry_run { + println!("{}\"{}\"{} -> {}{:?}{}", + tc::Fg(tc::Red), og_base_name, tc::Fg(tc::Reset), + tc::Fg(tc::Green), new_base_name, tc::Fg(tc::Reset)); } else { - eprintln!("Error while moving \"{}\": {:?}", og_base_name, res_rename); + if !args.is_silent { + println!("{}", new_full_name.to_str().unwrap()); + } + + fs::rename(og_full_name, new_full_name)?; } } Ok(()) } -fn fix_name(original_name: &Path, is_caps: bool) -> Try { - let base_name = original_name.file_name().unwrap(); +fn get_renamed_pairs(to_rename: &[PathBuf], is_caps: bool) -> Try> { + let mut rename_pairs: Vec<(PathBuf, PathBuf)> = Vec::new(); + + for og_rel_name in to_rename { + let og_full_name = fs::canonicalize(og_rel_name)?; + let new_full_name = fix_name(&og_full_name, is_caps)?; + + let conflict_index = rename_pairs.iter().position(|r| r.1 == new_full_name); + + // Check for naming conflict + if new_full_name == og_full_name { + // pass + } else if new_full_name.exists() { + return Err(format!("New file name {:?} already exists.", new_full_name).into()); + } else if conflict_index.is_some() { + let index = conflict_index.unwrap(); + + return Err(ManyToOneErr { + from_1: rename_pairs.swap_remove(index).0, + from_2: og_full_name, + to_name: new_full_name, + }.into()); + } + + rename_pairs.push((og_full_name, new_full_name)); + } + + Ok(rename_pairs) +} + +fn fix_name(og_full_name: &Path, is_caps: bool) -> Try { + let og_base_name = og_full_name.file_name().unwrap(); let mut new_base_name = String::new(); let s = &mut new_base_name; // Just an alias - for c in base_name.to_str().expect("Valid unicode").chars() { + for c in og_base_name.to_str().expect("Valid unicode").chars() { match c { '-' if s.is_empty() => continue, ':' if s.is_empty() => s.push('_'), @@ -114,6 +133,8 @@ fn fix_name(original_name: &Path, is_caps: bool) -> Try { '.' => s.push(c), ' ' => s.push('_'), ']' => s.push('_'), + ')' => s.push('_'), + '}' => s.push('_'), ':' => s.push('-'), ';' => s.push('-'), '&' => s.push_str("_and_"), @@ -123,23 +144,169 @@ fn fix_name(original_name: &Path, is_caps: bool) -> Try { _ if c.is_uppercase() => s.push(c.to_ascii_lowercase()), _ => continue, } + } - // Combine consecutive underscores - let c1 = s.chars().rev().nth(0).unwrap_or('a'); - let c2 = s.chars().rev().nth(1).unwrap_or('a'); + // Combine consecutive underscores and odd-looking _. + let mut new_base_name_vec: Vec = new_base_name.chars().collect(); + let mut i = 1; + + while i < new_base_name_vec.len() { + let c1 = new_base_name_vec[i-1]; + let c2 = new_base_name_vec[i]; if c1 == '_' && c2 == '_' { - s.pop(); + new_base_name_vec.remove(i-1); + } else if c1 == '_' && c2 == '.' { + new_base_name_vec.remove(i-1); + } else { + i += 1; } } - // Don't make dot file if it wasn't one initially - let og_first_char = original_name.to_str().unwrap().chars().next().unwrap_or('_'); + // Removes tailing underscores + while let Some(c) = new_base_name_vec.last() { + if *c != '_' { + break; + } + new_base_name_vec.pop(); + } + + new_base_name = new_base_name_vec.into_iter().collect(); + + // Don't make a dot file if the original wasn't one + let og_first_char = og_base_name.to_str().unwrap().chars().next().unwrap_or('_'); let new_first_char = new_base_name.chars().next().unwrap_or('_'); if og_first_char != '.' && new_first_char == '.' { new_base_name.insert(0, '_'); } - Ok(original_name.with_file_name(&new_base_name)) + if new_base_name.len() == 0 || new_base_name == "." || new_base_name == ".." { + Err(format!("{:?} has no name when renamed", og_base_name).into()) + } else { + Ok(og_full_name.with_file_name(&new_base_name)) + } +} + +#[derive(Debug)] +struct ManyToOneErr { + from_1: PathBuf, + from_2: PathBuf, + to_name: PathBuf, +} + +impl std::fmt::Display for ManyToOneErr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let red = tc::Fg(tc::Red); + let green = tc::Fg(tc::Green); + let reset = tc::Fg(tc::Reset); + + write!(f, "Error: many to one renaming!\n{}{:?}{} <- {}{:?}{}\n{}{:?}{} <- {}{:?}{}", + green, self.to_name, reset, red, self.from_1, reset, + green, self.to_name, reset, red, self.from_2, reset) + } +} + +impl std::error::Error for ManyToOneErr {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tailing_underscores() { + let one_tail = PathBuf::from(r"/mydir/sometail)"); + let mul_tail = PathBuf::from(r"/mydir/some tail)]])))"); + let and_tail = PathBuf::from(r"/mydir/some tail)]])))&"); + + let one_answer = PathBuf::from(r"/mydir/sometail"); + let mul_answer = PathBuf::from(r"/mydir/some_tail"); + let and_answer = PathBuf::from(r"/mydir/some_tail_and"); + + let one_tail_fix = fix_name(&one_tail, true).unwrap(); + let mul_tail_fix = fix_name(&mul_tail, true).unwrap(); + let and_tail_fix = fix_name(&and_tail, true).unwrap(); + + assert_eq!(one_tail_fix, one_answer); + assert_eq!(mul_tail_fix, mul_answer); + assert_eq!(and_tail_fix, and_answer); + } + + #[test] + fn blank_names() { + let blank_1 = PathBuf::from(r"/mydir/deep/((((("); + let blank_2 = PathBuf::from(r"/mydir/deep/((((()"); + let blank_3 = PathBuf::from(r"/mydir/deep/([[{()}]]]]]"); + let blank_4 = PathBuf::from(r"/mydir/deep/.([[{()}]]]]]"); + + let fix_1 = fix_name(&blank_1, true); + let fix_2 = fix_name(&blank_2, true); + let fix_3 = fix_name(&blank_3, true); + let fix_4 = fix_name(&blank_4, true); + + assert!(fix_1.is_err()); + assert!(fix_2.is_err()); + assert!(fix_3.is_err()); + assert!(fix_4.is_err()); + } + + #[test] + fn no_create_dotfiles() { + let dots_1 = PathBuf::from(r"/mydir/deep/_.name"); + let dots_2 = PathBuf::from(r"/mydir/deep/.some image.avif"); + let dots_3 = PathBuf::from(r"/mydir/deep/([[{(.]]]]]"); + + let answer_1 = PathBuf::from(r"/mydir/deep/_.name"); + let answer_2 = PathBuf::from(r"/mydir/deep/.some_image.avif"); + let answer_3 = PathBuf::from(r"/mydir/deep/_."); + + let fix_1 = fix_name(&dots_1, true).unwrap(); + let fix_2 = fix_name(&dots_2, true).unwrap(); + let fix_3 = fix_name(&dots_3, true).unwrap(); + + assert_eq!(fix_1, answer_1); + assert_eq!(fix_2, answer_2); + assert_eq!(fix_3, answer_3); + } + + #[test] + fn no_capitals() { + let dots_1 = PathBuf::from(r"/mydir/deep/_.NAME"); + let dots_2 = PathBuf::from(r"/mydir/deep/.some Image.avif"); + let dots_3 = PathBuf::from(r"/mydir/deep/([[{(.]]]]]"); + + let answer_1 = PathBuf::from(r"/mydir/deep/_.name"); + let answer_2 = PathBuf::from(r"/mydir/deep/.some_image.avif"); + let answer_3 = PathBuf::from(r"/mydir/deep/_."); + + let fix_1 = fix_name(&dots_1, false).unwrap(); + let fix_2 = fix_name(&dots_2, false).unwrap(); + let fix_3 = fix_name(&dots_3, false).unwrap(); + + assert_eq!(fix_1, answer_1); + assert_eq!(fix_2, answer_2); + assert_eq!(fix_3, answer_3); + } + + #[test] + fn many_to_one_mapping() { + fn touch(path: &Path) -> std::io::Result<()> { + match fs::OpenOptions::new().create(true).write(true).open(path) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + let paths = vec![ + PathBuf::from(r"/tmp/[somefile]&conflict.avif"), + PathBuf::from(r"/tmp/somefile and (conflict).avif))"), + ]; + + paths.iter().for_each(|p| touch(p).expect("Failed to create files for test")); + + let pairs_res = get_renamed_pairs(&paths, true); + + assert!(pairs_res.is_err()); + assert_eq!(&pairs_res.err().unwrap().to_string()[..28], "Error: many to one renaming!"); + } }