1use clap::Parser;
4use std::{
5 ffi::OsString,
6 fs::File,
7 io::{BufRead, BufReader},
8};
9
10#[derive(Default, Parser, Debug)]
12#[command(author = "ccQpein", version, about)]
13pub struct Args {
14 #[arg(short, long)]
16 pub(crate) filetypes: Vec<OsString>,
17
18 #[arg(short = 'x', long = "ignore-dir")]
20 pub(crate) ignore_dirs: Vec<OsString>,
21
22 #[arg(short, long)]
24 pub(crate) keywords: Option<Vec<String>>,
25
26 #[arg(short, long)]
28 pub(crate) jsonx: Option<String>,
29
30 #[arg(value_name = "files/dirs", default_value = ".")]
32 pub(crate) targets: Vec<String>,
33
34 #[arg(short = 'D', long = "del")]
36 pub(crate) delete: bool,
37
38 #[arg(short = 'R', long = "restore")]
40 pub(crate) restore: bool,
41
42 #[arg(short = 'y', long = "yes")]
44 pub(crate) yes: bool,
45
46 #[arg(long = "fmt")]
48 pub(crate) fmt_command: Option<String>,
49
50 #[arg(short = 'O', long = "output-format")]
52 pub(crate) output_format: Option<String>,
53
54 #[arg(long = "show-ignored", default_value = "false")]
56 pub(crate) show_ignore: bool,
57
58 #[arg(short = 'C', long = "config", default_value = ".")]
60 pub(crate) config_location: String,
61
62 #[arg(short, long, default_value = "0")]
64 pub(crate) range: u32,
65}
66
67impl Args {
68 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 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 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 assert_eq!(
265 vec!["codeitlater", "-x", "target", "-k", "TODO"],
266 read_config_raw_content(content)
267 );
268 }
269
270 #[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}