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
41pub static FALLBACK_REGEX: LazyLock<Regex> = LazyLock::new(|| {
42 Regex::new(r#"(//+|#+|;+|--+):=\s+(.*)"#).unwrap()
43});
44
45pub static KEYWORDS_REGEX: LazyLock<Mutex<Option<Regex>>> = LazyLock::new(|| Mutex::new(None));
46
47fn update_table(raw_json: &str) {
49 let new_table: HashMap<String, Vec<String>> = serde_json::from_str(raw_json).unwrap();
50
51 let mut table = TABLE.lock().unwrap();
52 for (k, v) in new_table.iter() {
53 table.insert(k.clone(), v.clone());
54 }
55
56 let mut re_table = REGEX_TABLE.lock().unwrap();
57 table
58 .iter()
59 .map(|(k, v)| (k.clone(), Regex::new(&make_regex(v)).unwrap()))
60 .for_each(|(k, v)| {
61 let _ = re_table.insert(k, v);
62 });
63}
64
65fn make_regex(com_syms: &Vec<String>) -> String {
67 let mut head = String::new();
68 for s in com_syms {
69 head.push('|');
70 head.push_str(s);
71 head.push_str("+");
72 }
73
74 let _ = head.drain(..1).collect::<String>();
75
76 format!("({}):=\\s+(.*)", head)
77}
78
79pub(super) fn make_key_regex(keywords: &Vec<String>) {
81 let mut ss = String::new();
82 for s in keywords {
83 ss.push_str(&s);
84 ss.push('|');
85 }
86
87 let _ = ss.drain(ss.len() - 1..).collect::<String>();
88 let mut kk = KEYWORDS_REGEX.lock().unwrap();
89 *kk = Some(
90 RegexBuilder::new(&format!("({}):\\s*(.*)", ss))
91 .case_insensitive(true)
92 .build()
93 .unwrap(),
94 );
95}
96
97pub fn clean_keywords_table() {
98 let mut kk = KEYWORDS_REGEX.lock().unwrap();
99 *kk = None;
100}
101
102#[derive(Clone, Debug)]
103pub(super) enum OutputFormat {
104 None,
105 Json,
106 List,
107 Range,
108}
109
110impl Default for OutputFormat {
111 fn default() -> Self {
112 Self::None
113 }
114}
115
116#[derive(Default, Debug, Clone)]
118pub struct Config {
119 pub(super) filetypes: Vec<OsString>,
120 pub(super) ignore_dirs: Vec<OsString>,
121 pub(super) files: Vec<String>,
122
123 pub(super) delete: bool,
125
126 pub(super) restore: bool,
128
129 pub(super) yes: bool,
131
132 pub(super) output: OutputFormat,
134
135 pub(super) show_ignored: bool,
137
138 pub(super) range: u32,
140}
141
142impl From<&Args> for Config {
143 fn from(a: &Args) -> Self {
144 match &a.jsonx {
145 Some(j) => {
146 let mut buf = vec![];
147 File::open(j).unwrap().read_to_end(&mut buf).unwrap();
148 update_table(&String::from_utf8(buf).unwrap());
149 }
150 None => (),
151 }
152
153 match &a.keywords {
154 Some(kk) => make_key_regex(&kk),
155 None => (),
156 }
157
158 let output = match &a.output_format {
159 Some(v) if v.to_lowercase().as_str() == "json" => OutputFormat::Json,
160 Some(v) if v.to_lowercase().as_str() == "list" => OutputFormat::List,
161 None if a.range > 0 => OutputFormat::Range,
162 _ => OutputFormat::None,
163 };
164
165 Self {
166 filetypes: a.filetypes.clone(),
167 ignore_dirs: a.ignore_dirs.clone(),
168 files: a.targets.clone(),
169
170 delete: a.delete,
171 restore: if a.delete { false } else { a.restore },
174 yes: a.yes,
175
176 output,
177 show_ignored: a.show_ignore,
178
179 range: a.range,
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use regex::Regex;
188
189 #[test]
190 fn test_update_table() {
191 assert_eq!(
192 TABLE.lock().unwrap().get("rs").unwrap(),
193 &vec![String::from("//"), String::from(r#"/\*"#)]
194 );
195
196 assert_eq!(
197 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
198 &String::from(r#"(//+|/\*+):=\s+(.*)"#)
199 );
200
201 update_table(r##"{"rs":["//","#"]}"##);
203
204 assert_eq!(
205 TABLE.lock().unwrap().get("rs").unwrap(),
206 &vec![String::from("//"), String::from("#")]
207 );
208
209 assert_eq!(
210 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
211 &String::from(r#"(//+|#+):=\s+(.*)"#)
212 );
213
214 update_table(r#"{"rs":["//","/\\*"]}"#);
216
217 assert_eq!(
218 TABLE.lock().unwrap().get("rs").unwrap(),
219 &vec![String::from("//"), String::from("/\\*")]
220 );
221
222 assert_eq!(
223 TABLE.lock().unwrap().get("rs").unwrap(),
224 &vec![String::from("//"), String::from(r#"/\*"#)]
225 );
226
227 assert_eq!(
228 REGEX_TABLE.lock().unwrap().get("rs").unwrap().as_str(),
229 &String::from(r#"(//+|/\*+):=\s+(.*)"#)
230 );
231 }
232
233 #[test]
234 fn test_update_table_with_json() {
235 let mut buf = vec![];
236 File::open("./tests/testcases/test.json")
237 .unwrap()
238 .read_to_end(&mut buf)
239 .unwrap();
240 let ss: &str = &String::from_utf8(buf).unwrap();
241
242 assert_eq!(
245 serde_json::from_str::<HashMap<String, Vec<String>>>(ss).unwrap(),
246 serde_json::from_str(r#"{"rs":["//","/\\*"]}"#).unwrap() );
248 }
249
250 #[test]
251 fn test_make_regex() {
252 assert_eq!(
253 make_regex(&vec![String::from("//"), String::from(";")]),
254 String::from(r#"(//+|;+):=\s+(.*)"#)
255 );
256
257 assert_eq!(
258 make_regex(&vec![String::from("//"), String::from(r#"/\*"#)]),
259 String::from(r#"(//+|/\*+):=\s+(.*)"#)
260 );
261 }
262
263 #[test]
264 fn test_regex() {
265 let re = Regex::new(&make_regex(&vec![String::from("--"), String::from(";")])).unwrap();
266 let cap = re.captures("Aabbcc --:= test").unwrap();
267 assert_eq!(&cap[2], "test");
268
269 let cap = re.captures("Aabbcc ;:= test").unwrap();
270 assert_eq!(&cap[2], "test");
271
272 let cap = re.captures("Aabbcc ;;;:= test").unwrap();
273 assert_eq!(&cap[2], "test");
274 assert_eq!(&cap[1], ";;;");
275
276 assert!(re.captures("Aabbcc #:= test").is_none());
277
278 assert!(re.captures("Aabbcc ; test").is_none());
279
280 assert!(re.captures("Aabbcc ; := test").is_none());
281
282 let re = Regex::new(&make_regex(&vec![
284 String::from("//"),
285 String::from(r#"/\*"#),
286 String::from(r#"// "#),
287 ]))
288 .unwrap();
289 assert!(re.captures("err := test").is_none());
290 assert!(re.captures("err // := test").is_some());
291 assert_eq!(&re.captures("err // := test").unwrap()[1], "// ");
292 }
293
294 #[test]
295 fn test_restore_overwrited_by_delete() {
296 let mut arg: Args = Default::default();
297 arg.delete = true;
298 arg.restore = true;
299 let conf = Config::from(&arg);
300 assert!(conf.delete);
301 assert!(!conf.restore);
302
303 arg.delete = false;
304 arg.restore = true;
305 let conf = Config::from(&arg);
306 assert!(!conf.delete);
307 assert!(conf.restore);
308 }
309}