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(long = "fmt")]
44 pub(crate) fmt_command: Option<String>,
45
46 #[arg(short = 'O', long = "output-format")]
48 pub(crate) output_format: Option<String>,
49
50 #[arg(long = "show-ignored", default_value = "false")]
52 pub(crate) show_ignore: bool,
53
54 #[arg(short = 'C', long = "config", default_value = ".")]
56 pub(crate) config_location: String,
57
58 #[arg(short, long, default_value = "0")]
60 pub(crate) range: u32,
61}
62
63impl Args {
64 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 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 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 assert_eq!(
252 vec!["codeitlater", "-x", "target", "-k", "TODO"],
253 read_config_raw_content(content)
254 );
255 }
256
257 #[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}