1use super::config::{Config, KEYWORDS_REGEX, REGEX_TABLE};
2use super::datatypes::*;
3use log::debug;
4use regex::Regex;
5use std::collections::{HashMap, HashSet};
6use std::ffi::OsString;
7use std::fs::{self, read_dir, OpenOptions};
8use std::io::{self, prelude::*, BufReader};
9use std::num::NonZeroUsize;
10use std::process::Command;
11use std::sync::{Arc, RwLock};
12use std::{io::Result, path::Path, path::PathBuf, thread};
13
14const THREAD_NUM: Option<NonZeroUsize> = NonZeroUsize::new(4);
16
17type Dirs = Vec<PathBuf>;
19
20#[derive(Debug)]
23struct File(PathBuf, &'static Regex);
24
25impl File {
26 fn to_string(&self) -> String {
28 self.0.as_os_str().to_os_string().into_string().unwrap()
29 }
30}
31
32type Files = Vec<File>;
33
34fn files_in_dir_or_file_vec(paths_or_files: &[impl AsRef<Path>], conf: &Config) -> Result<Files> {
37 let mut result: Files = vec![];
38 for ele in paths_or_files {
39 if ele.as_ref().is_dir() {
40 result.append(&mut all_files_in_dir(ele, conf)?)
41 } else {
42 file_checker(
43 &mut result,
44 ele.as_ref(),
45 &conf.filetypes,
46 conf.filetypes.len(),
47 )
48 }
49 }
50 Ok(result)
51}
52
53fn all_files_in_dir<T>(p: T, conf: &Config) -> Result<Files>
55where
56 T: AsRef<Path>,
57{
58 let mut result = vec![];
59 let (mut files, dirs) = files_and_dirs_in_path(p, &conf)?;
60 result.append(&mut files);
61
62 if dirs.len() != 0 {
63 result.append(
64 &mut dirs
65 .iter()
66 .map(|d| all_files_in_dir(d, conf).unwrap())
67 .flatten()
68 .collect::<Files>(),
69 )
70 }
71
72 Ok(result)
73}
74
75fn files_and_dirs_in_path(p: impl AsRef<Path>, conf: &Config) -> Result<(Files, Dirs)> {
77 let (mut f, mut d): (Files, Dirs) = (vec![], vec![]);
78
79 let filetypes = &conf.filetypes;
81 let filetypes_count = filetypes.len();
82
83 let ignore_dirs = &conf.ignore_dirs;
85 let ignore_dirs_count = ignore_dirs.len();
86
87 for entry in read_dir(p)? {
88 let dir = entry?;
89 let path = dir.path();
90
91 if path.is_dir() {
92 if ignore_dirs_count != 0 {
94 if let Some(d_name) = path.file_name() {
95 if !ignore_dirs.contains(&d_name.to_os_string()) {
96 d.push(path)
97 }
98 }
99 } else {
100 d.push(path)
101 }
102 } else {
103 file_checker(&mut f, &path, &filetypes, filetypes_count)
104 }
105 }
106 Ok((f, d))
107}
108
109fn file_checker(files: &mut Files, path: &Path, filetypes: &[OsString], filetypes_count: usize) {
111 if filetypes_count != 0 {
113 if let Some(t) = path.extension() {
115 if filetypes.contains(&t.to_os_string()) {
117 let aa = REGEX_TABLE.lock();
119 if let Some(re) = aa.as_ref().unwrap().get(t.to_str().unwrap()) {
120 let re = unsafe {
122 match (re as *const Regex).clone().as_ref() {
123 Some(a) => a,
124 None => return,
125 }
126 };
127 files.push(File(path.to_path_buf(), re))
128 }
129 }
130 }
131 } else {
132 if let Some(t) = path.extension() {
133 let aa = REGEX_TABLE.lock();
135 if let Some(re) = aa.as_ref().unwrap().get(t.to_str().unwrap()) {
136 let re = unsafe {
138 match (re as *const Regex).clone().as_ref() {
139 Some(a) => a,
140 None => return,
141 }
142 };
143 files.push(File(path.to_path_buf(), re))
144 }
145 }
146 }
147}
148
149fn filter_line(line: &str, line_num: usize, re: &Regex) -> Option<Crumb> {
151 match re.find(line) {
152 Some(mat) => {
153 let position = mat.start();
154 let cap = re.captures(line).unwrap();
155 let content = cap[2].to_string();
156 let comment_symbol_header = cap[1].to_string();
157 if content.starts_with('!') {
158 Some(
159 Crumb::new(line_num, position, content, comment_symbol_header)
160 .add_ignore_flag(),
161 )
162 } else {
163 Some(Crumb::new(
164 line_num,
165 position,
166 content,
167 comment_symbol_header,
168 ))
169 }
170 }
171 None => None,
172 }
173}
174
175fn op_file(file: File, kwreg: &Option<Regex>, conf: Arc<RwLock<Config>>) -> Result<Option<Bread>> {
177 let breads = match bake_bread(&file, kwreg, &conf.read().unwrap()) {
178 Ok(b) => b,
179 Err(e) => {
180 debug!("file {} had error {}", file.to_string(), e.to_string());
181 return Ok(None);
182 }
183 };
184
185 if !conf.read().unwrap().delete {
186 Ok(breads)
187 } else {
188 match breads {
189 Some(bb) => {
190 delete_the_crumbs(bb)?;
191 Ok(None)
192 }
193 None => Ok(None),
194 }
195 }
196}
197
198fn bake_bread(file: &File, kwreg: &Option<Regex>, conf: &Config) -> Result<Option<Bread>> {
200 let mut buf = vec![];
202 let file_p = file.to_string();
203 let mut f: std::fs::File = std::fs::File::open(file.0.clone())?;
204 f.read_to_end(&mut buf)?;
205
206 let mut line_num = 0;
207 let mut ss = String::new(); let mut buf = buf.as_slice();
209 let mut result = vec![];
210 let mut head: Option<Crumb> = None; let mut keyword_checker_and_push = |mut cb: Crumb| {
214 if kwreg.is_some() {
215 if cb.filter_keywords(kwreg.as_ref().unwrap()) {
217 result.push(cb)
218 }
219 } else {
220 if !cb.is_ignore() || conf.show_ignored {
221 result.push(cb)
222 }
223 }
224 };
225
226 loop {
227 line_num += 1;
228 match buf.read_line(&mut ss) {
229 Ok(0) | Err(_) => {
230 if head.is_some() {
231 keyword_checker_and_push(head.unwrap());
232 }
233 break; }
235 Ok(_) => match filter_line(&ss, line_num, file.1) {
236 Some(cb) => {
237 match head {
239 Some(ref mut h) => {
240 if h.has_tail() {
241 h.add_tail(cb);
243 ss.clear(); continue;
245 } else {
246 keyword_checker_and_push(head.unwrap());
248 head = None;
249 }
250 }
251 None => (),
252 }
253
254 if cb.has_tail() {
255 head = Some(cb);
257 } else {
258 keyword_checker_and_push(cb)
260 }
261 }
262 None => {
263 if head.is_some() {
264 keyword_checker_and_push(head.unwrap());
265 head = None;
266 }
267 }
268 },
269 }
270 ss.clear()
271 }
272
273 if result.len() == 0 {
274 Ok(None)
275 } else {
276 Ok(Some(Bread::new(file_p, result)))
277 }
278}
279
280pub fn delete_the_crumbs(Bread { file_path, crumbs }: Bread) -> Result<String> {
282 let all_delete_line_postion_pairs = crumbs
283 .iter()
284 .map(|crumb| crumb.all_lines_num_postion_pair())
285 .flatten();
286
287 delete_lines_on(&file_path, all_delete_line_postion_pairs)?;
288
289 println!("deleted the crumbs in {}", file_path);
290 Ok(file_path)
291}
292
293pub fn delete_the_crumbs_on_special_index(
295 Bread { file_path, crumbs }: Bread,
296 indexes: HashSet<usize>,
297) -> Result<String> {
298 let mut all_delete_lines = vec![];
299 for ind in &indexes {
300 match crumbs.get(*ind) {
301 Some(c) => all_delete_lines.append(&mut c.all_lines_num_postion_pair()),
302 None => return Err(io::Error::other("cannot find crumb index in bread")),
303 }
304 }
305
306 delete_lines_on(&file_path, all_delete_lines.into_iter())?;
307
308 println!("deleted {} crumbs in {}", indexes.len(), file_path);
309
310 Ok(file_path)
311}
312
313fn delete_lines_on(
315 file_path: &str,
316 line_num_pos_pairs: impl Iterator<Item = (usize, usize)>,
317) -> Result<()> {
318 let f = fs::File::open(&file_path)?;
319 let reader = BufReader::new(f).lines();
320
321 let all_delete_lines = line_num_pos_pairs.collect();
322
323 let finish_deleted = delete_nth_lines(reader, all_delete_lines)?
324 .into_iter()
325 .map(|line| line.into_bytes());
326
327 let mut new_file = OpenOptions::new()
328 .write(true)
329 .truncate(true)
330 .open(file_path)?;
331
332 for line in finish_deleted {
333 new_file.write_all(&line)?;
334 new_file.write_all(b"\n")?
335 }
336 Ok(())
337}
338
339fn delete_nth_lines(
341 f: impl Iterator<Item = Result<String>>,
342 nm: HashMap<usize, usize>,
343) -> Result<Vec<String>> {
344 let mut result = vec![];
345
346 for (line_num, ll) in f.enumerate() {
347 if nm.contains_key(&(line_num + 1)) {
348 let mut new_l = ll?;
349 new_l.truncate(*nm.get(&(line_num + 1)).unwrap());
350 if new_l == "" {
351 continue;
353 }
354 result.push(new_l);
355 } else {
356 result.push(ll?);
357 }
358 }
359
360 Ok(result)
361}
362
363pub fn restore_the_crumb(Bread { file_path, crumbs }: Bread) -> Result<String> {
365 let all_restore_lines = crumbs
366 .iter()
367 .map(|c| c.all_lines_num_postion_and_header_content())
368 .flatten();
369
370 restore_lines_on(&file_path, all_restore_lines)?;
371
372 println!("restored the crumbs in {}", file_path);
373 Ok(file_path)
374}
375
376pub fn restore_the_crumb_on_special_index(
378 Bread { file_path, crumbs }: Bread,
379 indexes: HashSet<usize>,
380) -> Result<String> {
381 let mut all_restore_lines = Vec::with_capacity(indexes.len());
382 for ind in &indexes {
383 match crumbs.get(*ind) {
384 Some(c) => all_restore_lines.append(&mut c.all_lines_num_postion_and_header_content()),
385 None => return Err(io::Error::other("cannot find crumb index in bread")),
386 }
387 }
388
389 restore_lines_on(&file_path, all_restore_lines.into_iter())?;
390
391 println!("restored {} crumbs in {}", indexes.len(), file_path);
392 Ok(file_path)
393}
394
395fn restore_lines_on<'a>(
396 file_path: &'a str,
397 all_restore_lines: impl Iterator<Item = (usize, usize, &'a str, &'a str)>,
398) -> Result<()> {
399 let f = fs::File::open(&file_path)?;
400 let reader = BufReader::new(f).lines();
401
402 let mut table: HashMap<usize, (usize, &str, &str)> =
403 HashMap::with_capacity(all_restore_lines.size_hint().1.unwrap_or(0));
404
405 all_restore_lines.for_each(|(line_num, pos, header, content)| {
406 table.insert(line_num, (pos, header, content));
407 });
408
409 let mut new_file = Vec::with_capacity(reader.size_hint().1.unwrap_or(0));
410 for (line_num, ll) in reader.enumerate() {
411 if let Some((pos, header, content)) = table.get(&(line_num + 1)) {
412 let mut new_l = ll?;
413 new_l.truncate(*pos);
414 new_l.push_str(*header);
415 new_l.push_str(" ");
416 new_l.push_str(*content);
417
418 new_file.push(new_l.into_bytes())
419 } else {
420 new_file.push(ll?.into_bytes());
421 }
422 }
423
424 let mut file = OpenOptions::new()
425 .write(true)
426 .truncate(true)
427 .open(file_path)?;
428
429 for line in new_file {
430 file.write_all(&line)?;
431 file.write_all(b"\n")?
432 }
433
434 Ok(())
435}
436
437pub fn run_format_command_to_file(
439 fmt_command: &str,
440 _files: impl IntoIterator<Item = String>,
441) -> std::result::Result<(), String> {
442 let mut command_splits = fmt_command.split(' ');
443 let first = command_splits
444 .next()
445 .ok_or("fmt_command cannot be empty".to_string())?;
446
447 let mut comm = Command::new(first);
448 let mut child = comm
449 .args(command_splits)
450 .spawn()
451 .expect("Cannot run the fmt_command");
452
453 println!("running fmt command: {}", fmt_command);
454 child
455 .wait()
456 .expect("fmt command wasn't running")
457 .exit_ok()
458 .map_err(|e| e.to_string())
459}
460
461pub fn handle_files(conf: Config) -> impl Iterator<Item = Bread> {
463 let mut all_files: Vec<File> = files_in_dir_or_file_vec(&conf.files, &conf).unwrap();
465
466 let threads_num: usize = thread::available_parallelism()
468 .unwrap_or(THREAD_NUM.unwrap())
469 .into();
470
471 let len = all_files.len();
472 let count = len / threads_num;
473 let mut groups: Vec<Vec<File>> = vec![];
474 for _ in 0..threads_num - 1 {
475 groups.push(all_files.drain(0..count).collect())
476 }
477 groups.push(all_files.drain(0..).collect());
478
479 let conf = Arc::new(RwLock::new(conf));
480 groups
481 .into_iter()
482 .map(move |fs| {
483 let kwreg = KEYWORDS_REGEX.lock().unwrap().clone();
484 let conf_c = Arc::clone(&conf);
485 thread::spawn(|| {
486 fs.into_iter()
487 .filter_map(move |f| op_file(f, &kwreg, conf_c.clone()).unwrap())
488 .collect::<Vec<Bread>>()
489 })
490 })
491 .map(|han| han.join().unwrap())
492 .flatten()
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_files_and_dirs_in_path() -> Result<()> {
501 let (fs, dirs) = files_and_dirs_in_path("./tests/testcases", &Default::default())?;
502
503 assert_eq!(dirs.len(), 0);
504 assert_eq!(fs[0].0, PathBuf::from("./tests/testcases/multilines.rs"),);
505 Ok(())
506 }
507
508 }