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}
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
41/// Update static table with new raw_json str
42fn 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
59/// Making regex string
60fn 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
73/// making the keyword regex, case insensitive
74pub(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/// config when running
111#[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    /// if delete
118    pub(super) delete: bool,
119
120    /// if restore
121    pub(super) restore: bool,
122
123    /// output format
124    pub(super) output: OutputFormat,
125
126    /// show ignored
127    pub(super) show_ignored: bool,
128
129    /// show the range of the content around the crumb
130    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            // delete and restore cannot be true at the same time
163            // and delete has higher priority
164            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 here
192        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        // more test
205        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        // This means file content equal the pure str
233        // which already test before in test_update_table
234        assert_eq!(
235            serde_json::from_str::<HashMap<String, Vec<String>>>(ss).unwrap(),
236            serde_json::from_str(r#"{"rs":["//","/\\*"]}"#).unwrap() // json reader need one more '/'
237        );
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        // more tests
273        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}