Rewrite: bug fixes
This commit is contained in:
parent
a28388f93c
commit
87a95da99f
|
@ -43,7 +43,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if timestamp.is_match(&line) {
|
if timestamp.is_match(&line) {
|
||||||
let time = NaiveDateTime::from_timestamp_opt(line[1..].parse()?, 0).unwrap();
|
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 start_line = starting_for_command_line(args.style);
|
||||||
let time_line = format_time_line(&fmttime, args.style);
|
let time_line = format_time_line(&fmttime, args.style);
|
||||||
|
|
|
@ -58,53 +58,72 @@ fn main() -> Try<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for original_relative_name in to_rename {
|
// Create a vector of valid rename mappings
|
||||||
let original_name = fs::canonicalize(original_relative_name)?;
|
let rename_pairs = match get_renamed_pairs(&to_rename, !args.is_no_caps) {
|
||||||
let res_rename = fix_name(&original_name, !args.is_no_caps);
|
Ok(rp) => rp,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let og_base_name = original_name.file_name()
|
for (og_full_name, new_full_name) in rename_pairs {
|
||||||
.unwrap()
|
let og_base_name = og_full_name.file_name().unwrap().to_str().unwrap();
|
||||||
.to_str()
|
let new_base_name = new_full_name.file_name().unwrap().to_str().unwrap();
|
||||||
.unwrap_or("<original-base-name>");
|
|
||||||
|
|
||||||
if let Ok(new_name) = res_rename {
|
if args.is_dry_run {
|
||||||
let new_base_name = new_name.file_name().unwrap();
|
println!("{}\"{}\"{} -> {}{:?}{}",
|
||||||
|
tc::Fg(tc::Red), og_base_name, tc::Fg(tc::Reset),
|
||||||
if new_name == original_name {
|
tc::Fg(tc::Green), new_base_name, tc::Fg(tc::Reset));
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
} else {
|
} 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_name(original_name: &Path, is_caps: bool) -> Try<PathBuf> {
|
fn get_renamed_pairs(to_rename: &[PathBuf], is_caps: bool) -> Try<Vec<(PathBuf, PathBuf)>> {
|
||||||
let base_name = original_name.file_name().unwrap();
|
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<PathBuf> {
|
||||||
|
let og_base_name = og_full_name.file_name().unwrap();
|
||||||
|
|
||||||
let mut new_base_name = String::new();
|
let mut new_base_name = String::new();
|
||||||
let s = &mut new_base_name; // Just an alias
|
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 {
|
match c {
|
||||||
'-' if s.is_empty() => continue,
|
'-' if s.is_empty() => continue,
|
||||||
':' if s.is_empty() => s.push('_'),
|
':' if s.is_empty() => s.push('_'),
|
||||||
|
@ -114,6 +133,8 @@ fn fix_name(original_name: &Path, is_caps: bool) -> Try<PathBuf> {
|
||||||
'.' => s.push(c),
|
'.' => s.push(c),
|
||||||
' ' => s.push('_'),
|
' ' => s.push('_'),
|
||||||
']' => s.push('_'),
|
']' => s.push('_'),
|
||||||
|
')' => s.push('_'),
|
||||||
|
'}' => s.push('_'),
|
||||||
':' => s.push('-'),
|
':' => s.push('-'),
|
||||||
';' => s.push('-'),
|
';' => s.push('-'),
|
||||||
'&' => s.push_str("_and_"),
|
'&' => s.push_str("_and_"),
|
||||||
|
@ -123,23 +144,169 @@ fn fix_name(original_name: &Path, is_caps: bool) -> Try<PathBuf> {
|
||||||
_ if c.is_uppercase() => s.push(c.to_ascii_lowercase()),
|
_ if c.is_uppercase() => s.push(c.to_ascii_lowercase()),
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Combine consecutive underscores
|
// Combine consecutive underscores and odd-looking _.
|
||||||
let c1 = s.chars().rev().nth(0).unwrap_or('a');
|
let mut new_base_name_vec: Vec<char> = new_base_name.chars().collect();
|
||||||
let c2 = s.chars().rev().nth(1).unwrap_or('a');
|
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 == '_' {
|
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
|
// Removes tailing underscores
|
||||||
let og_first_char = original_name.to_str().unwrap().chars().next().unwrap_or('_');
|
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('_');
|
let new_first_char = new_base_name.chars().next().unwrap_or('_');
|
||||||
|
|
||||||
if og_first_char != '.' && new_first_char == '.' {
|
if og_first_char != '.' && new_first_char == '.' {
|
||||||
new_base_name.insert(0, '_');
|
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!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue