use argdef::{SingleTarget, CollectionTarget, OptionTarget, ArgDef, ArgDefKind, SubCmd};
use help::Help;
use std::collections::{HashMap, HashSet, VecDeque};
use std::borrow::{Cow, Borrow};
use std::rc::Rc;
pub enum TargetRef<'def, 'tar> {
Flag(&'tar mut bool),
Count(&'tar mut usize),
Setting(&'tar mut OptionTarget),
Interrupt(Box<FnMut(Rc<Help<'def>>)>),
Collect(&'tar mut CollectionTarget),
}
pub struct ParseState<'def, 'tar> {
positional: VecDeque<(Cow<'def, str>, &'tar mut SingleTarget)>,
trail: Option<(Cow<'def, str>, bool, &'tar mut CollectionTarget)>,
subcommands: HashMap<Cow<'def, str>, SubCmd<'def>>,
options: HashMap<Cow<'def, str>, TargetRef<'def, 'tar>>,
short_map: HashMap<Cow<'def, str>, Cow<'def, str>>,
}
impl<'def, 'tar> ParseState<'def, 'tar> {
fn get_interned_name(&self, option: &str) -> Cow<'def, str> {
self.options.keys().find(|k| k.as_ref() == option).unwrap().clone()
}
fn get_target<'a>(&'a mut self, option: &str, help: Rc<Help<'def>>)
-> Result<(Cow<'def, str>, &'a mut TargetRef<'def, 'tar>), ParseError<'def>> {
let mut key = &option[2..];
if ! option.starts_with("--") {
if let Some(mapped_key) = self.short_map.get(&option[1..]) {
key = mapped_key.as_ref();
} else {
return ParseError::parse(format!("Unknown option: '{}'", option), help);
}
}
if ! self.options.contains_key(key) {
return ParseError::parse(format!("Unknown option '{}'", option), help);
}
let name = self.get_interned_name(key);
let target = self.options.get_mut(key).unwrap();
Ok((name, target))
}
fn read_option<'arg, I>(&mut self, option: &str, args: &mut I,
given_values: &mut HashSet<Cow<'def, str>>, help: Rc<Help<'def>>)
-> Result<Option<Cow<'def, str>>, ParseError<'def>>
where I: Iterator<Item=&'arg str>
{
use self::TargetRef::*;
match self.get_target(option, help.clone())? {
(_, &mut Flag(ref mut target)) => {
**target = true;
}
(_, &mut Count(ref mut target)) => {
**target += 1;
}
(ref name, &mut Setting(ref mut target)) => {
if given_values.contains(name) {
return ParseError::parse(format!("Option '{}' given twice!", name), help);
}
let arg = if let Some(arg) = args.next() {
arg
} else {
return ParseError::parse(format!("Missing argument for option '{}'", option), help);
};
match target.parse(arg) {
Ok(_) => {}
Err(msg) => return ParseError::parse(msg, help),
};
given_values.insert(name.clone());
}
(_, &mut Collect(ref mut collection_target)) => {
let arg = if let Some(arg) = args.next() {
arg
} else {
return ParseError::parse(format!("Missing argument for option '{}'", option), help);
};
match collection_target.parse_and_add(arg) {
Ok(_) => {}
Err(msg) => return ParseError::parse(msg, help),
};
}
(ref name, &mut Interrupt(ref mut callback)) => {
callback(help);
return Ok(Some(name.clone()));
}
}
Ok(None)
}
}
fn validate_short<'def, N: AsRef<str>>(name: &N) -> Result<(), ParseError<'def>> {
let name = name.as_ref();
if name.starts_with("-") {
ParseError::defs(format!("Invalid short identifier '{}'. Short ids may not start with '-'.", name))
} else {
Ok(())
}
}
fn add_option<'def, 'tar>(
name: Cow<'def, str>,
short: Option<Cow<'def, str>>,
target: TargetRef<'def, 'tar>,
options: &mut HashMap<Cow<'def, str>, TargetRef<'def, 'tar>>,
short_map: &mut HashMap<Cow<'def, str>, Cow<'def, str>>,
) -> Result<(), ParseError<'def>>
{
if let Some(short) = short {
validate_short(&short)?;
if short_map.contains_key(&short) {
return ParseError::defs(format!("Short name '{}' defined twice.", short));
}
short_map.insert(short, name.clone());
}
if options.contains_key(&name) {
return ParseError::defs(format!("Option '{}' defined twice.", name));
}
options.insert(name, target);
Ok(())
}
pub fn parse_definitions<'def, 'tar>(defs: Vec<ArgDef<'def, 'tar>>)
-> Result<ParseState<'def, 'tar>, ParseError<'def>> {
let mut positional = VecDeque::new();
let mut trail = None;
let mut options = HashMap::new(); let mut short_map = HashMap::new(); let mut subcommands = HashMap::new();
let mut has_positional = false;
let mut has_subcommand = false;
for def in defs {
match def.kind {
ArgDefKind::Positional { target } => {
if has_subcommand {
return ParseError::defs(format!("Positional (+trail) and subcommand definitions cannot be used together."));
}
has_positional = true;
positional.push_back((def.name, target));
}
ArgDefKind::Trail { optional, target } => {
if has_subcommand {
return ParseError::defs(format!("Positional (+trail) and subcommand definitions cannot be used together."));
}
has_positional = true;
if trail.is_some() {
return ParseError::defs(format!("Two trails defined."));
}
trail = Some((def.name, optional, target));
}
ArgDefKind::Subcommand { handler } => {
if has_positional {
return ParseError::defs(format!("Positional (+trail) and subcommand definitions cannot be used together."));
}
has_subcommand = true;
if subcommands.contains_key(&def.name) {
return ParseError::defs(format!("Sucommand '{}' defined twice", def.name))
}
subcommands.insert(def.name, handler);
}
ArgDefKind::Flag { short, target } => {
add_option(def.name, short, TargetRef::Flag(target), &mut options, &mut short_map)?;
}
ArgDefKind::Count { short, target } => {
add_option(def.name, short, TargetRef::Count(target), &mut options, &mut short_map)?;
}
ArgDefKind::Collect { short, target, .. } => {
add_option(def.name, short, TargetRef::Collect(target), &mut options, &mut short_map)?;
}
ArgDefKind::Setting { short, target, .. } => {
add_option(def.name, short, TargetRef::Setting(target), &mut options, &mut short_map)?;
}
ArgDefKind::Interrupt { short, callback } => {
add_option(def.name, short, TargetRef::Interrupt(callback), &mut options, &mut short_map)?;
}
}
}
Ok(ParseState { positional, trail, subcommands, options, short_map })
}
#[derive(Debug)]
pub enum ParseError<'def> {
InvalidDefinitions(String),
ParseFailed(String, Rc<Help<'def>>),
SubParseFailed,
Interrupted(Cow<'def, str>)
}
impl<'def> ParseError<'def> {
fn defs<T, S: Into<String>>(reason: S) -> Result<T, ParseError<'def>> {
Err(ParseError::InvalidDefinitions(reason.into()))
}
fn parse<T, S: Into<String>>(reason: S, help: Rc<Help<'def>>) -> Result<T, ParseError<'def>> {
Err(ParseError::ParseFailed(reason.into(), help))
}
fn interrupt<T>(name: Cow<'def, str>) -> Result<T, ParseError<'def>> {
Err( ParseError::Interrupted(name))
}
}
pub fn parse_plain<'def, 'tar, T, P: Into<String>>(program: P, args: &[T], definitions: Vec<ArgDef<'def, 'tar>>)
-> Result<Option<i32>, ParseError<'def>>
where T: Borrow<str>
{
let program = program.into();
let help = Rc::new(Help::new(program.clone(), &definitions));
let mut defs = parse_definitions(definitions)?;
let mut args = args.iter().map(|e| e.borrow());
let mut given_values = HashSet::new();
while let Some(arg) = args.next() {
if arg.starts_with("-") {
if let Some(interrupt) = defs.read_option(arg, &mut args, &mut given_values, help.clone())? {
return ParseError::interrupt(interrupt);
}
} else if ! defs.positional.is_empty() {
let (_name, target) = defs.positional.pop_front().unwrap();
match target.parse(arg) {
Ok(()) => {},
Err(msg) => return ParseError::parse(msg, help),
}
} else if ! defs.subcommands.is_empty() {
if let Some(handler) = defs.subcommands.get_mut(arg) {
let rest = args.collect::<Vec<_>>();
let subprogram = if program != "" {
format!("{} {}", program, arg)
} else {
arg.to_string()
};
return handler(subprogram, &rest);
} else {
return ParseError::parse(format!("Unknown subcommand: '{}'", arg), help);
}
} else {
if let Some((_, ref mut satisfied, ref mut target)) = defs.trail {
match target.parse_and_add(arg) {
Ok(()) => {},
Err(msg) => return ParseError::parse(msg, help),
}; *satisfied = true;
} else {
return ParseError::parse(format!("Unexpected argument '{}'", arg), help);
}
}
}
if let Some((name, _)) = defs.positional.pop_front() {
return ParseError::parse(format!("Missing positional argument '{}'", name), help);
}
if let Some((name, satisfied, _)) = defs.trail {
if ! satisfied {
return ParseError::parse(format!("Expected at least one trailing argument for '{}'", name), help);
}
}
if ! defs.subcommands.is_empty() {
return ParseError::parse(format!("No subcommand specified"), help);
}
Ok(None)
}
pub fn parse<'def, 'tar, T, P: Into<String>>(program: P, args: &[T], definitions: Vec<ArgDef<'def, 'tar>>)
-> Result<Option<i32>, ParseError<'def>>
where T: Borrow<str>
{
match parse_plain(program, args, definitions) {
Err(ParseError::InvalidDefinitions(msg)) => {
panic!("Invalid definitions: {}", msg);
}
Err(ParseError::ParseFailed(msg, help)) => {
println!("Parse failed: {}", msg);
help.print_usage();
Err(ParseError::SubParseFailed)
}
other => other,
}
}