use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::PathBuf;
use std::{fs, io};
use std::process::exit;
use clap::Parser;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(value_name = "DIR_PATH")]
path: String,
#[arg(short, long, default_value_t = String::new())]
file: String,
#[arg(short = 'n', long, action = clap::ArgAction::SetTrue)]
dry_run: bool,
}
fn get_new_name_list(file_path: &str) -> Result<Vec<String>, &str> {
if (file_path).is_empty() {
if atty::isnt(atty::Stream::Stdin) {
Ok(read_input_stream(io::stdin()))
} else {
Err("renls: error: stdin buffer is empty")
}
} else {
match File::open(file_path) {
Ok(file) => Ok(read_input_stream(file)),
Err(_) => Err("renls: error: unable to read file"),
}
}
}
fn read_input_stream<R: Read>(input_stream: R) -> Vec<String> {
BufReader::new(input_stream)
.lines()
.filter(|l| l.is_ok() && !(l.as_ref().unwrap().is_empty() || l.as_ref().unwrap().starts_with('#')))
.map(|l| l.unwrap().trim().to_string())
.collect()
}
fn get_file_list(dir_path: &str) -> Result<Vec<PathBuf>, &str>{
match fs::read_dir(dir_path) {
Ok(paths) => {
let mut file_list: Vec<PathBuf> = paths
.into_iter()
.filter(|p| Result::is_ok(p))
.map(|p| p.unwrap().path())
.collect();
file_list.sort();
Ok(file_list)
},
Err(_) => Err("renls: error: unable to read directory"),
}
}
fn make_rename_pair(new_name_list: &[String], file_list: &[PathBuf]) -> HashMap<PathBuf, PathBuf> {
file_list
.iter()
.enumerate()
.map(|(i, f)| {
let ext_str = match f.extension() {
Some(ext) => format!(".{}", ext.to_str().unwrap_or("")),
None => String::new(),
};
let new_filename = format!("{}{}", &new_name_list[i], ext_str);
let new_filepath = f.parent().unwrap().join(new_filename);
(f.clone(), new_filepath)
})
.collect()
}
fn print_rename_proposal(rename_pairs: &HashMap<PathBuf, PathBuf>) {
for (k, v) in rename_pairs.iter() {
println!("{} --> {}", k.display(), v.display());
}
}
fn rename_files(rename_pairs: &HashMap<PathBuf, PathBuf>) {
for (k, v) in rename_pairs {
if fs::rename(k, v).is_err() {
eprintln!("renls: warning: unable to rename file \"{}\"", k.display());
}
}
}
fn main() {
let args = Args::parse();
let new_name_list = match get_new_name_list(&args.file) {
Ok(list) => list,
Err(error_message) => {
eprintln!("{error_message}");
exit(1);
}
};
let ren_file_list = match get_file_list(&args.path) {
Ok(list) => list,
Err(error_message) => {
eprintln!("{error_message}");
exit(1);
}
};
if new_name_list.len() != ren_file_list.len() {
eprintln!("renls: error: file list and new name list do not have the same number of items");
exit(1);
}
let rename_pairs = make_rename_pair(&new_name_list, &ren_file_list);
if args.dry_run {
print_rename_proposal(&rename_pairs);
} else {
rename_files(&rename_pairs);
}
}