ren3/
lib.rs

1// Copyright (C) 2018 by Aloxaf
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20use 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}