1use colored::*;
21use std::borrow::Cow;
22use std::ffi::OsString;
23use std::fs;
24use std::io;
25use std::path::PathBuf;
26use std::process;
27
28pub mod sedregex;
29
30use crate::sedregex::{split_regex, ErrorKind};
31
32#[derive(Debug)]
33enum Error {
34 UnknownFileName(OsString),
35 RenameError(String, String, io::Error),
36}
37
38pub struct Args {
39 pub dir_only: bool,
40 pub file_only: bool,
41 pub recursive: bool,
42 pub apply: bool,
43 pub brief: bool,
44}
45
46pub struct SedRegex {
47 re: regex::Regex,
48 rep: String,
49 global: bool,
50}
51
52impl SedRegex {
53 pub fn new(expression: &str) -> SedRegex {
54 let replace_data = split_regex(expression).unwrap_or_else(|e| {
55 eprintln!("regex split error: {:?}", e);
56 process::exit(1);
57 });
58 let re = replace_data.build_regex().unwrap_or_else(|e| {
59 if let ErrorKind::RegexError(e) = e {
60 eprintln!("{}", e);
61 }
62 process::exit(1);
63 });
64 SedRegex {
65 re,
66 rep: replace_data.replace_str,
67 global: replace_data.flag_global,
68 }
69 }
70
71 #[inline]
72 pub fn replace<'t>(&self, text: &'t str) -> Cow<'t, str> {
73 if self.global {
74 self.re.replace_all::<&str>(text, self.rep.as_ref())
75 } else {
76 self.re.replace::<&str>(text, self.rep.as_ref())
77 }
78 }
79
80 #[inline]
81 pub fn is_match(&self, text: &str) -> bool {
82 self.re.is_match(text)
83 }
84}
85
86pub fn list_and_rename_files(re: &SedRegex, dir: &str, args: &Args) {
87 let paths = match fs::read_dir(dir) {
88 Ok(paths) => paths,
89 Err(e) => {
90 eprintln!("Unable to open directory '{}': {}", dir, e);
91 return;
92 }
93 };
94
95 for path in paths {
96 let path = path.unwrap();
97 let path = path.path();
98
99 if args.recursive && path.is_dir() {
100 list_and_rename_files(re, path.to_str().unwrap(), args);
101 }
102
103 if (args.dir_only && !path.is_dir()) || (args.file_only && !path.is_file()) {
104 continue;
105 }
106
107 rename_file(&re, &path, args).unwrap_or_else(|e| match e {
108 Error::UnknownFileName(filename) => eprintln!(
109 "{}",
110 format!("[ERROR] Unknown file name {:?}", filename).bright_red()
111 ),
112 Error::RenameError(old, new, err) => eprintln!(
113 "{}",
114 format!("[ERROR] Failed to rename {} -> {}: {}", old, new, err).bright_red()
115 ),
116 });
117 }
118}
119
120#[inline]
121fn rename_file(re: &SedRegex, file: &PathBuf, args: &Args) -> Result<(), Error> {
122 let old_path = file;
123 let old_file_name = match old_path.file_name().unwrap().to_str() {
124 Some(value) => value,
125 None => {
126 return Err(Error::UnknownFileName(
127 old_path.file_name().unwrap().to_os_string(),
128 ));
129 }
130 };
131
132 if !re.is_match(old_file_name) {
133 return Ok(());
134 }
135
136 let new_file_name = re.replace(old_file_name);
137
138 let mut new_path = old_path.clone();
139 new_path.set_file_name(&new_file_name.to_string());
140
141 let old_full_name = old_path.to_str().unwrap();
142 let new_full_name = new_path.to_str().unwrap();
143
144 if args.apply {
145 if let Err(e) = fs::rename(old_full_name, new_full_name) {
146 return Err(Error::RenameError(
147 old_full_name.to_string(),
148 new_full_name.to_string(),
149 e,
150 ));
151 }
152 }
153
154 if args.brief {
155 println!(
156 "{}",
157 format!("[OK] {}\t-> {}", old_file_name, new_file_name).bright_green()
158 );
159 } else {
160 println!(
161 "{}",
162 format!("[OK] {}\t-> {}", old_full_name, new_full_name).bright_green()
163 );
164 }
165 Ok(())
166}