Skip to main content

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