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, OpenOptions, read_dir};
8use std::io::{self, BufReader, prelude::*};
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 match aa.as_ref().unwrap().get(t.to_str().unwrap()) {
120 Some(re) => {
121 let re = unsafe {
123 match (re as *const Regex).clone().as_ref() {
124 Some(a) => a,
125 None => return,
126 }
127 };
128 files.push(File(path.to_path_buf(), re))
129 }
130 _ => {}
131 }
132 }
133 }
134 } else {
135 if let Some(t) = path.extension() {
136 let aa = REGEX_TABLE.lock();
138 match aa.as_ref().unwrap().get(t.to_str().unwrap()) {
139 Some(re) => {
140 let re = unsafe {
142 match (re as *const Regex).clone().as_ref() {
143 Some(a) => a,
144 None => return,
145 }
146 };
147 files.push(File(path.to_path_buf(), re))
148 }
149 _ => {}
150 }
151 }
152 }
153}
154
155fn filter_line(line: &str, line_num: usize, re: &Regex) -> Option<Crumb> {
157 match re.find(line) {
158 Some(mat) => {
159 let position = mat.start();
160 let cap = re.captures(line).unwrap();
161 let content = cap[2].to_string();
162 let comment_symbol_header = cap[1].to_string();
163 if content.starts_with('!') {
164 Some(
165 Crumb::new(line_num, position, content, comment_symbol_header)
166 .add_ignore_flag(),
167 )
168 } else {
169 Some(Crumb::new(
170 line_num,
171 position,
172 content,
173 comment_symbol_header,
174 ))
175 }
176 }
177 None => None,
178 }
179}
180
181fn op_file(file: File, kwreg: &Option<Regex>, conf: Arc<RwLock<Config>>) -> Result<Option<Bread>> {
183 let breads = match bake_bread(&file, kwreg, &conf.read().unwrap()) {
184 Ok(b) => b,
185 Err(e) => {
186 debug!("file {} had error {}", file.to_string(), e.to_string());
187 return Ok(None);
188 }
189 };
190
191 if !conf.read().unwrap().delete {
192 Ok(breads)
193 } else {
194 match breads {
195 Some(bb) => {
196 delete_the_crumbs(bb)?;
197 Ok(None)
198 }
199 None => Ok(None),
200 }
201 }
202}
203
204fn bake_bread(file: &File, kwreg: &Option<Regex>, conf: &Config) -> Result<Option<Bread>> {
206 let mut buf = vec![];
208 let file_p = file.to_string();
209 let mut f: std::fs::File = std::fs::File::open(file.0.clone())?;
210 f.read_to_end(&mut buf)?;
211
212 let mut line_num = 0;
213 let mut ss = String::new(); let mut buf = buf.as_slice();
215 let mut result = vec![];
216 let mut head: Option<Crumb> = None; let mut shadow_file = vec![]; let mut keyword_checker_and_push = |mut cb: Crumb| {
221 if kwreg.is_some() {
222 if cb.filter_keywords(kwreg.as_ref().unwrap()) {
224 result.push(cb)
225 }
226 } else {
227 if !cb.is_ignore() || conf.show_ignored {
228 result.push(cb)
229 }
230 }
231 };
232
233 loop {
234 line_num += 1;
235 match buf.read_line(&mut ss) {
236 Ok(0) | Err(_) => {
237 if head.is_some() {
238 keyword_checker_and_push(head.unwrap());
239 }
240 break; }
242 Ok(_) => match filter_line(&ss, line_num, file.1) {
243 Some(cb) => {
244 match head {
246 Some(ref mut h) => {
247 if h.has_tail() {
248 h.add_tail(cb);
250 ss.clear(); continue;
252 } else {
253 keyword_checker_and_push(head.unwrap());
255 head = None;
256 }
257 }
258 None => (),
259 }
260
261 if cb.has_tail() {
262 head = Some(cb);
264 } else {
265 keyword_checker_and_push(cb)
267 }
268 }
269 None => {
270 if head.is_some() {
271 keyword_checker_and_push(head.unwrap());
272 head = None;
273 }
274 }
275 },
276 }
277
278 if conf.range > 0 {
279 shadow_file.push(ss.clone());
280 }
281
282 ss.clear()
283 }
284
285 if conf.range > 0 {
287 result.iter_mut().for_each(|crumb| {
288 let ahead_ind = (crumb.line_num - 1).saturating_sub(conf.range as usize);
289 let tail_ind = (crumb.line_num - 1)
290 .saturating_add(conf.range as usize)
291 .min(shadow_file.len());
292 crumb.range_content = Some(
293 (ahead_ind + 1..tail_ind + 1)
294 .zip(
295 shadow_file
296 .get(ahead_ind..tail_ind)
297 .map(|x| x.to_vec())
298 .unwrap(),
299 )
300 .collect(),
301 );
302 });
303 }
304
305 if result.len() == 0 {
306 Ok(None)
307 } else {
308 Ok(Some(Bread::new(file_p, result)))
309 }
310}
311
312pub fn delete_the_crumbs(Bread { file_path, crumbs }: Bread) -> Result<String> {
314 let all_delete_line_postion_pairs = crumbs
315 .iter()
316 .map(|crumb| crumb.all_lines_num_postion_pair())
317 .flatten();
318
319 delete_lines_on(&file_path, all_delete_line_postion_pairs)?;
320
321 println!("deleted the crumbs in {}", file_path);
322 Ok(file_path)
323}
324
325pub fn delete_the_crumbs_on_special_index(
327 Bread { file_path, crumbs }: Bread,
328 indexes: HashSet<usize>,
329) -> Result<String> {
330 let mut all_delete_lines = vec![];
331 for ind in &indexes {
332 match crumbs.get(*ind) {
333 Some(c) => all_delete_lines.append(&mut c.all_lines_num_postion_pair()),
334 None => return Err(io::Error::other("cannot find crumb index in bread")),
335 }
336 }
337
338 delete_lines_on(&file_path, all_delete_lines.into_iter())?;
339
340 println!("deleted {} crumbs in {}", indexes.len(), file_path);
341
342 Ok(file_path)
343}
344
345fn delete_lines_on(
347 file_path: &str,
348 line_num_pos_pairs: impl Iterator<Item = (usize, usize)>,
349) -> Result<()> {
350 let f = fs::File::open(&file_path)?;
351 let reader = BufReader::new(f).lines();
352
353 let all_delete_lines = line_num_pos_pairs.collect();
354
355 let finish_deleted = delete_nth_lines(reader, all_delete_lines)?
356 .into_iter()
357 .map(|line| line.into_bytes());
358
359 let mut new_file = OpenOptions::new()
360 .write(true)
361 .truncate(true)
362 .open(file_path)?;
363
364 for line in finish_deleted {
365 new_file.write_all(&line)?;
366 new_file.write_all(b"\n")?
367 }
368 Ok(())
369}
370
371fn delete_nth_lines(
373 f: impl Iterator<Item = Result<String>>,
374 nm: HashMap<usize, usize>,
375) -> Result<Vec<String>> {
376 let mut result = vec![];
377
378 for (line_num, ll) in f.enumerate() {
379 if nm.contains_key(&(line_num + 1)) {
380 let mut new_l = ll?;
381 new_l.truncate(*nm.get(&(line_num + 1)).unwrap());
382 if new_l == "" {
383 continue;
385 }
386 result.push(new_l);
387 } else {
388 result.push(ll?);
389 }
390 }
391
392 Ok(result)
393}
394
395pub fn restore_the_crumb(Bread { file_path, crumbs }: Bread) -> Result<String> {
397 let all_restore_lines = crumbs
398 .iter()
399 .map(|c| c.all_lines_num_postion_and_header_content())
400 .flatten();
401
402 restore_lines_on(&file_path, all_restore_lines)?;
403
404 println!("restored the crumbs in {}", file_path);
405 Ok(file_path)
406}
407
408pub fn restore_the_crumb_on_special_index(
410 Bread { file_path, crumbs }: Bread,
411 indexes: HashSet<usize>,
412) -> Result<String> {
413 let mut all_restore_lines = Vec::with_capacity(indexes.len());
414 for ind in &indexes {
415 match crumbs.get(*ind) {
416 Some(c) => all_restore_lines.append(&mut c.all_lines_num_postion_and_header_content()),
417 None => return Err(io::Error::other("cannot find crumb index in bread")),
418 }
419 }
420
421 restore_lines_on(&file_path, all_restore_lines.into_iter())?;
422
423 println!("restored {} crumbs in {}", indexes.len(), file_path);
424 Ok(file_path)
425}
426
427fn restore_lines_on<'a>(
428 file_path: &'a str,
429 all_restore_lines: impl Iterator<Item = (usize, usize, &'a str, &'a str)>,
430) -> Result<()> {
431 let f = fs::File::open(&file_path)?;
432 let reader = BufReader::new(f).lines();
433
434 let mut table: HashMap<usize, (usize, &str, &str)> =
435 HashMap::with_capacity(all_restore_lines.size_hint().1.unwrap_or(0));
436
437 all_restore_lines.for_each(|(line_num, pos, header, content)| {
438 table.insert(line_num, (pos, header, content));
439 });
440
441 let mut new_file = Vec::with_capacity(reader.size_hint().1.unwrap_or(0));
442 for (line_num, ll) in reader.enumerate() {
443 if let Some((pos, header, content)) = table.get(&(line_num + 1)) {
444 let mut new_l = ll?;
445 new_l.truncate(*pos);
446 new_l.push_str(*header);
447 new_l.push_str(" ");
448 new_l.push_str(*content);
449
450 new_file.push(new_l.into_bytes())
451 } else {
452 new_file.push(ll?.into_bytes());
453 }
454 }
455
456 let mut file = OpenOptions::new()
457 .write(true)
458 .truncate(true)
459 .open(file_path)?;
460
461 for line in new_file {
462 file.write_all(&line)?;
463 file.write_all(b"\n")?
464 }
465
466 Ok(())
467}
468
469pub fn run_format_command_to_file(
471 fmt_command: &str,
472 _files: impl IntoIterator<Item = String>,
473) -> std::result::Result<(), String> {
474 let mut command_splits = fmt_command.split(' ');
475 let first = command_splits
476 .next()
477 .ok_or("fmt_command cannot be empty".to_string())?;
478
479 let mut comm = Command::new(first);
480 let mut child = comm
481 .args(command_splits)
482 .spawn()
483 .expect("Cannot run the fmt_command");
484
485 println!("running fmt command: {}", fmt_command);
486 child
487 .wait()
488 .expect("fmt command wasn't running")
489 .exit_ok()
490 .map_err(|e| e.to_string())
491}
492
493pub fn handle_files(conf: Config) -> impl Iterator<Item = Bread> {
495 let mut all_files: Vec<File> = files_in_dir_or_file_vec(&conf.files, &conf).unwrap();
497
498 let threads_num: usize = thread::available_parallelism()
500 .unwrap_or(THREAD_NUM.unwrap())
501 .into();
502
503 let len = all_files.len();
504 let count = len / threads_num;
505 let mut groups: Vec<Vec<File>> = vec![];
506 for _ in 0..threads_num - 1 {
507 groups.push(all_files.drain(0..count).collect())
508 }
509 groups.push(all_files.drain(0..).collect());
510
511 let conf = Arc::new(RwLock::new(conf));
512 groups
513 .into_iter()
514 .map(move |fs| {
515 let kwreg = KEYWORDS_REGEX.lock().unwrap().clone();
516 let conf_c = Arc::clone(&conf);
517 thread::spawn(|| {
518 fs.into_iter()
519 .filter_map(move |f| op_file(f, &kwreg, conf_c.clone()).unwrap())
520 .collect::<Vec<Bread>>()
521 })
522 })
523 .map(|han| han.join().unwrap())
524 .flatten()
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_files_and_dirs_in_path() -> Result<()> {
533 let (fs, dirs) = files_and_dirs_in_path("./tests/testcases", &Default::default())?;
534
535 assert_eq!(dirs.len(), 0);
536 assert_eq!(fs[0].0, PathBuf::from("./tests/testcases/multilines.rs"),);
537 Ok(())
538 }
539
540 }