dsl_cli_core/parse/
cli.rs

1use std::{any::Any, collections::HashMap, iter::Peekable};
2
3use crate::{Cli, CliArgument, CliOption, FromParsed, error::ParseError};
4
5// The Box<dyn Any> represents either None or a String
6type 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                // Check if the option is included in the template
88                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                // Option has no arguments = flag-only option
99                if opt_def.args.is_empty() {
100                    parsed_opts.insert(opt_def.name.clone(), Box::new(true));
101                    continue;
102                }
103
104                // Handle positional arguments for option
105                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 the option only has one argument, insert the value into the option directly
119                    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                // Check if we've gone past the number of positional arguments
135                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                // Handle positional arguments
142                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    // ------------------------------------------------------------
156    // Utils
157    // ------------------------------------------------------------
158    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    // ------------------------------------------------------------
176    // Validation methods
177    // ------------------------------------------------------------
178    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    // ------------------------------------------------------------
272    // Boolean Utils
273    // ------------------------------------------------------------
274    fn is_option_token(token: &str) -> bool {
275        token.starts_with('-') && token != "-"
276    }
277    // ------------------------------------------------------------
278    // Initialization Utils
279    // ------------------------------------------------------------
280    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}