code_it_later_rs/
args.rs

1//! The arguments of codeitlater are using
2
3use clap::Parser;
4use std::{
5    ffi::OsString,
6    fs::File,
7    io::{BufRead, BufReader},
8};
9
10/// Command Line Args
11#[derive(Default, Parser, Debug)]
12#[command(author = "ccQpein", version, about)]
13pub struct Args {
14    /// What are the filetypes you want to scan.
15    #[arg(short, long)]
16    pub(crate) filetypes: Vec<OsString>,
17
18    /// The folder name should ignored
19    #[arg(short = 'x', long = "ignore-dir")]
20    pub(crate) ignore_dirs: Vec<OsString>,
21
22    /// Keywords
23    #[arg(short, long)]
24    pub(crate) keywords: Option<Vec<String>>,
25
26    /// Expand dictionary json file path
27    #[arg(short, long)]
28    pub(crate) jsonx: Option<String>,
29
30    /// Files/Dirs input directly
31    #[arg(value_name = "files/dirs", default_value = ".")]
32    pub(crate) targets: Vec<String>,
33
34    /// Delete the crumbs
35    #[arg(short = 'D', long = "del")]
36    pub(crate) delete: bool,
37
38    /// Restore the crumbs back to normal comment
39    #[arg(short = 'R', long = "restore")]
40    pub(crate) restore: bool,
41
42    /// Format command after delete crumbs
43    #[arg(long = "fmt")]
44    pub(crate) fmt_command: Option<String>,
45
46    /// Output format: json, list
47    #[arg(short = 'O', long = "output-format")]
48    pub(crate) output_format: Option<String>,
49
50    /// Show all ignored crumbs
51    #[arg(long = "show-ignored", default_value = "false")]
52    pub(crate) show_ignore: bool,
53
54    /// Config file location, default value it "."
55    #[arg(short = 'C', long = "config", default_value = ".")]
56    pub(crate) config_location: String,
57
58    /// Show content around the crumb for giving more context
59    #[arg(short, long, default_value = "0")]
60    pub(crate) range: u32,
61}
62
63impl Args {
64    /// cover this args with other, self values totally rewrotten by other
65    /// if both of args have same fields. Except ignore dirs, they are merged
66    pub fn cover(&mut self, mut other: Self) {
67        if other.filetypes.len() != 0 {
68            self.filetypes = other.filetypes
69        }
70
71        if other.ignore_dirs.len() != 0 {
72            self.ignore_dirs.append(&mut other.ignore_dirs)
73        }
74
75        if other.keywords.is_some() {
76            self.keywords = other.keywords
77        }
78
79        if other.jsonx.is_some() {
80            self.jsonx = other.jsonx
81        }
82
83        if other.targets.len() != 0 {
84            self.targets = other.targets;
85        }
86
87        if other.delete {
88            self.delete = other.delete
89        }
90
91        if other.restore {
92            self.restore = other.restore
93        }
94
95        if other.fmt_command.is_some() {
96            self.fmt_command = other.fmt_command
97        }
98
99        if other.output_format.is_some() {
100            self.output_format = other.output_format
101        }
102
103        self.show_ignore = other.show_ignore;
104
105        self.range = other.range
106    }
107
108    pub fn fmt_command(&self) -> Option<&String> {
109        self.fmt_command.as_ref()
110    }
111
112    pub fn config_location(&self) -> String {
113        self.config_location.to_string()
114    }
115}
116
117fn split_space_exclude_those_in_inner_string(s: &str) -> Result<Vec<String>, String> {
118    let mut result = vec![];
119    let mut buf = vec![];
120    let mut in_string = false;
121
122    for b in s.bytes() {
123        if b == b' ' && !in_string {
124            if !buf.is_empty() {
125                result.push(String::from_utf8(buf).map_err(|e| e.to_string())?);
126                buf = vec![];
127            }
128        } else {
129            if b == b'"' {
130                in_string ^= true;
131                continue;
132            }
133            buf.push(b);
134        }
135    }
136
137    if !buf.is_empty() {
138        result.push(String::from_utf8(buf).map_err(|e| e.to_string())?);
139    }
140
141    Ok(result)
142}
143
144fn read_config_raw_content<R: BufRead>(content: R) -> Vec<String> {
145    let buf_reader = BufReader::new(content);
146    let mut a = vec!["codeitlater".to_string()];
147    a.append(
148        &mut buf_reader
149            .lines()
150            .filter_map(|l| {
151                let ll = l.unwrap();
152                if ll.is_empty() {
153                    None
154                } else {
155                    Some(split_space_exclude_those_in_inner_string(&ll).unwrap())
156                }
157            })
158            .flatten()
159            .collect::<Vec<String>>(),
160    );
161    a
162}
163
164pub fn parse_from_current_path_config(config_folder: String) -> Option<Args> {
165    match File::open(config_folder + "/.codeitlater") {
166        Ok(f) => Some(Args::parse_from(read_config_raw_content(BufReader::new(f)))),
167        Err(_e) => {
168            //println!("{}", _e.to_string());
169            None
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use clap::Parser;
178
179    #[test]
180    fn test_parse_from_iter() {
181        let args = vec!["codeitlater", "-x", "dd"];
182        assert_eq!(Args::parse_from(args).ignore_dirs, vec!["dd"]);
183
184        let args = vec!["codeitlater", "-x", "dd", "-x", "ff"];
185        assert_eq!(Args::parse_from(args).ignore_dirs, vec!["dd", "ff"]);
186
187        // if there are some options, "--" is required
188        let args = vec!["codeitlater", "-x", "dd", "--", "a", "b", "c"];
189        assert_eq!(
190            Args::parse_from(args).targets,
191            vec!["a".to_string(), "b".to_string(), "c".to_string()]
192        );
193
194        let args = vec!["codeitlater", "--", "a", "b", "c"];
195        assert_eq!(
196            Args::parse_from(args).targets,
197            vec!["a".to_string(), "b".to_string(), "c".to_string()]
198        );
199
200        let args = vec!["codeitlater", "a", "b", "c"];
201        assert_eq!(
202            Args::parse_from(args).targets,
203            vec!["a".to_string(), "b".to_string(), "c".to_string()]
204        );
205
206        let args = vec!["codeitlater", "--del", "--", "a", "b", "c"];
207        assert_eq!(
208            Args::parse_from(args).targets,
209            vec!["a".to_string(), "b".to_string(), "c".to_string()]
210        );
211
212        let args = vec!["codeitlater", "-x", "dd", "-x", "ff", "-D", "a", "b", "c"];
213        let args = Args::parse_from(args);
214        assert_eq!(
215            args.targets,
216            vec!["a".to_string(), "b".to_string(), "c".to_string()]
217        );
218        assert_eq!(args.delete, true);
219        assert_eq!(args.ignore_dirs, vec!["dd", "ff"]);
220
221        let args = vec![
222            "codeitlater",
223            "-x",
224            "dd",
225            "-x",
226            "ff",
227            "-D",
228            "a",
229            "b",
230            "c",
231            "-R",
232        ];
233        let args = Args::parse_from(args);
234        assert_eq!(
235            args.targets,
236            vec!["a".to_string(), "b".to_string(), "c".to_string()]
237        );
238        assert_eq!(args.delete, true);
239        assert_eq!(args.ignore_dirs, vec!["dd", "ff"]);
240        assert_eq!(args.restore, true);
241    }
242
243    #[test]
244    fn test_read_current_path_config() {
245        let content = "
246-x target
247
248-k    TODO"
249            .as_bytes();
250        //dbg!(read_config_raw_content(content));
251        assert_eq!(
252            vec!["codeitlater", "-x", "target", "-k", "TODO"],
253            read_config_raw_content(content)
254        );
255    }
256
257    /// fmt command is the shell command, so it has to be string
258    #[test]
259    fn test_parse_the_fmt_string() {
260        let args = vec!["codeitlater", "--fmt", "aaa bbb"];
261        assert_eq!(Args::parse_from(args).fmt_command.unwrap(), "aaa bbb");
262
263        let args = vec!["codeitlater", "--fmt", r#""cargo fmt""#];
264        assert_eq!(
265            Args::parse_from(args).fmt_command.unwrap(),
266            r#""cargo fmt""#
267        )
268    }
269}