1use std::collections::HashMap;
2use std::{result, env};
3use std::rc::Rc;
4use crate::error::ParserError;
5use crate::{Group, HelpEntry, HelpPrinter};
6use crate::option;
7use crate::arg;
8use crate::help::DefaultHelpPrinter;
9
10pub type Result<T> = result::Result<T, ParserError>;
12
13static OPTION_PREFIX: char = '-';
14static OPTION_KEY_VALUE_SPLIT: char = '=';
15static HELP_OPTION: &str = "help";
16static HELP_OPTION_ALIAS: &str = "?";
17
18pub struct ParseOptions {
20 pub help_printer: Option<Box<dyn HelpPrinter>>,
22}
23
24pub fn parse(group: Group, options: Option<ParseOptions>) -> Result<()> {
26 let args: Vec<String> = env::args().collect();
27 let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
28
29 parse_from(group, &args[..], options)
30}
31
32pub fn parse_from(group: Group, args: &[&str], options: Option<ParseOptions>) -> Result<()> {
34 let group = Rc::new(group);
35
36 let (ctx_group, anticipated_options, parse_start_pos) = prepare_parsing_context(Rc::clone(&group), args)?;
37 let arg_descriptors = ctx_group.get_arguments();
38
39 let option_descriptor_lookup = prepare_option_descriptor_lookup(&anticipated_options)?;
40
41 let (raw_options, raw_arguments) = split_raw_arguments(&args[parse_start_pos..], &option_descriptor_lookup)?;
42
43 let mut option_value_lookup = parse_options(raw_options, &option_descriptor_lookup)?;
44 fill_default_options(&mut option_value_lookup, &anticipated_options);
45
46 if let option::Value::Bool { value } = option_value_lookup.get(HELP_OPTION).unwrap() {
48 if *value {
49 show_help(
50 &ctx_group,
51 &anticipated_options,
52 arg_descriptors,
53 if options.is_some() { options.unwrap().help_printer } else { None },
54 );
55 return Ok(());
56 }
57 }
58
59 let argument_values = parse_arguments(arg_descriptors, raw_arguments)?;
60
61 ctx_group.get_consumer()(&argument_values, &option_value_lookup);
63 Ok(())
64}
65
66fn prepare_parsing_context<'a>(group: Rc<Group>, args: &[&str]) -> Result<(Rc<Group>, HashMap<Rc<String>, Rc<option::Descriptor>>, usize)> {
70 let mut anticipated_options: HashMap<Rc<String>, Rc<option::Descriptor>> = HashMap::new();
71
72 let help_option_descriptor = option::Descriptor::new(HELP_OPTION, option::Type::Bool { default: false }, "Get this information displayed")
74 .add_alias(HELP_OPTION_ALIAS);
75 anticipated_options.insert(help_option_descriptor.take_name(), Rc::new(help_option_descriptor));
76
77 for (option_name, option_descriptor) in group.get_options() {
79 anticipated_options.insert(Rc::clone(option_name), Rc::clone(option_descriptor));
80 }
81
82 let mut cur_group = group;
84 let mut args_pos = 1;
85
86 for arg in &args[1..] {
87 let arg = *arg;
88
89 match cur_group.get_child_known_for(arg) {
90 Some(v) => {
91 cur_group = v;
92
93 for (option_name, option_descriptor) in cur_group.get_options() {
95 if anticipated_options.contains_key(option_name) {
96 return Err(ParserError {
97 message: format!("Option '{}' declared multiple times in group specifications", option_name)
98 });
99 }
100 anticipated_options.insert(Rc::clone(option_name), Rc::clone(option_descriptor));
101 }
102 }
103 None => break };
105
106 args_pos += 1;
107 }
108
109 Ok((cur_group, anticipated_options, args_pos))
110}
111
112fn prepare_option_descriptor_lookup(anticipated_options: &HashMap<Rc<String>, Rc<option::Descriptor>>) -> Result<HashMap<&String, &option::Descriptor>> {
114 let mut option_descriptor_lookup = HashMap::new();
115
116 for (option_name, option_descriptor) in anticipated_options {
117 if option_descriptor_lookup.contains_key(option_name.as_ref()) {
118 return Err(ParserError {
119 message: format!("Option name or alias '{}' specified more than once", option_name.as_ref()),
120 });
121 }
122 option_descriptor_lookup.insert(option_name.as_ref(), option_descriptor.as_ref());
123
124 for alias in option_descriptor.get_aliases() {
125 if option_descriptor_lookup.contains_key(alias) {
126 return Err(ParserError {
127 message: format!("Option name or alias '{}' specified more than once", alias),
128 });
129 }
130 option_descriptor_lookup.insert(alias, option_descriptor.as_ref());
131 }
132 }
133
134 Ok(option_descriptor_lookup)
135}
136
137fn get_option_descriptor_for_name<'a>(option_name: &str, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<&'a option::Descriptor> {
139 match option_descriptor_lookup.get(&String::from(option_name)) {
140 Some(o) => Ok(*o),
141 None => Err(ParserError {
142 message: format!("Option '--{}' is unknown in the command context", option_name)
143 })
144 }
145}
146
147fn is_option(raw_arg: &str) -> bool {
149 raw_arg.starts_with(OPTION_PREFIX)
150}
151
152fn split_raw_arguments<'a>(args: &[&'a str], option_descriptor_lookup: &HashMap<&String, &option::Descriptor>) -> Result<(HashMap<&'a str, &'a str>, Vec<&'a str>)> {
154 let mut raw_options = HashMap::new();
155 let mut raw_arguments = Vec::new();
156
157 let mut skip_next = false;
158 for i in 0..args.len() {
159 if skip_next {
160 skip_next = false;
161 continue;
162 }
163
164 let arg = args[i];
165
166 if is_option(arg) {
167 let raw_option = arg.trim_start_matches(OPTION_PREFIX); let is_key_value_option = arg.contains(OPTION_KEY_VALUE_SPLIT);
170 let (option_name, option_value) = if is_key_value_option {
171 let parts: Vec<&str> = raw_option.split(OPTION_KEY_VALUE_SPLIT).collect();
173 (parts[0], parts[1])
174 } else {
175 let next_arg = if args.len() > i + 1 { Some(&args[i + 1]) } else { None };
177
178 let is_option_without_value = next_arg.is_none()
179 || is_option(next_arg.unwrap());
180
181 let option_value = if is_option_without_value {
182 let option_type = get_option_descriptor_for_name(raw_option, option_descriptor_lookup)?.value_type();
184
185 match option_type {
186 option::Type::Bool { default: _ } => "true",
187 _ => return Err(ParserError {
188 message: format!("Encountered option '{}' without value that is not of type boolean. Specify a value for the option.", raw_option)
189 })
190 }
191 } else {
192 next_arg.unwrap()
193 };
194
195 skip_next = true; (raw_option, option_value)
198 };
199
200 raw_options.insert(option_name, option_value);
201 } else {
202 raw_arguments.push(arg);
203 }
204 }
205
206 Ok((raw_options, raw_arguments))
207}
208
209fn parse_options<'a>(raw_options: HashMap<&str, &str>, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<HashMap<&'a str, option::Value>> {
211 let mut option_value_lookup: HashMap<&str, option::Value> = HashMap::new();
212
213 for (option_name, raw_value) in raw_options.into_iter() {
214 let (option_name, option_value) = parse_option(option_name, raw_value, option_descriptor_lookup)?;
215 option_value_lookup.insert(option_name, option_value);
216 }
217
218 Ok(option_value_lookup)
219}
220
221fn parse_option<'a>(name: &str, raw_value: &str, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<(&'a String, option::Value)> {
223 let option_descriptor = get_option_descriptor_for_name(name, option_descriptor_lookup)?;
224
225 Ok((option_descriptor.name(), match option::Value::parse(option_descriptor.value_type(), raw_value) {
226 Ok(v) => v,
227 Err(_) => return Err(ParserError {
228 message: format!("Expected value '{}' of option '--{}' to be of type '{}'", raw_value, name, option_descriptor.value_type())
229 })
230 }))
231}
232
233fn fill_default_options<'a>(option_value_lookup: &mut HashMap<&'a str, option::Value>, anticipated_options: &'a HashMap<Rc<String>, Rc<option::Descriptor>>) {
235 for (option_name, descriptor) in anticipated_options {
236 if !option_value_lookup.contains_key(option_name as &str) {
237 option_value_lookup.insert(option_name, option::Value::from_default(descriptor.value_type()));
238 }
239 }
240}
241
242fn parse_arguments(descriptors: &Vec<arg::Descriptor>, raw_arguments: Vec<&str>) -> Result<Vec<arg::Value>> {
244 if raw_arguments.len() != descriptors.len() {
245 return Err(ParserError {
246 message: format!("Expected to have {} arguments but got {}", descriptors.len(), raw_arguments.len())
247 });
248 }
249
250 let mut argument_values = Vec::with_capacity(raw_arguments.len());
251 for i in 0..raw_arguments.len() {
252 let desc = &descriptors[i];
253 let arg = raw_arguments[i];
254
255 let value = match arg::Value::parse(desc.value_type(), arg) {
257 Ok(v) => v,
258 Err(_) => return Err(ParserError {
259 message: format!("Expected argument '{}' at position {} to be of type '{}'", arg, i + 1, desc.value_type())
260 })
261 };
262
263 argument_values.push(value);
264 }
265
266 Ok(argument_values)
267}
268
269fn show_help(group: &Group, option_descriptors: &HashMap<Rc<String>, Rc<option::Descriptor>>, arg_descriptors: &Vec<arg::Descriptor>, help_printer: Option<Box<dyn HelpPrinter>>) {
271 let mut subcommand_entries = Vec::with_capacity(group.get_children().len());
273 for (group_name, group) in group.get_children() {
274 subcommand_entries.push(HelpEntry {
275 key: group_name,
276 value: group,
277 });
278 }
279 subcommand_entries.sort_by(|a, b| a.key.cmp(b.key));
280
281 let mut option_entries = Vec::with_capacity(option_descriptors.len());
283 for (option_name, option_descriptor) in option_descriptors {
284 option_entries.push(HelpEntry {
285 key: option_name,
286 value: option_descriptor,
287 });
288 }
289 option_entries.sort_by(|a, b| a.key.cmp(b.key));
290
291 match help_printer {
292 Some(v) => v.print(group, &subcommand_entries, &option_entries, arg_descriptors),
293 None => DefaultHelpPrinter {}.print(group, &subcommand_entries, &option_entries, arg_descriptors),
294 }
295}