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
59impl Args {
60 pub fn cover(&mut self, mut other: Self) {
63 if other.filetypes.len() != 0 {
64 self.filetypes = other.filetypes
65 }
66
67 if other.ignore_dirs.len() != 0 {
68 self.ignore_dirs.append(&mut other.ignore_dirs)
69 }
70
71 if other.keywords.is_some() {
72 self.keywords = other.keywords
73 }
74
75 if other.jsonx.is_some() {
76 self.jsonx = other.jsonx
77 }
78
79 if other.targets.len() != 0 {
80 self.targets = other.targets;
81 }
82
83 if other.delete {
84 self.delete = other.delete
85 }
86
87 if other.restore {
88 self.restore = other.restore
89 }
90
91 if other.fmt_command.is_some() {
92 self.fmt_command = other.fmt_command
93 }
94
95 if other.output_format.is_some() {
96 self.output_format = other.output_format
97 }
98
99 self.show_ignore = other.show_ignore
100 }
101
102 pub fn fmt_command(&self) -> Option<&String> {
103 self.fmt_command.as_ref()
104 }
105
106 pub fn config_location(&self) -> String {
107 self.config_location.to_string()
108 }
109}
110
111fn split_space_exclude_those_in_inner_string(s: &str) -> Result<Vec<String>, String> {
112 let mut result = vec![];
113 let mut buf = vec![];
114 let mut in_string = false;
115
116 for b in s.bytes() {
117 if b == b' ' && !in_string {
118 if !buf.is_empty() {
119 result.push(String::from_utf8(buf).map_err(|e| e.to_string())?);
120 buf = vec![];
121 }
122 } else {
123 if b == b'"' {
124 in_string ^= true;
125 continue;
126 }
127 buf.push(b);
128 }
129 }
130
131 if !buf.is_empty() {
132 result.push(String::from_utf8(buf).map_err(|e| e.to_string())?);
133 }
134
135 Ok(result)
136}
137
138fn read_config_raw_content<R: BufRead>(content: R) -> Vec<String> {
139 let buf_reader = BufReader::new(content);
140 let mut a = vec!["codeitlater".to_string()];
141 a.append(
142 &mut buf_reader
143 .lines()
144 .filter_map(|l| {
145 let ll = l.unwrap();
146 if ll.is_empty() {
147 None
148 } else {
149 Some(split_space_exclude_those_in_inner_string(&ll).unwrap())
150 }
151 })
152 .flatten()
153 .collect::<Vec<String>>(),
154 );
155 a
156}
157
158pub fn parse_from_current_path_config(config_folder: String) -> Option<Args> {
159 match File::open(config_folder + "/.codeitlater") {
160 Ok(f) => Some(Args::parse_from(read_config_raw_content(BufReader::new(f)))),
161 Err(_e) => {
162 None
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use clap::Parser;
172
173 #[test]
174 fn test_parse_from_iter() {
175 let args = vec!["codeitlater", "-x", "dd"];
176 assert_eq!(Args::parse_from(args).ignore_dirs, vec!["dd"]);
177
178 let args = vec!["codeitlater", "-x", "dd", "-x", "ff"];
179 assert_eq!(Args::parse_from(args).ignore_dirs, vec!["dd", "ff"]);
180
181 let args = vec!["codeitlater", "-x", "dd", "--", "a", "b", "c"];
183 assert_eq!(
184 Args::parse_from(args).targets,
185 vec!["a".to_string(), "b".to_string(), "c".to_string()]
186 );
187
188 let args = vec!["codeitlater", "--", "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", "--del", "--", "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", "-x", "dd", "-x", "ff", "-D", "a", "b", "c"];
207 let args = Args::parse_from(args);
208 assert_eq!(
209 args.targets,
210 vec!["a".to_string(), "b".to_string(), "c".to_string()]
211 );
212 assert_eq!(args.delete, true);
213 assert_eq!(args.ignore_dirs, vec!["dd", "ff"]);
214
215 let args = vec![
216 "codeitlater",
217 "-x",
218 "dd",
219 "-x",
220 "ff",
221 "-D",
222 "a",
223 "b",
224 "c",
225 "-R",
226 ];
227 let args = Args::parse_from(args);
228 assert_eq!(
229 args.targets,
230 vec!["a".to_string(), "b".to_string(), "c".to_string()]
231 );
232 assert_eq!(args.delete, true);
233 assert_eq!(args.ignore_dirs, vec!["dd", "ff"]);
234 assert_eq!(args.restore, true);
235 }
236
237 #[test]
238 fn test_read_current_path_config() {
239 let content = "
240-x target
241
242-k TODO"
243 .as_bytes();
244 assert_eq!(
246 vec!["codeitlater", "-x", "target", "-k", "TODO"],
247 read_config_raw_content(content)
248 );
249 }
250
251 #[test]
253 fn test_parse_the_fmt_string() {
254 let args = vec!["codeitlater", "--fmt", "aaa bbb"];
255 assert_eq!(Args::parse_from(args).fmt_command.unwrap(), "aaa bbb");
256
257 let args = vec!["codeitlater", "--fmt", r#""cargo fmt""#];
258 assert_eq!(
259 Args::parse_from(args).fmt_command.unwrap(),
260 r#""cargo fmt""#
261 )
262 }
263}