1use std::fs;
2use std::path::Path;
3use regex::Regex;
4
5
6use serde::{Serialize, Deserialize};
7
8#[derive(Serialize, Deserialize)]
9pub struct Finder {
10 pub ignore_dirs: Vec<String>,
11 pub ignore_files: Vec<String>,
12 pub match_files: Vec<String>,
13 pub max_depth: Option<usize>,
14
15 #[serde(default)]
16 pub filter_path: Vec<String>,
17}
18
19struct FinderReg {
20 ignore_dirs: Vec<Regex>,
21 ignore_files: Vec<Regex>,
22 match_files: Vec<Regex>,
23 max_depth: usize,
24 filter_path: Vec<Regex>,
25}
26
27fn check_dir(path:&Path, patterns: &Vec<Regex>, default:bool) -> bool {
28 if patterns.len() == 0 {
29 return default;
30 }
31 if let Some(path) = path.to_str() {
32 return patterns.iter().any(|reg| {reg.is_match(path)});
33 }
34
35 return false;
36}
37
38fn check_file(path:&Path, patterns: &Vec<Regex>, default:bool) -> bool {
39 if patterns.len() == 0 {
40 return default;
41 }
42 if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
43 return patterns.iter().any(|reg| {reg.is_match(filename)});
44 }
45
46 return false;
47}
48
49
50impl Finder {
51 pub fn find <P: AsRef<Path>>(&self, path: P, cb: &mut impl FnMut(&Path)) {
52 let _conf = FinderReg {
53 ignore_files :
54 self.ignore_files
55 .iter()
56 .filter_map(|p| Regex::new(&p).ok()).collect(),
57
58 match_files :
59 self.match_files
60 .iter()
61 .filter_map(|p| Regex::new(&p).ok()).collect(),
62 ignore_dirs :
63 self.ignore_dirs.iter()
64 .filter_map(|p| Regex::new(&p).ok()).collect(),
65 max_depth:self.max_depth.unwrap_or(100),
66 filter_path:
67 self.filter_path
68 .iter()
69 .filter_map(|p| Regex::new(&p).ok()).collect()
70 };
71
72 self._find(path, cb, &_conf, 0);
73
74 }
75
76 fn _find<P: AsRef<Path>>(&self,
77 path: P,
78 cb: &mut impl FnMut(&Path),
79 conf:&FinderReg,
80 depth:usize) {
81 if depth > conf.max_depth {
82 return
83 }
84 if let Ok(entries) = fs::read_dir(path) {
85 for entry in entries.flatten() {
86 let path = entry.path();
87
88
89 if path.is_dir() {
90 if !check_dir(&path, &conf.ignore_dirs, false) {
91 self._find(&path, cb, conf, depth + 1);
92 }
93 } else if path.is_file() {
94 if !check_dir(&path, &conf.filter_path, true) {
95 continue;
96 }
97
98 if !check_file(&path, &conf.ignore_files, false)
99 && check_file(&path, &conf.match_files, true) {
100 cb(&path);
101 }
102 }
103 }
104 }
105 }
106}
107
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use std::path::Path;
113
114 #[test]
115 fn test_check_file_match() {
116
117 let regex:Vec<Regex> = vec![Regex::new("error").unwrap(),
118 Regex::new("\\.rs$").unwrap()];
119 let empty: Vec<Regex>= vec![];
120 {
121 let path0 = Path::new("foo/error.log");
122 assert!(check_file(path0, &empty, true));
123
124 let path1 = Path::new("coin");
125 assert!(check_file(path1, &empty, true));
126 }{
127 let path = Path::new("foo/error.log");
128 assert!(check_file(path, ®ex, true));
129 }{
130 let path = Path::new("error.py");
131 assert!(check_file(path, ®ex, true));
132 }{
133 let path = Path::new("coin.rs");
134 assert!(check_file(path, ®ex, true));
135 }{
136 let path = Path::new("rscorsin.py");
137 assert!(!check_file(path, ®ex, true));
138 }
139 }
140
141 #[test]
142 fn test_check_dir_match() {
143 let regex = vec![Regex::new("error").unwrap(),
144 Regex::new("\\.rs$").unwrap()];
145 let empty: Vec<Regex> = vec![];
146 {
147 let path = Path::new("/home/user/node_modules/awesome");
148 assert!(check_dir(path, &empty, true));
149 }{
150 let path = Path::new("/home/user/node_modules/awesome");
151 assert!(!check_dir(path, ®ex, true));
152 }{
153 let path = Path::new("/home/user/.git/awesome");
154 assert!(!check_dir(path, ®ex, true));
155 }{
156 let path = Path::new("/home/user_modules/awesome");
157 assert!(!check_dir(path, ®ex, true));
158 }{
159 let path = Path::new("/home/user/git/awesome");
160 assert!(!check_dir(path, ®ex, true));
161 }
162 }
163}