dsl_cli_core/parse/
cli.rs1use std::{any::Any, collections::HashMap, iter::Peekable};
2
3use crate::{Cli, CliArgument, CliOption, FromParsed, error::ParseError};
4
5type ParsedArgs = HashMap<String, Box<dyn Any>>;
7type ParsedOpts = HashMap<String, Box<dyn Any>>;
8
9impl Cli {
10 pub fn parse(&mut self, env_args: Vec<String>) -> (ParsedArgs, ParsedOpts) {
11 let result = self.try_parse(env_args);
12
13 match result {
14 Ok((parsed_args, parsed_opts)) => (parsed_args, parsed_opts),
15 Err(e) => {
16 self.handle_parse_error(e);
17 std::process::exit(1);
18 }
19 }
20 }
21 fn try_parse(&mut self, env_args: Vec<String>) -> Result<(ParsedArgs, ParsedOpts), ParseError> {
22 let potential_cmd_name = &env_args
23 .first()
24 .map(|s| s.to_owned())
25 .unwrap_or("".to_owned());
26
27 let potential_cmd_name = potential_cmd_name.as_str();
28
29 if potential_cmd_name == "help" {
30 let second = env_args.get(1).map(|s| s.to_owned());
31
32 if let Some(second) = second {
33 if let None = self.commands.iter().find(|cmd| cmd.name == second) {
34 return Err(ParseError::InvalidCommand(second.to_string()));
35 }
36
37 self.show_help(second.to_string());
38 } else {
39 self.show_help("cli".to_owned());
40 }
41 std::process::exit(0);
42 }
43
44 let possible_command_names = self
45 .commands
46 .iter()
47 .map(|cmd| cmd.name.as_str())
48 .collect::<Vec<&str>>();
49
50 let mut env_args = env_args.into_iter();
51 let command_def = if possible_command_names.contains(&potential_cmd_name) {
52 self.used_command = Some(potential_cmd_name.to_owned());
53 env_args.next();
54 self.commands
55 .iter()
56 .find(|cmd| cmd.name == potential_cmd_name)
57 .unwrap()
58 } else if possible_command_names.contains(&"cli") {
59 self.used_command = Some("cli".to_owned());
60 &self.commands.iter().find(|cmd| cmd.name == "cli").unwrap()
61 } else {
62 return Err(ParseError::InvalidCommand(potential_cmd_name.to_string()));
63 };
64 let env_args = env_args.collect::<Vec<String>>();
65
66 let (parsed_args, parsed_opts) = Self::parse_args(
67 env_args,
68 command_def.arguments.clone(),
69 command_def.options.clone(),
70 )?;
71
72 Ok((parsed_args, parsed_opts))
73 }
74
75 fn parse_args(
76 env_args: Vec<String>,
77 template_args: Vec<CliArgument>,
78 template_opts: Vec<CliOption>,
79 ) -> Result<(ParsedArgs, ParsedOpts), ParseError> {
80 let mut parsed_args = Self::initialize_parsed_args(&template_args);
81 let mut parsed_opts = Self::initialize_parsed_opts(&template_opts);
82 let mut tokens = env_args.into_iter().peekable();
83 let mut positional_idx = 0;
84
85 while let Some(token) = tokens.next() {
86 if Self::is_option_token(&token) {
87 if !template_opts.iter().any(|opt| opt.flags == token) {
89 return Err(ParseError::InvalidOptionFlag(token));
90 }
91
92 let opt_idx = template_opts
93 .iter()
94 .position(|opt| opt.flags == token)
95 .unwrap();
96 let opt_def = &template_opts[opt_idx];
97
98 if opt_def.args.is_empty() {
100 parsed_opts.insert(opt_def.name.clone(), Box::new(true));
101 continue;
102 }
103
104 let opt_args = opt_def.args.clone();
106 let mut parsed_opt_args = Self::initialize_parsed_args(&opt_args);
107 let mut idx = 0;
108
109 while idx < parsed_opt_args.len() {
110 if tokens.peek().is_none() || Self::is_option_token(tokens.peek().unwrap()) {
111 break;
112 }
113
114 let arg_def = &opt_args[idx];
115 let token = tokens.next().unwrap();
116 let parsed_value = Self::parse_arg(arg_def, token, &mut tokens)?;
117
118 if parsed_opt_args.len() == 1 {
120 parsed_opts.insert(opt_def.name.clone(), parsed_value);
121 } else {
122 parsed_opt_args.insert(arg_def.name.clone(), parsed_value);
123 }
124
125 idx += 1;
126 }
127
128 Self::check_for_missing_required_args(&opt_args, idx, Some(opt_idx))?;
129
130 if opt_def.args.len() > 1 {
131 parsed_opts.insert(opt_def.name.clone(), Box::new(parsed_opt_args));
132 }
133 } else {
134 if positional_idx >= template_args.len() {
136 let mut remaining_args = vec![token];
137 remaining_args.extend(tokens);
138 return Err(ParseError::TooManyArguments(remaining_args));
139 }
140
141 let arg_def = &template_args[positional_idx];
143 let parsed_value = Self::parse_arg(arg_def, token, &mut tokens)?;
144 parsed_args.insert(arg_def.name.clone(), parsed_value);
145 positional_idx += 1;
146 }
147 }
148
149 Self::check_for_missing_required_args(&template_args, positional_idx, None)?;
150 Self::check_for_missing_required_opts(&parsed_opts, &template_opts)?;
151
152 Ok((parsed_args, parsed_opts))
153 }
154
155 fn parse_arg(
159 arg_def: &CliArgument,
160 current_token: String,
161 tokens: &mut Peekable<std::vec::IntoIter<String>>,
162 ) -> Result<Box<dyn Any>, ParseError> {
163 if arg_def.variadic {
164 let mut values = vec![current_token];
165 while tokens.peek().is_some() && !Self::is_option_token(tokens.peek().unwrap()) {
166 values.push(tokens.next().unwrap());
167 }
168
169 Ok(Box::new(values))
170 } else {
171 Ok(Box::new(current_token))
172 }
173 }
174
175 fn check_for_missing_required_opts(
179 parsed_opts: &ParsedOpts,
180 template_opts: &Vec<CliOption>,
181 ) -> Result<(), ParseError> {
182 let required_opts = template_opts.iter().filter(|opt| !opt.optional);
183
184 let mut missing_required_opts = Vec::new();
185
186 for opt in required_opts {
187 let flags = format!(
188 "({})",
189 opt.flags
190 .values()
191 .iter()
192 .filter_map(|f| f.as_ref().map(|s| s.to_string()))
193 .collect::<Vec<String>>()
194 .join(", ")
195 );
196
197 match opt.args.len() {
198 0 => {
199 if parsed_opts
200 .get(&opt.name.clone())
201 .unwrap()
202 .downcast_ref::<bool>()
203 .is_none()
204 {
205 missing_required_opts.push(flags);
206 }
207 }
208 1 => {
209 let arg_def = &opt.args[0];
210 if arg_def.variadic {
211 if parsed_opts
212 .get(&opt.name.clone())
213 .unwrap()
214 .downcast_ref::<Vec<String>>()
215 .is_none()
216 {
217 missing_required_opts.push(flags);
218 }
219 } else {
220 if parsed_opts
221 .get(&opt.name.clone())
222 .unwrap()
223 .downcast_ref::<String>()
224 .is_none()
225 {
226 missing_required_opts.push(flags);
227 }
228 }
229 }
230 _ => {
231 if parsed_opts
232 .get(&opt.name.clone())
233 .unwrap()
234 .downcast_ref::<HashMap<String, Box<dyn Any>>>()
235 .is_none()
236 {
237 missing_required_opts.push(flags);
238 }
239 }
240 };
241 }
242
243 if !missing_required_opts.is_empty() {
244 return Err(ParseError::MissingRequiredOptions(missing_required_opts));
245 }
246
247 Ok(())
248 }
249 fn check_for_missing_required_args(
250 template_args: &Vec<CliArgument>,
251 positional_idx: usize,
252 opt_idx: Option<usize>,
253 ) -> Result<(), ParseError> {
254 if positional_idx < template_args.iter().filter(|arg| !arg.optional).count() {
255 let missing_args = template_args[positional_idx..]
256 .iter()
257 .filter(|arg| !arg.optional)
258 .map(|arg| arg.reconstruct_name())
259 .collect::<Vec<String>>();
260 if let Some(opt_idx) = opt_idx {
261 return Err(ParseError::MissingRequiredArgumentsForOption(
262 opt_idx,
263 missing_args,
264 ));
265 } else {
266 return Err(ParseError::MissingRequiredArguments(missing_args));
267 }
268 }
269 Ok(())
270 }
271 fn is_option_token(token: &str) -> bool {
275 token.starts_with('-') && token != "-"
276 }
277 fn initialize_parsed_args(template_args: &Vec<CliArgument>) -> ParsedArgs {
281 let mut parsed_args: ParsedArgs = HashMap::new();
282 for arg in template_args {
283 match arg.variadic {
284 true => parsed_args.insert(arg.name.clone(), Box::new(None::<Vec<String>>)),
285 false => parsed_args.insert(arg.name.clone(), Box::new(None::<String>)),
286 };
287 }
288 parsed_args
289 }
290 fn initialize_parsed_opts(template_opts: &Vec<CliOption>) -> ParsedOpts {
291 let mut parsed_opts: ParsedOpts = HashMap::new();
292 for opt in template_opts {
293 match opt.args.len() {
294 0 => {
295 parsed_opts.insert(opt.name.clone(), Box::new(None::<bool>));
296 }
297 1 => {
298 let arg_def = &opt.args[0];
299 if arg_def.variadic {
300 parsed_opts.insert(opt.name.clone(), Box::new(None::<Vec<String>>));
301 } else {
302 parsed_opts.insert(opt.name.clone(), Box::new(None::<String>));
303 }
304 }
305 _ => {
306 if opt.optional {
307 let parsed_opt_args = Self::initialize_parsed_args(&opt.args);
308 parsed_opts.insert(opt.name.clone(), Box::new(parsed_opt_args));
309 } else {
310 parsed_opts.insert(
311 opt.name.clone(),
312 Box::new(None::<HashMap<String, Box<dyn Any>>>),
313 );
314 }
315 }
316 }
317 }
318 parsed_opts
319 }
320}