1use regex::{Regex, RegexBuilder};
2use std::collections::HashMap;
3use std::ffi::OsString;
4
5use std::fs::File;
6use std::io::Read;
7use std::sync::{LazyLock, Mutex};
8
9use super::args::Args;
10
11const DICT: &'static str = r#"
13{
14"rs":["//"],
15"go":["//", "// "],
16"lisp":[";"],
17"asd":[";"],
18"asdf":[";"],
19"py":["\\#"],
20"hs":["--", "-- "],
21"el":[";"],
22"clj":[";"],
23"js":["//"],
24"makefile":["\\#"],
25"dockerfile":["\\#"]
26}
27"#;
28
29static TABLE: LazyLock<Mutex<HashMap<String, Vec<String>>>> =
30 LazyLock::new(|| Mutex::new(serde_json::from_str(DICT).unwrap()));
31
32pub static REGEX_TABLE: LazyLock<Mutex<HashMap<String, Regex>>> = LazyLock::new(|| {
33 Mutex::new({
34 let a = TABLE.lock().unwrap();
35 a.iter()
36 .map(|(k, v)| (k.clone(), Regex::new(&make_regex(v)).unwrap()))
37 .collect()
38 })
39});
40
41const DICT_MUL_LINE: &'static str = r#"
43{
44"rs":[["/\\*", "\\*/"]],
45"go":[["/\\*", "\\*/"]],
46"js":[["/\\*", "\\*/"]],
47"hs":[["\\{-", "-\\}"]],
48"py":[["\"\"\"", "\"\"\""], ["'''", "'''"]]
49}
50"#;
51
52pub static TABLE_MUL: LazyLock<Mutex<HashMap<String, Vec<(String, String)>>>> =
53 LazyLock::new(|| Mutex::new(serde_json::from_str(DICT_MUL_LINE).unwrap()));
54
55pub static REGEX_TABLE_MUL: LazyLock<Mutex<HashMap<String, (Regex, Regex)>>> =
56 LazyLock::new(|| {
57 Mutex::new({
58 let a = TABLE_MUL.lock().unwrap();
59 a.iter()
60 .map(|(k, v)| (k.clone(), make_mul_regex(v)))
61 .collect()
62 })
63 });
64
65pub static FALLBACK_REGEX: LazyLock<Regex> =
66 LazyLock::new(|| Regex::new(r#"(//+|#+|;+|--+):=\s+(.*)"#).unwrap());
67
68pub static KEYWORDS_REGEX: LazyLock<Mutex<Option<Regex>>> = LazyLock::new(|| Mutex::new(None));
69
70fn update_table(raw_json: &str) {
72 let new_table: HashMap<String, Vec<String>> = serde_json::from_str(raw_json).unwrap();
73
74 let mut table = TABLE.lock().unwrap();
75 for (k, v) in new_table.iter() {
76 table.insert(k.clone(), v.clone());
77 }
78
79 let mut re_table = REGEX_TABLE.lock().unwrap();
80 table
81 .iter()
82 .map(|(k, v)| (k.clone(), Regex::new(&make_regex(v)).unwrap()))
83 .for_each(|(k, v)| {
84 let _ = re_table.insert(k, v);
85 });
86}
87
88fn make_regex(com_syms: &Vec<String>) -> String {
90 let mut head = String::new();
91 for s in com_syms {
92 head.push('|');
93 head.push_str(s);
94 head.push_str("+");
95 }
96
97 let _ = head.drain(..1).collect::<String>();
98
99 format!("({}):=\\s+(.*)", head)
100}
101
102fn make_mul_regex(com_syms: &Vec<(String, String)>) -> (Regex, Regex) {
104 let mut start_head = String::new();
105 let mut end_head = String::new();
106 for (start, end) in com_syms {
107 start_head.push('|');
108 start_head.push_str(start);
109 start_head.push_str("+");
110
111 end_head.push('|');
112 end_head.push_str(end);
113 end_head.push_str("+");
114 }
115
116 let _ = start_head.drain(..1).collect::<String>();
117 let _ = end_head.drain(..1).collect::<String>();
118
119 let start_re = Regex::new(&format!("({}):=\\s*(.*)", start_head)).unwrap();
120 let end_re = Regex::new(&format!("(.*)({})", end_head)).unwrap();
121
122 (start_re, end_re)
123}
124
125
126pub(super) fn make_key_regex(keywords: &Vec<String>) {
128 let mut ss = String::new();
129 for s in keywords {
130 ss.push_str(&s);
131 ss.push('|');
132 }
133
134 let _ = ss.drain(ss.len() - 1..).collect::<String>();
135 let mut kk = KEYWORDS_REGEX.lock().unwrap();
136 *kk = Some(
137 RegexBuilder::new(&format!("({}):\\s*(.*)", ss))
138 .case_insensitive(true)
139 .build()
140 .unwrap(),
141 );
142}
143
144pub fn clean_keywords_table() {
145 let mut kk = KEYWORDS_REGEX.lock().unwrap();
146 *kk = None;
147}
148
149#[derive(Clone, Debug)]
150pub(super) enum OutputFormat {
151 None,
152 Json,
153 List,
154 Range,
155}
156
157impl Default for OutputFormat {
158 fn default() -> Self {
159 Self::None
160 }
161}
162
163#[derive(Default, Debug, Clone)]
165pub struct Config {
166 pub(super) filetypes: Vec<OsString>,
167 pub(super) ignore_dirs: Vec<OsString>,
168 pub(super) files: Vec<String>,
169
170 pub(super) delete: bool,
172
173 pub(super) restore: bool,
175
176 pub(super) yes: bool,
178
179 pub(super) output: OutputFormat,
181
182 pub(super) show_ignored: bool,
184
185 pub(super) range: u32,
187}
188
189impl From<&Args> for Config {
190 fn from(a: &Args) -> Self {
191 match &a.jsonx {
192 Some(j) => {
193 let mut buf = vec![];
194 File::open(j).unwrap().read_to_end(&mut buf).unwrap();
195 update_table(&String::from_utf8(buf).unwrap());
196 }
197 None => (),
198 }
199
200 match &a.keywords {
201 Some(kk) => make_key_regex(&kk),
202 None => (),
203 }
204
205 let output = match &a.output_format {
206 Some(v) if v.to_lowercase().as_str() == "json" => OutputFormat::Json,
207 Some(v) if v.to_lowercase().as_str() == "list" => OutputFormat::List,
208 None if a.range > 0 => OutputFormat::Range,
209 _ => OutputFormat::None,
210 };
211
212 Self {
213 filetypes: a.filetypes.clone(),
214 ignore_dirs: a.ignore_dirs.clone(),
215 files: a.targets.clone(),
216
217 delete: a.delete,
218 restore: if a.delete { false } else { a.restore },
221 yes: a.yes,
222
223 output,
224 show_ignored: a.show_ignore,
225
226 range: a.range,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use regex::Regex;
235
236 #[test]
237 fn test_update_table() {
238 assert_eq!(
239 TABLE.lock().unwrap().get("rs").unwrap(),
240 &vec![String::from("//")]
241 );
242
243 assert_eq!(
244 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
245 &String::from(r#"(//+):=\s+(.*)"#)
246 );
247
248
249 update_table(r##"{"rs":["//","#"]}"##);
251
252 assert_eq!(
253 TABLE.lock().unwrap().get("rs").unwrap(),
254 &vec![String::from("//"), String::from("#")]
255 );
256
257 assert_eq!(
258 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
259 &String::from(r#"(//+|#+):=\s+(.*)"#)
260 );
261
262 update_table(r#"{"rs":["//","/\\*"]}"#);
264
265 assert_eq!(
266 TABLE.lock().unwrap().get("rs").unwrap(),
267 &vec![String::from("//"), String::from("/\\*")]
268 );
269
270 assert_eq!(
271 TABLE.lock().unwrap().get("rs").unwrap(),
272 &vec![String::from("//"), String::from(r#"/\*"#)]
273 );
274
275 assert_eq!(
276 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
277 &String::from(r#"(//+|/\*+):=\s+(.*)"#)
278 );
279 }
280
281 #[test]
282 fn test_update_table_with_json() {
283 let mut buf = vec![];
284 File::open("./tests/testcases/test.json")
285 .unwrap()
286 .read_to_end(&mut buf)
287 .unwrap();
288 let ss: &str = &String::from_utf8(buf).unwrap();
289
290 assert_eq!(
293 serde_json::from_str::<HashMap<String, Vec<String>>>(ss).unwrap(),
294 serde_json::from_str(r#"{"rs":["//","/\\*"]}"#).unwrap() );
296 }
297
298 #[test]
299 fn test_make_regex() {
300 assert_eq!(
301 make_regex(&vec![String::from("//"), String::from(";")]),
302 String::from(r#"(//+|;+):=\s+(.*)"#)
303 );
304
305 assert_eq!(
306 make_regex(&vec![String::from("//"), String::from(r#"/\*"#)]),
307 String::from(r#"(//+|/\*+):=\s+(.*)"#)
308 );
309 }
310
311 #[test]
312 fn test_regex() {
313 let re = Regex::new(&make_regex(&vec![String::from("--"), String::from(";")])).unwrap();
314 let cap = re.captures("Aabbcc --:= test").unwrap();
315 assert_eq!(&cap[2], "test");
316
317 let cap = re.captures("Aabbcc ;:= test").unwrap();
318 assert_eq!(&cap[2], "test");
319
320 let cap = re.captures("Aabbcc ;;;:= test").unwrap();
321 assert_eq!(&cap[2], "test");
322 assert_eq!(&cap[1], ";;;");
323
324 assert!(re.captures("Aabbcc #:= test").is_none());
325
326 assert!(re.captures("Aabbcc ; test").is_none());
327
328 assert!(re.captures("Aabbcc ; := test").is_none());
329
330 let re = Regex::new(&make_regex(&vec![
332 String::from("//"),
333 String::from(r#"/\*"#),
334 String::from(r#"// "#),
335 ]))
336 .unwrap();
337 assert!(re.captures("err := test").is_none());
338 assert!(re.captures("err // := test").is_some());
339 assert_eq!(&re.captures("err // := test").unwrap()[1], "// ");
340 }
341
342 #[test]
343 fn test_restore_overwrited_by_delete() {
344 let mut arg: Args = Default::default();
345 arg.delete = true;
346 arg.restore = true;
347 let conf = Config::from(&arg);
348 assert!(conf.delete);
349 assert!(!conf.restore);
350
351 arg.delete = false;
352 arg.restore = true;
353 let conf = Config::from(&arg);
354 assert!(!conf.delete);
355 assert!(conf.restore);
356 }
357}