Skip to main content

code_it_later_rs/
config.rs

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
11/// Inner dictionary
12const 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
41/// Inner dictionary for multi-line comment syntax
42const 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
70/// Update static table with new raw_json str
71fn 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
88/// Making regex string
89fn 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
102/// Making regex tuple (start_regex, end_regex) for multi-line comments
103fn 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
126/// making the keyword regex, case insensitive
127pub(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/// config when running
164#[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    /// if delete
171    pub(super) delete: bool,
172
173    /// if restore
174    pub(super) restore: bool,
175
176    /// assume yes to prompts
177    pub(super) yes: bool,
178
179    /// output format
180    pub(super) output: OutputFormat,
181
182    /// show ignored
183    pub(super) show_ignored: bool,
184
185    /// show the range of the content around the crumb
186    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            // delete and restore cannot be true at the same time
219            // and delete has higher priority
220            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 here
250        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        // more test
263        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        // This means file content equal the pure str
291        // which already test before in test_update_table
292        assert_eq!(
293            serde_json::from_str::<HashMap<String, Vec<String>>>(ss).unwrap(),
294            serde_json::from_str(r#"{"rs":["//","/\\*"]}"#).unwrap() // json reader need one more '/'
295        );
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        // more tests
331        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}