use std::collections::HashMap;
use std::{result, env};
use std::rc::Rc;
use crate::error::ParserError;
use crate::{Group, HelpEntry, HelpPrinter};
use crate::option;
use crate::arg;
use crate::help::DefaultHelpPrinter;
pub type Result<T> = result::Result<T, ParserError>;
static OPTION_PREFIX: char = '-';
static OPTION_KEY_VALUE_SPLIT: char = '=';
static HELP_OPTION: &str = "help";
static HELP_OPTION_ALIAS: &str = "?";
pub struct ParseOptions {
pub help_printer: Option<Box<dyn HelpPrinter>>,
}
pub fn parse(group: Group, options: Option<ParseOptions>) -> Result<()> {
let args: Vec<String> = env::args().collect();
let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
parse_from(group, &args[..], options)
}
pub fn parse_from(group: Group, args: &[&str], options: Option<ParseOptions>) -> Result<()> {
let group = Rc::new(group);
let (ctx_group, anticipated_options, parse_start_pos) = prepare_parsing_context(Rc::clone(&group), args)?;
let arg_descriptors = ctx_group.get_arguments();
let option_descriptor_lookup = prepare_option_descriptor_lookup(&anticipated_options)?;
let (raw_options, raw_arguments) = split_raw_arguments(&args[parse_start_pos..], &option_descriptor_lookup)?;
let mut option_value_lookup = parse_options(raw_options, &option_descriptor_lookup)?;
fill_default_options(&mut option_value_lookup, &anticipated_options);
if let option::Value::Bool { value } = option_value_lookup.get(HELP_OPTION).unwrap() {
if *value {
show_help(
&ctx_group,
&anticipated_options,
arg_descriptors,
if options.is_some() { options.unwrap().help_printer } else { None },
);
return Ok(());
}
}
let argument_values = parse_arguments(arg_descriptors, raw_arguments)?;
ctx_group.get_consumer()(&argument_values, &option_value_lookup);
Ok(())
}
fn prepare_parsing_context<'a>(group: Rc<Group>, args: &[&str]) -> Result<(Rc<Group>, HashMap<Rc<String>, Rc<option::Descriptor>>, usize)> {
let mut anticipated_options: HashMap<Rc<String>, Rc<option::Descriptor>> = HashMap::new();
let help_option_descriptor = option::Descriptor::new(HELP_OPTION, option::Type::Bool { default: false }, "Get this information displayed")
.add_alias(HELP_OPTION_ALIAS);
anticipated_options.insert(help_option_descriptor.take_name(), Rc::new(help_option_descriptor));
for (option_name, option_descriptor) in group.get_options() {
anticipated_options.insert(Rc::clone(option_name), Rc::clone(option_descriptor));
}
let mut cur_group = group;
let mut args_pos = 1;
for arg in &args[1..] {
let arg = *arg;
match cur_group.get_child_known_for(arg) {
Some(v) => {
cur_group = v;
for (option_name, option_descriptor) in cur_group.get_options() {
if anticipated_options.contains_key(option_name) {
return Err(ParserError {
message: format!("Option '{}' declared multiple times in group specifications", option_name)
});
}
anticipated_options.insert(Rc::clone(option_name), Rc::clone(option_descriptor));
}
}
None => break };
args_pos += 1;
}
Ok((cur_group, anticipated_options, args_pos))
}
fn prepare_option_descriptor_lookup(anticipated_options: &HashMap<Rc<String>, Rc<option::Descriptor>>) -> Result<HashMap<&String, &option::Descriptor>> {
let mut option_descriptor_lookup = HashMap::new();
for (option_name, option_descriptor) in anticipated_options {
if option_descriptor_lookup.contains_key(option_name.as_ref()) {
return Err(ParserError {
message: format!("Option name or alias '{}' specified more than once", option_name.as_ref()),
});
}
option_descriptor_lookup.insert(option_name.as_ref(), option_descriptor.as_ref());
for alias in option_descriptor.get_aliases() {
if option_descriptor_lookup.contains_key(alias) {
return Err(ParserError {
message: format!("Option name or alias '{}' specified more than once", alias),
});
}
option_descriptor_lookup.insert(alias, option_descriptor.as_ref());
}
}
Ok(option_descriptor_lookup)
}
fn get_option_descriptor_for_name<'a>(option_name: &str, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<&'a option::Descriptor> {
match option_descriptor_lookup.get(&String::from(option_name)) {
Some(o) => Ok(*o),
None => Err(ParserError {
message: format!("Option '--{}' is unknown in the command context", option_name)
})
}
}
fn is_option(raw_arg: &str) -> bool {
raw_arg.starts_with(OPTION_PREFIX)
}
fn split_raw_arguments<'a>(args: &[&'a str], option_descriptor_lookup: &HashMap<&String, &option::Descriptor>) -> Result<(HashMap<&'a str, &'a str>, Vec<&'a str>)> {
let mut raw_options = HashMap::new();
let mut raw_arguments = Vec::new();
let mut skip_next = false;
for i in 0..args.len() {
if skip_next {
skip_next = false;
continue;
}
let arg = args[i];
if is_option(arg) {
let raw_option = arg.trim_start_matches(OPTION_PREFIX);
let is_key_value_option = arg.contains(OPTION_KEY_VALUE_SPLIT);
let (option_name, option_value) = if is_key_value_option {
let parts: Vec<&str> = raw_option.split(OPTION_KEY_VALUE_SPLIT).collect();
(parts[0], parts[1])
} else {
let next_arg = if args.len() > i + 1 { Some(&args[i + 1]) } else { None };
let is_option_without_value = next_arg.is_none()
|| is_option(next_arg.unwrap());
let option_value = if is_option_without_value {
let option_type = get_option_descriptor_for_name(raw_option, option_descriptor_lookup)?.value_type();
match option_type {
option::Type::Bool { default: _ } => "true",
_ => return Err(ParserError {
message: format!("Encountered option '{}' without value that is not of type boolean. Specify a value for the option.", raw_option)
})
}
} else {
next_arg.unwrap()
};
skip_next = true;
(raw_option, option_value)
};
raw_options.insert(option_name, option_value);
} else {
raw_arguments.push(arg);
}
}
Ok((raw_options, raw_arguments))
}
fn parse_options<'a>(raw_options: HashMap<&str, &str>, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<HashMap<&'a str, option::Value>> {
let mut option_value_lookup: HashMap<&str, option::Value> = HashMap::new();
for (option_name, raw_value) in raw_options.into_iter() {
let (option_name, option_value) = parse_option(option_name, raw_value, option_descriptor_lookup)?;
option_value_lookup.insert(option_name, option_value);
}
Ok(option_value_lookup)
}
fn parse_option<'a>(name: &str, raw_value: &str, option_descriptor_lookup: &HashMap<&String, &'a option::Descriptor>) -> Result<(&'a String, option::Value)> {
let option_descriptor = get_option_descriptor_for_name(name, option_descriptor_lookup)?;
Ok((option_descriptor.name(), match option::Value::parse(option_descriptor.value_type(), raw_value) {
Ok(v) => v,
Err(_) => return Err(ParserError {
message: format!("Expected value '{}' of option '--{}' to be of type '{}'", raw_value, name, option_descriptor.value_type())
})
}))
}
fn fill_default_options<'a>(option_value_lookup: &mut HashMap<&'a str, option::Value>, anticipated_options: &'a HashMap<Rc<String>, Rc<option::Descriptor>>) {
for (option_name, descriptor) in anticipated_options {
if !option_value_lookup.contains_key(option_name as &str) {
option_value_lookup.insert(option_name, option::Value::from_default(descriptor.value_type()));
}
}
}
fn parse_arguments(descriptors: &Vec<arg::Descriptor>, raw_arguments: Vec<&str>) -> Result<Vec<arg::Value>> {
if raw_arguments.len() != descriptors.len() {
return Err(ParserError {
message: format!("Expected to have {} arguments but got {}", descriptors.len(), raw_arguments.len())
});
}
let mut argument_values = Vec::with_capacity(raw_arguments.len());
for i in 0..raw_arguments.len() {
let desc = &descriptors[i];
let arg = raw_arguments[i];
let value = match arg::Value::parse(desc.value_type(), arg) {
Ok(v) => v,
Err(_) => return Err(ParserError {
message: format!("Expected argument '{}' at position {} to be of type '{}'", arg, i + 1, desc.value_type())
})
};
argument_values.push(value);
}
Ok(argument_values)
}
fn show_help(group: &Group, option_descriptors: &HashMap<Rc<String>, Rc<option::Descriptor>>, arg_descriptors: &Vec<arg::Descriptor>, help_printer: Option<Box<dyn HelpPrinter>>) {
let mut subcommand_entries = Vec::with_capacity(group.get_children().len());
for (group_name, group) in group.get_children() {
subcommand_entries.push(HelpEntry {
key: group_name,
value: group,
});
}
subcommand_entries.sort_by(|a, b| a.key.cmp(b.key));
let mut option_entries = Vec::with_capacity(option_descriptors.len());
for (option_name, option_descriptor) in option_descriptors {
option_entries.push(HelpEntry {
key: option_name,
value: option_descriptor,
});
}
option_entries.sort_by(|a, b| a.key.cmp(b.key));
match help_printer {
Some(v) => v.print(group, &subcommand_entries, &option_entries, arg_descriptors),
None => DefaultHelpPrinter {}.print(group, &subcommand_entries, &option_entries, arg_descriptors),
}
}