diff --git a/bin/rename_and_move.sh b/bin/rename_and_move.sh deleted file mode 100755 index d1d406c..0000000 --- a/bin/rename_and_move.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -print_help() { - cat < - -See rename_for_shell.sh for more details on the renaming mechanics. Does not -work if the directory also has a bad name - -EXAMPLES: - fd -x rename_and_move.sh -HELP -} - -if [[ -z "$1" || "$1" == "-h" || "$1" == "--help" ]]; then - print_help - exit 1 -else - mv -n "$@" "$(~/.configs_pointer/bin/rename_for_shell.sh "$@")" &>/dev/null -fi diff --git a/bin/rename_for_shell.sh b/bin/rename_for_shell.sh deleted file mode 100755 index 6f47f85..0000000 --- a/bin/rename_for_shell.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -print_help() { - cat < - -Removes characters that shouldn't be used in file names. Effectively leaves -/[0-9A-z-_.]/ with a few additional rules (like no leading hyphens) -HELP -} - -if [[ -z "$1" ]]; then - print_help - exit 1 -else - echo "$@" | awk '{ - split($0, a, "") - - for (i in a) { - c=a[i] - - if (!length(name) && c == "-") - continue; - else if (c ~ /[0-9A-Za-z_.\-\/]/) - name = name c - else if (c == " ") - name = name "_" - else if (c == ":" || c == ";") - name = name "-" - } - } - - END { print name }' -fi diff --git a/bin/rewritten_in_rust/Cargo.toml b/bin/rewritten_in_rust/Cargo.toml index 2426d73..6f66a18 100644 --- a/bin/rewritten_in_rust/Cargo.toml +++ b/bin/rewritten_in_rust/Cargo.toml @@ -14,7 +14,12 @@ path = "src/prettify_bash_history.rs" name = "sway_tree" path = "src/sway_tree.rs" +[[bin]] +name = "rename_for_unix" +path = "src/rename_for_unix.rs" + [dependencies] clap = { version = "4", features = ["derive"] } chrono = "0.4" regex = "1" +termion = "2" diff --git a/bin/rewritten_in_rust/src/rename_for_unix.rs b/bin/rewritten_in_rust/src/rename_for_unix.rs new file mode 100644 index 0000000..f06f88d --- /dev/null +++ b/bin/rewritten_in_rust/src/rename_for_unix.rs @@ -0,0 +1,145 @@ +use clap::Parser; +use std::fs; +use std::path::{Path, PathBuf}; +use termion::color as tc; + +#[derive(Parser, Debug)] +#[command(name = "rename_for_unix", author, version = "0.1.2")] +/// Renames a file to a reasonable (no special characters) name +/// +/// Removes leading hyphens. Final file name character set is in /[0-9A-z._-]/, unless the +/// --no-caps flag is used +struct Args { + /// Do not move the file, just print the changes that will be made + #[arg(long = "dry-run")] + is_dry_run: bool, + /// Do not print the new file's path to stdout after moving + #[arg(long = "silent")] + is_silent: bool, + /// Convert all letters to lowercase + #[arg(long = "no-caps")] + is_no_caps: bool, + /// Rename all files/directories in the specified directory, not including itself + #[arg(long = "full-directory")] + is_full_directory: bool, + /// Path to file that'll be renamed + #[arg(value_name = "PATH")] + original_name: PathBuf, +} + +type Try = Result>; + +fn main() -> Try<()> { + let args = Args::parse(); + let mut to_rename: Vec; + + // Validate args + if args.is_full_directory && args.original_name.is_dir() { + to_rename = args.original_name.read_dir() + .expect("Unable to read directory") + .map(|x| x.expect("IO error while reading directory").path()) + .collect(); + } else if args.is_full_directory { + eprintln!("Provided path is not a directory. Incompatible option --full-directory"); + std::process::exit(1); + } else { + to_rename = vec![args.original_name]; + } + + // Sort by length then subsort alphabetically + to_rename.sort_by(|a, b| { + let sa = a.to_str().unwrap_or(""); + let sb = b.to_str().unwrap_or(""); + + if sa.len() == sb.len() { + sa.cmp(&sb) + } else { + sa.len().cmp(&sb.len()) + } + }); + + 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); + + let og_base_name = original_name.file_name() + .unwrap() + .to_str() + .unwrap_or(""); + + 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)?; + } + } else { + eprintln!("Error while moving \"{}\": {:?}", og_base_name, res_rename); + } + } + + Ok(()) +} + +fn fix_name(original_name: &Path, is_caps: bool) -> Try { + let base_name = original_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() { + match c { + '-' if s.is_empty() => continue, + ':' if s.is_empty() => s.push('_'), + ';' if s.is_empty() => s.push('_'), + '-' => s.push(c), + '_' => s.push(c), + '.' => s.push(c), + ' ' => s.push('_'), + ']' => s.push('_'), + ':' => s.push('-'), + ';' => s.push('-'), + '&' => s.push_str("_and_"), + _ if c.is_digit(10) => s.push(c), + _ if c.is_lowercase() => s.push(c), + _ if c.is_uppercase() && is_caps => s.push(c), + _ 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'); + + if c1 == '_' && c2 == '_' { + s.pop(); + } + } + + // Don't make dot file if it wasn't one initially + let og_first_char = original_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)) +}