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
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
47/// Update static table with new raw_json str
48fn 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
65/// Making regex string
66fn 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
79/// making the keyword regex, case insensitive
80pub(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/// config when running
117#[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    /// if delete
124    pub(super) delete: bool,
125
126    /// if restore
127    pub(super) restore: bool,
128
129    /// assume yes to prompts
130    pub(super) yes: bool,
131
132    /// output format
133    pub(super) output: OutputFormat,
134
135    /// show ignored
136    pub(super) show_ignored: bool,
137
138    /// show the range of the content around the crumb
139    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            // delete and restore cannot be true at the same time
172            // and delete has higher priority
173            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 here
202        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        // more test
215        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        // This means file content equal the pure str
243        // which already test before in test_update_table
244        assert_eq!(
245            serde_json::from_str::<HashMap<String, Vec<String>>>(ss).unwrap(),
246            serde_json::from_str(r#"{"rs":["//","/\\*"]}"#).unwrap() // json reader need one more '/'
247        );
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        // more tests
283        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}