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