use super::config::{Config, KEYWORDS_REGEX, REGEX_TABLE};
use super::datatypes::*;
use log::debug;
use regex::Regex;
use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use std::fs::{self, read_dir, OpenOptions};
use std::io::{self, prelude::*, BufReader};
use std::process::Command;
use std::sync::{Arc, RwLock};
use std::{io::Result, path::Path, path::PathBuf, thread};
type Dirs = Vec<PathBuf>;
#[derive(Debug)]
struct File(PathBuf, &'static Regex);
impl File {
fn to_string(&self) -> String {
self.0.as_os_str().to_os_string().into_string().unwrap()
}
}
type Files = Vec<File>;
fn files_in_dir_or_file_vec(paths_or_files: &[impl AsRef<Path>], conf: &Config) -> Result<Files> {
let mut result: Files = vec![];
for ele in paths_or_files {
if ele.as_ref().is_dir() {
result.append(&mut all_files_in_dir(ele, conf)?)
} else {
file_checker(
&mut result,
ele.as_ref(),
&conf.filetypes,
conf.filetypes.len(),
)
}
}
Ok(result)
}
fn all_files_in_dir<T>(p: T, conf: &Config) -> Result<Files>
where
T: AsRef<Path>,
{
let mut result = vec![];
let (mut files, dirs) = files_and_dirs_in_path(p, &conf)?;
result.append(&mut files);
if dirs.len() != 0 {
result.append(
&mut dirs
.iter()
.map(|d| all_files_in_dir(d, conf).unwrap())
.flatten()
.collect::<Files>(),
)
}
Ok(result)
}
fn files_and_dirs_in_path(p: impl AsRef<Path>, conf: &Config) -> Result<(Files, Dirs)> {
let (mut f, mut d): (Files, Dirs) = (vec![], vec![]);
let filetypes = &conf.filetypes;
let filetypes_count = filetypes.len();
let ignore_dirs = &conf.ignore_dirs;
let ignore_dirs_count = ignore_dirs.len();
for entry in read_dir(p)? {
let dir = entry?;
let path = dir.path();
if path.is_dir() {
if ignore_dirs_count != 0 {
if let Some(d_name) = path.file_name() {
if !ignore_dirs.contains(&d_name.to_os_string()) {
d.push(path)
}
}
} else {
d.push(path)
}
} else {
file_checker(&mut f, &path, &filetypes, filetypes_count)
}
}
Ok((f, d))
}
fn file_checker(files: &mut Files, path: &Path, filetypes: &[OsString], filetypes_count: usize) {
if filetypes_count != 0 {
if let Some(t) = path.extension() {
if filetypes.contains(&t.to_os_string()) {
let aa = REGEX_TABLE.lock();
if let Some(re) = aa.as_ref().unwrap().get(t.to_str().unwrap()) {
let re = unsafe {
match (re as *const Regex).clone().as_ref() {
Some(a) => a,
None => return,
}
};
files.push(File(path.to_path_buf(), re))
}
}
}
} else {
if let Some(t) = path.extension() {
let aa = REGEX_TABLE.lock();
if let Some(re) = aa.as_ref().unwrap().get(t.to_str().unwrap()) {
let re = unsafe {
match (re as *const Regex).clone().as_ref() {
Some(a) => a,
None => return,
}
};
files.push(File(path.to_path_buf(), re))
}
}
}
}
fn filter_line(line: &str, line_num: usize, re: &Regex) -> Option<Crumb> {
match re.find(line) {
Some(mat) => {
let position = mat.start();
let content = re.captures(line).unwrap()[2].to_string();
if content.starts_with('!') {
Some(Crumb::new(line_num, position, None, content).add_ignore_flag())
} else {
Some(Crumb::new(line_num, position, None, content))
}
}
None => None,
}
}
fn op_file(file: File, kwreg: &Option<Regex>, conf: Arc<RwLock<Config>>) -> Result<Option<Bread>> {
let breads = match bake_bread(&file, kwreg, &conf.read().unwrap()) {
Ok(b) => b,
Err(e) => {
debug!("file {} had error {}", file.to_string(), e.to_string());
return Ok(None);
}
};
if !conf.read().unwrap().delete {
Ok(breads)
} else {
match breads {
Some(bb) => {
clean_the_crumbs(bb)?;
Ok(None)
}
None => Ok(None),
}
}
}
fn bake_bread(file: &File, kwreg: &Option<Regex>, conf: &Config) -> Result<Option<Bread>> {
let mut buf = vec![];
let file_p = file.to_string();
let mut f: std::fs::File = std::fs::File::open(file.0.clone())?;
f.read_to_end(&mut buf)?;
let mut line_num = 0;
let mut ss = String::new(); let mut buf = buf.as_slice();
let mut result = vec![];
let mut head: Option<Crumb> = None; let mut keyword_checker_and_push = |mut cb: Crumb| {
if kwreg.is_some() {
if cb.filter_keywords(kwreg.as_ref().unwrap()) {
result.push(cb)
}
} else {
if !cb.is_ignore() || conf.show_ignored {
result.push(cb)
}
}
};
loop {
line_num += 1;
match buf.read_line(&mut ss) {
Ok(0) | Err(_) => {
if head.is_some() {
keyword_checker_and_push(head.unwrap());
}
break; }
Ok(_) => match filter_line(&ss, line_num, file.1) {
Some(cb) => {
match head {
Some(ref mut h) => {
if h.has_tail() {
h.add_tail(cb);
ss.clear(); continue;
} else {
keyword_checker_and_push(head.unwrap());
head = None;
}
}
None => (),
}
if cb.has_tail() {
head = Some(cb);
} else {
keyword_checker_and_push(cb)
}
}
None => {
if head.is_some() {
keyword_checker_and_push(head.unwrap());
head = None;
}
}
},
}
ss.clear()
}
if result.len() == 0 {
Ok(None)
} else {
Ok(Some(Bread::new(file_p, result)))
}
}
pub fn clean_the_crumbs(Bread { file_path, crumbs }: Bread) -> Result<String> {
let all_delete_line_postion_pairs = crumbs
.iter()
.map(|crumb| crumb.all_lines_num_postion_pair())
.flatten();
delete_lines_on(&file_path, all_delete_line_postion_pairs)?;
println!("cleaned the crumbs in {}", file_path);
Ok(file_path)
}
pub fn clean_the_crumbs_on_special_index(
Bread { file_path, crumbs }: Bread,
indexes: HashSet<usize>,
) -> Result<String> {
let mut all_delete_lines = vec![];
for ind in &indexes {
match crumbs.get(*ind) {
Some(c) => all_delete_lines.append(&mut c.all_lines_num_postion_pair()),
None => return Err(io::Error::other("cannot find crumb index in bread")),
}
}
delete_lines_on(&file_path, all_delete_lines.into_iter())?;
println!("cleaned {} crumbs in {}", indexes.len(), file_path);
Ok(file_path)
}
fn delete_lines_on(
file_path: &str,
line_num_pos_pairs: impl Iterator<Item = (usize, usize)>,
) -> Result<()> {
let f = fs::File::open(&file_path)?;
let reader = BufReader::new(f).lines();
let all_delete_lines = line_num_pos_pairs.collect();
let finish_deleted = delete_nth_lines(reader, all_delete_lines)?
.into_iter()
.map(|line| line.into_bytes());
let mut new_file = OpenOptions::new()
.write(true)
.truncate(true)
.open(file_path.clone())?;
for line in finish_deleted {
new_file.write_all(&line)?;
new_file.write_all(b"\n")?
}
Ok(())
}
fn delete_nth_lines(
f: impl Iterator<Item = Result<String>>,
nm: HashMap<usize, usize>,
) -> Result<Vec<String>> {
let mut result = vec![];
for (line_num, ll) in f.enumerate() {
if nm.contains_key(&(line_num + 1)) {
let mut new_l = ll?;
new_l.truncate(*nm.get(&(line_num + 1)).unwrap());
if new_l == "" {
continue;
}
result.push(new_l);
} else {
result.push(ll?);
}
}
Ok(result)
}
pub fn run_format_command_to_file(
fmt_command: &str,
_files: impl IntoIterator<Item = String>,
) -> std::result::Result<(), String> {
let mut command_splits = fmt_command.split(' ');
let first = command_splits
.next()
.ok_or("fmt_command cannot be empty".to_string())?;
let mut comm = Command::new(first);
let mut child = comm
.args(command_splits)
.spawn()
.expect("Cannot run the fmt_command");
println!("running fmt command: {}", fmt_command);
child
.wait()
.expect("fmt command wasn't running")
.exit_ok()
.map_err(|e| e.to_string())
}
pub fn handle_files(conf: Config) -> impl Iterator<Item = Bread> {
let mut all_files: Vec<File> = files_in_dir_or_file_vec(&conf.files, &conf).unwrap();
let threads_num = 24;
let len = all_files.len();
let count = len / threads_num;
let mut groups: Vec<Vec<File>> = vec![];
for _ in 0..threads_num - 1 {
groups.push(all_files.drain(0..count).collect())
}
groups.push(all_files.drain(0..).collect());
let conf = Arc::new(RwLock::new(conf));
groups
.into_iter()
.map(move |fs| {
let kwreg = KEYWORDS_REGEX.lock().unwrap().clone();
let conf_c = Arc::clone(&conf);
thread::spawn(|| {
fs.into_iter()
.filter_map(move |f| op_file(f, &kwreg, conf_c.clone()).unwrap())
.collect::<Vec<Bread>>()
})
})
.map(|han| han.join().unwrap())
.flatten()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_files_and_dirs_in_path() -> Result<()> {
let (fs, dirs) = files_and_dirs_in_path("./tests/testcases", &Default::default())?;
assert_eq!(dirs.len(), 0);
assert_eq!(fs[0].0, PathBuf::from("./tests/testcases/multilines.rs"),);
Ok(())
}
}