rustop 1.1.2

A simple command line parser.
Documentation
// Copyright (c) 2015-2021 Frank Fischer <frank-fischer@shadow-soft.de>
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see  <http://www.gnu.org/licenses/>

#![allow(clippy::needless_doctest_main)]
//! A simple command line parser.
//!
//! `rustop` is a simple command line parsing crate in the spirit of Ruby's
//! [trollop](https://manageiq.github.io/trollop/). It allows to write command
//! line parameters in a type-safe way with as little effort as possible.
//! `rustop` does not aim to be a full featured command line parser for complex
//! cases but to be a simple, easy to use crate for programs with a simple set
//! of command line options.
//!
//! # Example
//! ```
//! use rustop::opts;
//! fn main() {
//!     let (args, rest) = opts! {
//!         synopsis "This is a simple test program.";          // short info message for the help page
//!         opt verbose:bool, desc:"Be verbose.";               // a flag -v or --verbose
//!         opt luck:bool=true, desc:"We have no luck.";        // a flag -l or --no-luck
//!         opt number_of_lines:usize=1,
//!             desc:"The number of lines.";                    // an option -n or --number-of-lines
//!         param file:Option<String>, desc:"Input file name."; // an optional (positional) parameter
//!     }.parse_or_exit();
//!
//!     if args.verbose {
//!         println!("Start the test program.");
//!     }
//!
//!     if let Some(file) = args.file { println!("Read file: {}", file); }
//!     println!("Number of lines: {}", args.number_of_lines);
//! }
//! ```
//!
//! # Usage
//!
//! The crate exports a macro `opts!` that should be used to define
//! the flags, options and parameters of the program. Usually a single
//! line for each optional is sufficient.
//!
//! Each line within the body of `opts!` specified an option, a
//! parameter or some additional attributed. A line starts with a
//! command followed by a comma-separated list of (keyword) arguments
//! and must be ended with a semicolon. Options and parameters are
//! specified using `opt` and `param` commands:
//!
//! - `opt var:type[=default], desc:"text", [PARAMS...];`
//! - `param var:type[=default], desc:"text", [PARAMS...];`
//!
//! The `opts!` macro generates a struct whose fields correspond
//! exactly to the specified options and parameters. The values of the
//! fields are filled during the parsing process.
//!
//! Actually, the opts! macro returns a `Parser` whose methods should
//! be called to parse the command line, usually `parse()`. This method
//! returns a pair `(args, rest)`, where `args` is the struct of
//! command line arguments and `rest` is a `Vec<...>` of the unparsed
//! command line arguments.
//!
//! The following general commands are supported:
//!
//! - `auto_shorts bool`: Set whether short options should be
//!   generated automatically (using the first non-used letter of the
//!   variable name). The default is `true`.
//! - `stop_on &str`: Specify an additional string to stop parsing
//!   command line parameters. The parser immediately stops parsing
//!   the command line if one of these strings is found. The default
//!   is `"--"`.
//! - `command_name &str`: Set the command name. Usually extracted
//!   from the command line if `parse()` is called.
//! - `synopsis &str`: Set a one-line description of the program to be
//!   shown on the help page. The default is the empty string.
//! - `usage &str`: Set an explicit usage string. If not specified
//!   (the default) an automatic usage string is generated.
//! - `version &str`: Set the program version to be shown on the help
//!   page. The default is the empty string.
//! - `help bool`: Add a -h,--help flag to show a usage message. The
//!   default is `true.
//!
//! # Option and parameters
//!
//! An option is specified by a line of the form
//!
//! ```ignore
//! opt var:type[=default], [PARAMS...];
//! ```
//!
//! The parameters PARAMS are the following `key:value` pairs:
//!
//! - `desc:&str`: Set the description text of this option.
//! - `long:&str`: Set the long option name. If not specified, the
//!    long option corresponds to the variable name (with '_' replaced by
//!    '-').
//! - `short:char`: Set the short option name. If not specified and
//!   `auto_shorts` is `true`, the short option corresponds to the
//!   first unused letter in the variable name.
//! - `name:&str`: Set the name of the argument of this option. This
//!   is shown in the help page as `--argument=NAME`. It is
//!   particularly useful for parameters specified by the `param` command.
//!
//! A parameter is specified like an option but with the `param`
//! instead of the `opt` command. Of course, `long` and `short`
//! attributes cannot be specified for parameters.
//!
//! ## Option types.
//!
//! Options in `rustop` are typed. The command line arguments are
//! parsed and converted to the specified type. However, depending on
//! the option type and the presence of a default value, the parser
//! behaves differently.
//!
//! ### Flags
//!
//! If `type` is `bool` the option is a flag. A flag does not take an
//! additional parameter. Instead, the value of the variable is `true`
//! if the flag is specified on the command line and `false`
//! otherwise.
//!
//! If a default value is specified and set to `true`, the flag
//! becomes a negative flag. The default name of the argument is
//! prepended by "no-" and the variable is set to `true` if the is not
//! specified on the command line and `false` otherwise.
//!
//! ```
//! use rustop::opts;
//! let (args, rest) = opts! {
//!     opt xxx:bool;       // a flag -x or --xxx
//!     opt yyy:bool=true;  // a negative flag -y or --no-yyy
//! }.parse_or_exit();
//! ```
//!
//! ### Required options
//!
//! An option with a plain type and no default value is obligatory: it
//! *must* be specified on the command line, otherwise an error is
//! raised.
//!
//! ```should_panic
//! use rustop::opts;
//! let (args, _) = opts! {
//!     opt xxx:usize;       // an option -x <usize> or --xxx=<usize>
//! }.parse_or_exit();
//! ```
//!
//! ### Options with a default value
//!
//! An option with a default value does not have to be specified. In
//! this case the variable is set to its default value.
//!
//! ```
//! use rustop::opts;
//! let (args, _) = opts! {
//!     opt xxx:usize=42;       // an option -x <usize> or --xxx=<usize>
//! }.parse_or_exit();
//! assert_eq!(args.xxx, 42);
//! ```
//!
//! ### Optional options
//!
//! An option of type `Option<T>` is optional. If it is specified on
//! the command line, the value is set to `Some(...)`, otherwise it is
//! `None`.
//!
//! ```
//! use rustop::opts;
//! let (args, _) = opts! {
//!     opt xxx:Option<usize>;       // an option -x <usize> or --xxx=<usize>
//! }.parse_or_exit();
//! assert!(args.xxx.is_none());
//! ```
//!
//! ### Options with an optional argument
//!
//! An option of type `Option<T>` with a default value is optional and
//! takes an optional argument. If it is specified on the command line
//! without an argument, e.g. `--arg`, the variable is set to
//! `Some(default)`. If it is specified with an argument, e.g.
//! `--arg=42`, the variable is set to `Some(42)`. If the option is
//! not specified at all, the variable is set to `None`.
//!
//! *Note*: there must be a space between the closing '>' and the '='.
//!
//! ```
//! use rustop::opts;
//! let (args, _) = opts! {
//!     opt xxx:Option<i32> = Some(42); // an option -x <usize> or --xxx[=<usize>]
//! }.parse_or_exit();
//! assert!(args.xxx.is_none());
//! ```
//!
//! ### Options with multiple arguments
//!
//! An option of type `Vec<T>` can be specified multiple times. The
//! value of the variable is the vector of all arguments. If, in
//! addition, `multi:true` is set of this option, a single invokation
//! of this option is followed by an arbitrary number of arguments (up
//! to the next option, i.e. a command line argument starting with a
//! dash).
//!
//! Note that without a default value, the option must be specified at
//! least once.
//!
//! This variant is in particular useful for parameters, e.g. if the
//! command takes an arbitrary list of file names.
//!
//! ```
//! use rustop::opts;
//! let (args, _) = opts! {
//!     opt xxx:Vec<usize> = vec![];       // an multi option -x <usize> or --xxx=<usize>
//! }.parse_or_exit();
//! assert!(args.xxx.is_empty());
//! ```
//!
//! # Running the parser
//!
//! The macro `opts!` creates and returns a wrapper around the parser
//! struct. In order to parse some command line arguments, one of
//! three methods of the returned struct must be called.
//!
//! 1. `parse_or_exit`: The most often used method. Runs the parser on
//!    then command line arguments of the program returned by
//!    `std::env::args()` and returns a pair `(args, rest)`. The first
//!    element `args` is the struct of parsed options and the second
//!    `rest` is a vector of unparsed command line arguments. In case
//!    of an error the program is stopped and an appropriate error
//!    message is written to stderr.
//! 2. `parse`: As before but returns `Result<.., Error>`, so in case
//!    of an error the user must handle the error herself.
//! 3. `parse_args`: As `parse` but the command line arguments are not
//!    taken from `std::env::args()` but passed as an iterator of type
//!    `Iterator<Item=&'a str>`. Note that in this case the command name
//!    cannot be extracted from `std::env::args()` so you must specify
//!    the `command_name` attribute.

use std::clone::Clone;
use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt::Write as FmtWrite;
use std::io::{Cursor, Write};
use std::iter::Peekable;
use std::str::FromStr;

use crate::Error::*;
use crate::OptName::*;

///
/// The name of an option or parameter.
///
/// This describes an option as it is shown in the help message or
/// error messages.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum OptName {
    /// A short option.
    Short(char),
    /// A long option.
    Long(String),
    /// Both, a short and a long option.
    Both(char, String),
    /// A parameter.
    Parameter(String),
}

impl std::fmt::Display for OptName {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        match *self {
            Short(ref c) => write!(fmt, "'-{}'", c),
            Long(ref l) => write!(fmt, "'--{}'", l),
            Both(ref c, ref l) => write!(fmt, "'-{}', '--{}'", c, l),
            Parameter(ref n) => write!(fmt, "{}", n),
        }
    }
}

/// An error when parsing an argument of an option or parameter.
#[derive(Debug)]
pub enum ArgError {
    /// Required argument is missing.
    MissingArgument,
    /// An unexpected argument has been found (e.g. for a flag).
    UnexpectedArgument(String),
    /// Error parsing the string to the required argument type.
    ParseError(Box<dyn StdError>),
}

impl std::fmt::Display for ArgError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use crate::ArgError::*;
        match *self {
            ParseError(ref err) => write!(fmt, "error parsing the argument: {}", err),
            UnexpectedArgument(ref arg) => write!(fmt, "unexpected argument: {}", arg),
            MissingArgument => write!(fmt, "missing argument"),
        }
    }
}

impl StdError for ArgError {
    fn cause(&self) -> Option<&dyn StdError> {
        use crate::ArgError::*;
        match *self {
            ParseError(ref err) => Some(err.as_ref()),
            _ => None,
        }
    }
}

/// An error message when parsing command line arguments.
#[derive(Debug)]
pub enum Error {
    /// A duplicated long option has been specified.
    DuplicateLong(String),
    /// A duplicated short option has been specified.
    DuplicateShort(char),
    /// Missing short and long option.
    MissingLongAndShort,

    /// Unknown option.
    Unknown(OptName),
    /// An invalid long option name.
    InvalidLong(String),
    /// An error when parsing an argument.
    InvalidArgument(OptName, ArgError),
    /// A command-line prefix is ambigious.
    AmbiguousPrefix(String, Vec<String>),
    /// A regular option has been specified multiple times.
    Multiple(OptName),
    /// A required option has not been specified.
    MissingOpt(OptName),
    /// A required parameter has not been specified.
    MissingParam(OptName),
    /// The help message should be shown.
    Help(String),
}

impl Error {
    fn invalid_arg<'a>(opt: &'a Opt<'a>, err: ArgError) -> Error {
        Error::InvalidArgument(opt.optname(), err)
    }

    fn invalid_param<'a>(param: &'a Param<'a>, err: ArgError) -> Error {
        Error::InvalidArgument(param.optname(), err)
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        match *self {
            DuplicateLong(ref long) => write!(fmt, "duplicate long option: --{}", long),
            DuplicateShort(ref short) => write!(fmt, "duplicate short option: -{}", short),
            Unknown(ref opt) => write!(fmt, "unknown option: {}", opt),
            Multiple(ref opt) => write!(fmt, "option specified multiple times: {}", opt),
            MissingOpt(ref opt) => write!(fmt, "missing required option: {}", opt),
            MissingParam(ref opt) => write!(fmt, "missing required parameter: {}", opt),
            AmbiguousPrefix(ref long, ref candidates) => {
                write!(fmt, "ambiguous prefix: '--{}' (candidates: ", long)?;
                for (i, cand) in candidates.iter().enumerate() {
                    if i > 0 {
                        write!(fmt, ", ")?
                    }
                    write!(fmt, "'--{}'", cand)?;
                }
                write!(fmt, ")")
            }
            InvalidLong(ref long) => write!(fmt, "invalid long option: {} (only characters and '_' allowed)", long),
            InvalidArgument(ref opt, ref err) => write!(fmt, "invalid argument for {} ({})", opt, err),
            MissingLongAndShort => write!(fmt, "missing long and short option"),
            Help(..) => write!(fmt, "show help page"),
        }
    }
}

/// Show a standard error message and quit current process.
pub fn error_and_exit(err: &Error) -> ! {
    if let Err(e) = writeln!(std::io::stderr(), "Error: {}\nTry --help for help.", err) {
        panic!("{}", e);
    }
    std::process::exit(1);
}

/**
 * Default name for some type.
 *
 * This name is used in the help message to indicate the type of an
 * argument.
 */
pub trait DefaultName {
    fn default_name() -> Option<&'static str> {
        Some("<x>")
    }
}

impl DefaultName for bool {
    fn default_name() -> Option<&'static str> {
        None
    }
}

impl DefaultName for i8 {
    fn default_name() -> Option<&'static str> {
        Some("<i>")
    }
}
impl DefaultName for i16 {
    fn default_name() -> Option<&'static str> {
        Some("<i>")
    }
}
impl DefaultName for i32 {
    fn default_name() -> Option<&'static str> {
        Some("<i>")
    }
}
impl DefaultName for i64 {
    fn default_name() -> Option<&'static str> {
        Some("<i>")
    }
}
impl DefaultName for isize {
    fn default_name() -> Option<&'static str> {
        Some("<i>")
    }
}
impl DefaultName for u8 {
    fn default_name() -> Option<&'static str> {
        Some("<u>")
    }
}
impl DefaultName for u16 {
    fn default_name() -> Option<&'static str> {
        Some("<u>")
    }
}
impl DefaultName for u32 {
    fn default_name() -> Option<&'static str> {
        Some("<u>")
    }
}
impl DefaultName for u64 {
    fn default_name() -> Option<&'static str> {
        Some("<u>")
    }
}
impl DefaultName for usize {
    fn default_name() -> Option<&'static str> {
        Some("<u>")
    }
}
impl DefaultName for f32 {
    fn default_name() -> Option<&'static str> {
        Some("<f>")
    }
}
impl DefaultName for f64 {
    fn default_name() -> Option<&'static str> {
        Some("<f>")
    }
}
impl DefaultName for String {
    fn default_name() -> Option<&'static str> {
        Some("<s>")
    }
}

/// Trait for variables that can be used as option arguments.
pub trait CommandLineArgument {
    /// Add the value from the command line string.
    fn parse_value(&mut self, arg: &str) -> Result<(), ArgError>;

    /// Called if the option has been specified without argument.
    ///
    /// The method should return the error `ArgError::MissingArgument`
    /// if the argument is required.
    fn set_default(&mut self) -> Result<(), ArgError> {
        Err(ArgError::MissingArgument)
    }

    /// Called if the argument has never been seen.
    ///
    /// This method can be used to store a specific "never seen" value
    /// in the variable.
    fn set_unseen(&mut self) {}

    /// Return true if the argument can parsed multiple times.
    fn is_multi(&self) -> bool {
        false
    }

    /// Return the initial value as a string.
    fn default_value(&self) -> Option<String> {
        None
    }

    /// Return a description of a argument of this type.
    ///
    /// `name` is a use-defined description (if any) of the underlying
    /// type. The function may add additional formatting.
    fn write_name(&self, name: Option<&str>) -> Option<String>;
}

/// A flag.
///
/// A flag must not take an argument but always uses its default
/// value.
impl<'a> CommandLineArgument for &'a mut bool {
    fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
        Err(ArgError::UnexpectedArgument(arg.to_string()))
    }

    fn set_default(&mut self) -> Result<(), ArgError> {
        **self = !**self;
        Ok(())
    }

    fn write_name(&self, _name: Option<&str>) -> Option<String> {
        None
    }
}

/// A single argument option.
pub struct Single<T>(pub T);

impl<'a, T> CommandLineArgument for Single<&'a mut T>
where
    T: FromStr + DefaultName + std::fmt::Display,
    <T as FromStr>::Err: StdError + 'static,
{
    fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
        *self.0 = arg
            .parse()
            .map_err(|err| Box::new(err) as Box<dyn StdError>)
            .map_err(ArgError::ParseError)?;
        Ok(())
    }

    fn default_value(&self) -> Option<String> {
        Some(format!("{}", self.0))
    }

    fn write_name(&self, name: Option<&str>) -> Option<String> {
        #[allow(clippy::redundant_closure)]
        name.or_else(|| T::default_name()).map(|n| n.to_string())
    }
}

/// An optional argument option.
impl<'a, T> CommandLineArgument for &'a mut Option<T>
where
    T: FromStr + DefaultName + std::fmt::Display,
    <T as FromStr>::Err: StdError + 'static,
{
    fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
        **self = arg
            .parse()
            .map(Some)
            .map_err(|err| Box::new(err) as Box<dyn StdError>)
            .map_err(ArgError::ParseError)?;
        Ok(())
    }

    fn set_default(&mut self) -> Result<(), ArgError> {
        // only optional options with default value (!= None) can be
        // used without the argument
        if self.is_none() {
            Err(ArgError::MissingArgument)
        } else {
            Ok(())
        }
    }

    fn set_unseen(&mut self) {
        **self = None
    }

    fn default_value(&self) -> Option<String> {
        self.as_ref().map(|x| format!("{}", x))
    }

    fn write_name(&self, name: Option<&str>) -> Option<String> {
        #[allow(clippy::redundant_closure)]
        name.or_else(|| T::default_name()).map(|n| format!("[{}]", n))
    }
}

/// A multi argument option.
///
/// The first argument is a collection, the second a flag whether this
/// argument has been seen before.
pub struct Multi<T>(pub T, pub bool);

impl<'a, T> CommandLineArgument for Multi<&'a mut Vec<T>>
where
    T: FromStr + DefaultName + std::fmt::Debug,
    <T as FromStr>::Err: StdError + 'static,
{
    fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
        if !self.1 {
            self.0.clear();
            self.1 = true;
        }
        self.0.push(
            arg.parse()
                .map_err(|err| Box::new(err) as Box<dyn StdError>)
                .map_err(ArgError::ParseError)?,
        );
        Ok(())
    }

    fn is_multi(&self) -> bool {
        true
    }

    fn default_value(&self) -> Option<String> {
        Some(format!("{:?}", self.0))
    }

    fn write_name(&self, name: Option<&str>) -> Option<String> {
        #[allow(clippy::redundant_closure)]
        name.or_else(|| T::default_name()).map(|n| format!("[{}...]", n))
    }
}

/// A command line parameter.
#[allow(dead_code)]
pub struct Param<'a> {
    /// Associated variable.
    var: Box<dyn CommandLineArgument + 'a>,
    /// Description text of the parameter.
    description: Option<&'a str>,
    /// Descriptive name of the argument.
    name: Option<String>,
    /// Whether this parameter is required.
    required: bool,
    /// The default value of this option as string (for help message).
    default: Option<String>,

    /// Whether the parameter has been seen.
    seen: bool,
}

impl<'a> Param<'a> {
    fn optname(&self) -> OptName {
        let name = self
            .var
            .write_name(self.name.as_ref().map(|n| &n[..]))
            .unwrap_or_else(|| "PARAM".to_string());
        OptName::Parameter(name)
    }
}

/// A command line option.
#[allow(dead_code)]
pub struct Opt<'a> {
    /// Associated variable.
    var: Box<dyn CommandLineArgument + 'a>,
    /// Description text of the option.
    description: Option<&'a str>,
    /// Descriptive name of the argument (not used if flag).
    name: Option<String>,
    /// Whether this option is required.
    required: bool,
    /// The default value of this option as string (for help message).
    default: Option<String>,
    /// The long option.
    long: Option<String>,
    /// The short option.
    short: Option<char>,
    /// True if this is a multi option.
    multi: bool,

    /// Whether the parameter has been seen.
    seen: bool,
}

impl<'a> Opt<'a> {
    fn optname(&self) -> OptName {
        match (self.short, self.long.as_ref()) {
            (Some(short), Some(long)) => OptName::Both(short, long.to_string()),
            (None, Some(long)) => OptName::Long(long.to_string()),
            (Some(short), None) => OptName::Short(short),
            (None, None) => panic!("Neither long nor short option name"),
        }
    }
}

/// The command line parser.
pub struct Parser<'a> {
    /// The options.
    opts: Vec<Opt<'a>>,
    /// The parameters.
    params: Vec<Param<'a>>,
    /// Strings at which parsing should be stopped.
    stopons: Vec<&'a str>,
    /// Whether automatic short options should be generated.
    auto_shorts: bool,

    /// The name of the command.
    command_name: Option<&'a str>,
    /// Usage string.
    usage: Option<&'a str>,
    /// Synopsis string.
    synopsis: Option<&'a str>,
    /// Version string.
    version: Option<&'a str>,
    /// Add -h,--help option to show usage.
    help: bool,
}

#[allow(dead_code)]
pub fn clean_long(long: &str, negate: bool) -> Result<String, Error> {
    let longstr: String = long.chars().map(|c| if c == '_' { '-' } else { c }).collect();

    if longstr.chars().all(|c| c == '-' || c.is_alphanumeric()) {
        if !negate {
            Ok(longstr)
        } else {
            Ok(format!("no-{}", longstr))
        }
    } else {
        Err(InvalidLong(long.to_string()))
    }
}

impl<'a> Default for Parser<'a> {
    fn default() -> Self {
        Parser::new()
    }
}

impl<'a> Parser<'a> {
    /// Return a new parser.
    pub fn new() -> Parser<'a> {
        Parser {
            opts: vec![],
            params: vec![],
            stopons: vec!["--"],
            auto_shorts: true,
            command_name: None,
            usage: None,
            synopsis: None,
            version: None,
            help: true,
        }
    }

    /**
     * Add and return an option.
     *
     * - `var` is the associated variable
     */
    pub fn opt<T>(&mut self, var: T) -> &mut Opt<'a>
    where
        T: 'a + CommandLineArgument,
    {
        let default = var.default_value();
        self.opts.push(Opt {
            var: Box::new(var),
            description: None,
            name: None,
            required: false,
            default,
            long: None,
            short: None,
            multi: false,
            seen: false,
        });
        let n = self.opts.len();
        &mut self.opts[n - 1]
    }

    /**
     * Add and return a parameter.
     *
     * - `var` is the associated variable
     */
    pub fn param<T>(&mut self, var: T) -> &mut Param<'a>
    where
        T: 'a + CommandLineArgument,
    {
        let default = var.default_value();
        self.params.push(Param {
            var: Box::new(var),
            description: None,
            name: None,
            default,
            required: false,
            seen: false,
        });
        let n = self.params.len();
        &mut self.params[n - 1]
    }

    /// Set whether short options should be generated automatically.
    pub fn auto_shorts(&mut self, auto_shorts: bool) {
        self.auto_shorts = auto_shorts;
    }

    /// Specify an additional stop string.
    pub fn stop_on(&mut self, stopon: &'a str) {
        self.stopons.push(stopon);
    }

    /// Set the command name.
    pub fn command_name(&mut self, command_name: &'a str) {
        self.command_name = Some(command_name);
    }

    /// Set the usage string.
    pub fn usage(&mut self, usage: &'a str) {
        self.usage = Some(usage);
    }

    /// Set the synopsis string.
    pub fn synopsis(&mut self, synopsis: &'a str) {
        self.synopsis = Some(synopsis);
    }

    /// Set the version string.
    pub fn version(&mut self, version: &'a str) {
        self.version = Some(version);
    }

    /// Add help option.
    pub fn help(&mut self, help: bool) {
        self.help = help;
    }

    /**
     * Parse vector of command line arguments.
     *
     * Returns a vector of remaining (unparsed) arguments.
     */
    pub fn parse<'b, I>(&mut self, args: I) -> Result<Vec<&'b str>, Error>
    where
        I: IntoIterator<Item = &'b str>,
    {
        // Reset seen.
        for opt in &mut self.opts {
            opt.seen = false;
        }
        for param in &mut self.params {
            param.seen = false;
        }

        // prepare short and long options for binary search
        let (shortopts, longopts) = self.prepare_options()?;

        let mut rest = vec![];
        let mut paramidx = 0;
        let mut args = args.into_iter().peekable();

        while let Some(arg) = args.next() {
            if self.stopons.contains(&arg) {
                // stop parsing arguments
                break;
            } else if arg.len() > 2 && arg.starts_with("--") {
                self.parse_long(&arg[2..], &mut args, &longopts)?;
            } else if arg.len() > 1 && arg.starts_with("-") {
                self.parse_short(&arg[1..], &mut args, &shortopts)?;
            } else if paramidx < self.params.len() {
                // parse a positional parameter
                let param = &mut self.params[paramidx];
                param
                    .var
                    .parse_value(arg)
                    .map_err(|err| Error::invalid_param(param, err))?;
                param.seen = true;
                if !param.var.is_multi() {
                    paramidx += 1;
                }
            } else {
                // unparsed parameter
                rest.push(arg);
            }
        }

        // test whether all required options and parameters have been specified
        for opt in &mut self.opts {
            if !opt.seen {
                if opt.required {
                    return Err(MissingOpt(opt.optname()));
                }
                opt.var.set_unseen();
            }
        }
        for par in &mut self.params {
            if !par.seen {
                if par.required {
                    return Err(MissingOpt(par.optname()));
                }
                par.var.set_unseen();
            }
        }

        // add remaining unparsed arguments
        rest.extend(args);
        Ok(rest)
    }

    /// Return ordered lists of options and parameters.
    ///
    /// Furthermore, unspecified long and shorts options are computed.
    fn prepare_options(&mut self) -> Result<(Vec<usize>, Vec<usize>), Error> {
        // Check if each option has at least a short or a long option.
        if self.opts.iter().any(|opt| opt.short.is_none() && opt.long.is_none()) {
            return Err(Error::MissingLongAndShort);
        }

        // collect all existing short options in a set
        let mut shorts: HashSet<_> = self.opts.iter().filter_map(|opt| opt.short).collect();

        // Auto help eats 'h'
        if self.help {
            if shorts.contains(&'h') {
                return Err(Error::DuplicateShort('h'));
            }
            if self
                .opts
                .iter()
                .any(|opt| opt.long.as_ref().map(|s| &s[..]) == Some("help"))
            {
                return Err(Error::DuplicateLong("help".to_string()));
            }
            shorts.insert('h');
        }

        // extract automatic short options (if possible)
        if self.auto_shorts {
            for opt in self.opts.iter_mut().filter(|opt| opt.short.is_none()) {
                // extract automatic short options
                if let Some(ref long) = opt.long {
                    if let Some(short) = long.chars().find(|c| c.is_alphanumeric() && !shorts.contains(c)) {
                        shorts.insert(short);
                        opt.short = Some(short)
                    }
                }
            }
        }

        // check whether long/shorts are unique
        let mut longopts: Vec<_> = self
            .opts
            .iter()
            .enumerate()
            .filter_map(|(i, opt)| opt.long.as_ref().map(|_| i))
            .collect();
        longopts.sort_by_key(|&i| self.opts[i].long.as_ref());
        if let Some((&i, _)) = longopts
            .iter()
            .zip(longopts.iter().skip(1))
            .find(|&(&i, &j)| self.opts[i].long == self.opts[j].long)
        {
            return Err(Error::DuplicateLong(self.opts[i].long.clone().unwrap()));
        }

        let mut shortopts: Vec<_> = self
            .opts
            .iter()
            .enumerate()
            .filter_map(|(i, opt)| opt.short.map(|_| i))
            .collect();
        shortopts.sort_by_key(|&i| self.opts[i].short);
        if let Some((&i, _)) = shortopts
            .iter()
            .zip(shortopts.iter().skip(1))
            .find(|&(&i, &j)| self.opts[i].short == self.opts[j].short)
        {
            return Err(Error::DuplicateShort(self.opts[i].short.unwrap()));
        }

        Ok((shortopts, longopts))
    }

    /// Parses a long option in `arg`.
    fn parse_long<'b, I>(&mut self, arg: &str, args: &mut Peekable<I>, longopts: &[usize]) -> Result<(), Error>
    where
        I: Iterator<Item = &'b str>,
    {
        // first find possible '='
        let end = arg.chars().position(|ch| ch == '=');

        // find matching long option
        let optidx = self.find_long(&arg[..end.unwrap_or_else(|| arg.len())], longopts)?;
        let opt = &mut self.opts[optidx];
        opt.seen = true;

        // parse argument
        if let Some(end) = end {
            // we must parse the argument
            opt.var
                .parse_value(&arg[end + 1..])
                .map_err(|err| Error::invalid_arg(opt, err))?;
        } else if !opt.multi {
            // we may parse the argument from the following command line parameter
            opt.var
                .set_default()
                .or_else(|err| {
                    // we must use the following parameter
                    args.next().ok_or(err).and_then(|arg| {
                        // ok, there *is* a following parameter -> parse it
                        opt.var.parse_value(arg)
                    })
                })
                .map_err(|err| Error::invalid_arg(opt, err))?;
        } else {
            // this is a multi option, we parse the following
            // command line parameters until we find a stop
            // argument or another option.
            #[allow(clippy::while_let_loop)]
            loop {
                if let Some(arg) = args.peek() {
                    if self.stopons.contains(arg) || (arg.len() > 1 && arg.starts_with("-")) {
                        break;
                    }
                    if let Err(err) = opt.var.parse_value(arg) {
                        return Err(Error::invalid_arg(opt, err));
                    }
                } else {
                    break;
                }
                args.next();
            }
        }

        Ok(())
    }

    fn find_long(&self, long: &str, longopts: &[usize]) -> Result<usize, Error> {
        if let Ok(idx) = longopts.binary_search_by(|i| self.opts[*i].long.as_ref().unwrap()[..].cmp(long)) {
            Ok(longopts[idx])
        } else {
            let mut idx = None;
            for &j in longopts {
                if self.opts[j].long.as_ref().unwrap().starts_with(long) {
                    if idx.is_none() {
                        idx = Some(j);
                    } else {
                        let candidates = longopts.iter().filter_map(|&j| {
                            let l = self.opts[j].long.as_ref().unwrap();
                            if l.starts_with(long) {
                                Some(l.to_string())
                            } else {
                                None
                            }
                        });
                        return Err(AmbiguousPrefix(long.to_string(), candidates.collect()));
                    }
                }
            }
            idx.ok_or_else(|| {
                if self.help && "help".starts_with(long) {
                    let mut helpmsg = Cursor::new(Vec::new());
                    self.write_usage(&mut helpmsg).unwrap();
                    Help(String::from_utf8(helpmsg.into_inner()).unwrap())
                } else {
                    Unknown(Long(long.to_string()))
                }
            })
        }
        .and_then(|idx| {
            let opt = &self.opts[idx];
            if opt.seen && (!opt.var.is_multi() || opt.multi) {
                Err(Error::Multiple(opt.optname()))
            } else {
                Ok(idx)
            }
        })
    }

    /// Parses one or several short options in `arg`.
    fn parse_short<'b, I>(&mut self, arg: &str, args: &mut Peekable<I>, shortopts: &[usize]) -> Result<(), Error>
    where
        I: Iterator<Item = &'b str>,
    {
        // parse a short option
        let mut chars = arg.chars();
        while let Some(short) = chars.next() {
            let optidx = self.find_short(short, shortopts)?;
            let opt = &mut self.opts[optidx];
            opt.seen = true;

            if let Err(err) = opt.var.set_default() {
                // option has an obligatory argument (in particular, it is not a flag)
                // check if there are remaining characters in this argument
                let arg = chars.as_str();
                if !arg.is_empty() {
                    if let Err(err) = opt.var.parse_value(arg) {
                        return Err(Error::invalid_arg(opt, err));
                    }
                    if !opt.multi {
                        return Ok(());
                    }
                } else if !opt.multi && args.peek().is_none() {
                    return Err(Error::invalid_arg(opt, err));
                }

                // parse arguments from the next command line parameters
                loop {
                    if let Some(arg) = args.peek() {
                        if self.stopons.contains(arg) || (arg.len() > 1 && arg.starts_with("-")) {
                            break;
                        }
                        if let Err(err) = opt.var.parse_value(arg) {
                            return Err(Error::invalid_arg(opt, err));
                        }
                    } else {
                        break;
                    }
                    args.next();
                    if !opt.multi {
                        break;
                    }
                }

                // done collecting argument, so this option is done, too
                return Ok(());
            }
        }

        Ok(())
    }

    fn find_short(&self, short: char, shortopts: &[usize]) -> Result<usize, Error> {
        shortopts
            .binary_search_by(|i| self.opts[*i].short.unwrap().cmp(&short))
            .map_err(|_| {
                if self.help && short == 'h' {
                    let mut helpmsg = Cursor::new(Vec::new());
                    self.write_usage(&mut helpmsg).unwrap();
                    Help(String::from_utf8(helpmsg.into_inner()).unwrap())
                } else {
                    Unknown(Short(short))
                }
            })
            .and_then(|idx| {
                let idx = shortopts[idx];
                if self.opts[idx].seen && !self.opts[idx].var.is_multi() {
                    Err(Error::Multiple(self.opts[idx].optname()))
                } else {
                    Ok(idx)
                }
            })
    }

    /// Write the usage string to some output stream.
    pub fn write_usage(&self, output: &mut dyn std::io::Write) -> Result<(), std::io::Error> {
        if let Some(u) = self.usage {
            if !u.is_empty() {
                writeln!(output, "Usage: {} {}\n", self.command_name.unwrap_or("COMMAND"), u)?;
            }
        } else {
            // use automatic usage string
            write!(output, "Usage: {}", self.command_name.unwrap_or("COMMAND"))?;
            if !self.opts.is_empty() {
                write!(output, " [OPTIONS]")?;
            }
            for param in &self.params {
                write!(output, " {}", param.optname())?;
            }
            writeln!(output, "\n")?;
        }

        if let Some(s) = self.synopsis {
            writeln!(output, "{}\n", s)?;
        }
        if let Some(v) = self.version {
            writeln!(output, "{}\n", v)?;
        }

        let mut arglefts = vec![];
        let mut argrights = vec![];
        for param in &self.params {
            arglefts.push(param.optname().to_string());
            let mut right = param.description.unwrap_or("").to_string();
            if !param.required {
                if let Some(ref default) = param.default {
                    write!(right, " (default: {})", default).unwrap();
                }
            }
            argrights.push(right);
        }

        let mut lefts = vec![];
        let mut rights = vec![];

        for opt in &self.opts {
            let mut left = "".to_string();
            if let Some(short) = opt.short {
                left.push('-');
                left.push(short);
                if let Some(ref name) = opt.name {
                    left.push(' ');
                    left.push_str(name);
                }
            }
            if let Some(ref long) = opt.long {
                if opt.short.is_some() {
                    left.push_str(", ");
                }
                left.push_str("--");
                left.push_str(long);
                if let Some(ref name) = opt.name {
                    left.push('=');
                    left.push_str(name);
                }
            }

            let mut right = "".to_string();
            if let Some(d) = opt.description {
                right.push_str(d);
                if !opt.required {
                    if let Some(ref default) = opt.default {
                        write!(right, " (default: {})", default).unwrap();
                    }
                }
            }

            lefts.push(left);
            rights.push(right);
        }

        if self.help {
            lefts.push("-h, --help".to_string());
            rights.push("Show this help message.".to_string());
        }

        let width = lefts
            .iter()
            .chain(arglefts.iter())
            .map(|l| l.chars().count())
            .max()
            .unwrap();

        if !arglefts.is_empty() {
            writeln!(output, "Parameters:")?;
            for i in 0..arglefts.len() {
                writeln!(
                    output,
                    "  {}{:3$}    {}",
                    arglefts[i],
                    "",
                    argrights[i],
                    width - arglefts[i].chars().count()
                )?;
            }
            writeln!(output)?;
        }

        if !lefts.is_empty() {
            writeln!(output, "Options:")?;
            for i in 0..lefts.len() {
                writeln!(
                    output,
                    "  {}{:3$}    {}",
                    lefts[i],
                    "",
                    rights[i],
                    width - lefts[i].chars().count()
                )?;
            }
        }

        Ok(())
    }
}

impl<'a> Opt<'a> {
    /// Set the description string of this option.
    pub fn desc<'b: 'a>(&mut self, desc: &'b str) -> &mut Self {
        self.description = Some(desc);
        self
    }

    /// Set long or short name from the given variable name.
    #[allow(dead_code)]
    pub fn varname(&mut self, varname: &str) -> &mut Self {
        assert!(!varname.is_empty());
        assert!(varname.chars().all(|c| c.is_alphanumeric() || c == '-'));

        if varname.len() > 1 {
            self.long = Some(varname.to_string());
        } else {
            self.short = varname.chars().next();
        }

        self
    }

    /// Mark this option as required.
    #[allow(dead_code)]
    pub fn required(&mut self, required: bool) -> &mut Self {
        self.required = required;
        self
    }

    /// Set the long option of this option.
    pub fn long(&mut self, long: &str) -> &mut Self {
        assert!(!long.is_empty(), "Long option must not be empty");
        assert!(
            long.chars().all(|c| c.is_alphanumeric() || c == '-'),
            "Long options consist of alpha-numeric characters and '-' only (got: --{})",
            long
        );

        self.long = Some(long.to_string());
        self
    }

    /// Set the short option of this option.
    pub fn short(&mut self, short: char) -> &mut Self {
        assert!(
            short.is_alphanumeric(),
            "Short options must be alpha-numeric (got: -{})",
            short
        );

        self.short = Some(short);
        self
    }

    /// Set the name of the option's argument.
    pub fn name(&mut self, name: &str) -> &mut Self {
        self.name = Some(name.to_string());
        self
    }

    /// Set whether this is a multi-argument option.
    pub fn multi(&mut self, multi: bool) -> &mut Self {
        self.multi = multi;
        self
    }
}

impl<'a> Param<'a> {
    /// Set the description of this parameter.
    pub fn desc<'b: 'a>(&mut self, desc: &'b str) -> &mut Self {
        self.description = Some(desc);
        self
    }

    /// Set the (short) name of this parameter.
    pub fn name(&mut self, name: &str) -> &mut Self {
        self.name = Some(name.to_string());
        self
    }

    /// Mark this option as required.
    #[allow(dead_code)]
    pub fn required(&mut self, required: bool) -> &mut Self {
        self.required = required;
        self
    }
}

#[macro_export]
macro_rules! _opts {
    // flag options
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : bool $(,$par:ident : $what:expr)*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
                       ($($_opts)* [Flag, bool, $var : bool = false, $($par : $what),*])
                       ($($_params)*)
                       $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : bool = false;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
                       ($($_opts)* [Flag, bool, $var : bool = false,])
                       ($($_params)*)
                       $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : bool = false, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
                       ($($_opts)* [Flag, bool, $var : bool = false, $($par : $what),*])
                       ($($_params)*)
                       $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : bool = true;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
                       ($($_opts)* [NegFlag, bool, $var : bool = true,])
                       ($($_params)*)
                       $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : bool = true, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [NegFlag, bool, $var : bool = true, $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    // multi options
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Vec<$typ:ty> $(,$par:ident : $what:expr)*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Multi, $typ, $var : Vec<$typ> = vec![], $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Vec<$typ:ty> = $val:expr;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Multi, $typ, $var : Vec<$typ> = $val,])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Vec<$typ:ty> = $val:expr, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Multi, $typ, $var : Vec<$typ> = $val, $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    // optional options
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Option<$typ:ty> $(,$par:ident : $what:expr)*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Optional, $typ, $var : Option<$typ> = None, $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Option<$typ:ty> = $val:expr;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Optional, $typ, $var : Option<$typ> = $val,])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : Option<$typ:ty> = $val:expr, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Optional, $typ, $var : Option<$typ> = $val, $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    // required options
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : $typ:ty;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(),])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : $typ:ty, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : $typ:ty = $val:expr;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Single, $typ, $var : $typ = $val,])
               ($($_params)*)
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      opt $var:ident : $typ:ty = $val:expr, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)* [Single, $typ, $var : $typ = $val, $($par : $what),*])
               ($($_params)*)
               $($rest)*)
    };

    // parameters
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : Vec<$typ:ty> $(,$par:ident : $what:expr)*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [Multi, $typ, $var : Vec<$typ> = vec![], $($par : $what),*])
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : Option<$typ:ty> $(,$par:ident : $what:expr)*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [Optional, $typ, $var : Option<$typ> = None, $($par : $what),*])
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : $typ:ty;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(),])
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : $typ:ty, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : $typ:ty = $val:expr;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [Single, $typ, $var : $typ = $val,])
               $($rest)*)
    };

    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      param $var:ident : $typ:ty = $val:expr, $($par:ident : $what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)*)
               ($($_opts)*)
               ($($_params)* [Single, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
               $($rest)*)
    };

    // parser settings
    ( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
      $which:ident $($what:expr),*;
      $($rest:tt)* ) =>
    {
        $crate::_opts!(($($_sets)* [$which, $($what),*])
               ($($_opts)*)
               ($($_params)*)
               $($rest)*)
    };

    // wrapper types and values
    ( NegFlag , $var:expr )        => {&mut $var};
    ( Flag , $var:expr )           => {&mut $var};
    ( Optional, $var:expr )        => {&mut $var};
    ( Multi, $var:expr )           => {$crate::Multi(&mut $var, false)};
    ( SingleRequired, $var:expr )  => {$crate::Single(&mut $var)};
    ( Single, $var:expr )          => {$crate::Single(&mut $var)};

    ( Required, SingleRequired ) => { true };
    ( Required, $kind:ident )    => { false };

    ( Negate, NegFlag )     => { true };
    ( Negate, $kind:ident ) => { false };

    // code
    ( ($([$_sname:ident, $($_sarg:expr),*])*)
      ($([$_okind:ident, $_oelmtyp:ty, $_ovar:ident : $_otyp:ty = $_oval:expr, $($_opar:ident : $_owhat:expr),*])*)
      ($([$_pkind:ident, $_pelmtyp:ty, $_pvar:ident : $_ptyp:ty = $_pval:expr, $($_ppar:ident : $_pwhat:expr),*])*) ) => {{
        struct Args {
            $($_ovar : $_otyp,)*
            $($_pvar : $_ptyp,)*
        }
        struct Parser;
        impl Parser {
            #[allow(dead_code)]
            fn parse_or_exit(&self) -> (Args, std::vec::Vec<std::string::String>) {
                match self.parse() {
                    Ok(result) => result,
                    Err($crate::Error::Help(msg)) => {
                        eprintln!("{}", msg);
                        std::process::exit(1);
                    },
                    Err(err) => $crate::error_and_exit(&err),
                }
            }

            #[allow(dead_code)]
            fn parse(&self) -> std::result::Result<(Args, std::vec::Vec<std::string::String>), $crate::Error> {
                let mut it = std::env::args();
                let command_name = match it.next() {
                    Some(path) => std::path::Path::new(&path)
                        .file_name()
                        .and_then(|f| f.to_str())
                        .and_then(|f| Some(f.to_string())),
                    None => None
                };
                let args: Vec<_> = it.collect();
                self._parse_args(args.iter().map(|s| s.as_str()), command_name.as_ref().map(|s| s.as_str()))
                    .map(|(args, rest)| (args, rest.into_iter().map(|s| s.to_string()).collect()))
            }

            #[allow(dead_code)]
            fn parse_args<'a, I>(&self, args: I)
                                 -> std::result::Result<(Args, std::vec::Vec<&'a str>), $crate::Error>
                where I: IntoIterator<Item=&'a str>
            {
                self._parse_args(args, None)
            }

            #[allow(dead_code)]
            #[allow(unused_must_use)]
            fn _parse_args<'a, I>(&self, args: I, command_name: std::option::Option<&'a str>)
                                  -> std::result::Result<(Args, std::vec::Vec<&'a str>), $crate::Error>
                where I: IntoIterator<Item=&'a str>
            {
                let mut data = Args {
                    $($_ovar : $_oval,)*
                    $($_pvar : $_pval,)*
                };
                let rest;
                {
                    let mut parser = $crate::Parser::new();
                    if let Some(c) = command_name {
                        parser.command_name(c);
                    }

                    $(parser.$_sname($($_sarg),*);)*
                    $(parser.opt($crate::_opts!($_okind, data.$_ovar))
                      .varname(&$crate::clean_long(stringify!($_ovar), $crate::_opts!(Negate, $_okind))?)
                      .required($crate::_opts!(Required, $_okind))
                      $(.$_opar($_owhat))*;)*
                    $(parser.param($crate::_opts!($_pkind, data.$_pvar))
                      .name(&stringify!($_pvar).to_uppercase())
                      .required($crate::_opts!(Required, $_pkind))
                      $(.$_ppar($_pwhat))*;)*
                    rest = parser.parse(args);
                }
                match rest {
                    Ok(rest) => Ok((data, rest)),
                    Err(err) => Err(err)
                }
            }
        }
        Parser
    }};
}

#[macro_export]
macro_rules! opts {
    ( $($rest:tt)* ) => {
        $crate::_opts!(() () () $($rest)*)
    }
}

#[test]
fn test_short_flag() {
    let (args, rest) = opts! {
        opt r:bool;
        opt g:bool, short:'g';
        opt l:bool, short:'l';
        opt a:bool=true, short:'a';
        opt b:bool=true, short:'b';
    }
    .parse_args(vec!["-r", "-g", "-a", "PARAM"])
    .unwrap();

    assert_eq!(args.r, true);
    assert_eq!(args.g, true);
    assert_eq!(args.l, false);
    assert_eq!(args.a, false);
    assert_eq!(args.b, true);
    assert_eq!(rest, vec!["PARAM"]);
}

#[test]
fn test_long_flag() {
    let (args, rest) = opts! {
        opt long_flag:bool;
        opt g:bool, long:"global";
        opt l:bool, long:"long";
    }
    .parse_args(vec!["--global", "--long-flag", "ARG1", "ARG2"])
    .unwrap();

    assert!(args.long_flag);
    assert!(args.g);
    assert!(!args.l);
    assert_eq!(rest, vec!["ARG1", "ARG2"]);
}

#[test]
fn test_long_opt() {
    let (args, _) = opts! {
        opt long:i32;
        opt long2:i32;
    }
    .parse_args(vec!["--long", "42", "--long2=23"])
    .unwrap();
    assert_eq!(args.long, 42);
    assert_eq!(args.long2, 23);
}

#[test]
#[should_panic]
fn test_long_opt_missing() {
    let (_, _) = opts! {
        opt long:i32;
    }
    .parse_args(vec!["--long"])
    .unwrap();
}

#[test]
#[should_panic]
fn test_unknown_short() {
    let (_, _) = opts! {
        opt g:bool, long:"global";
        opt l:bool, long:"long";
    }
    .parse_args(vec!["-g", "-u"])
    .unwrap();
}

#[test]
#[should_panic]
fn test_unknown_long() {
    let (_, _) = opts! {
        opt g:bool, long:"global";
        opt l:bool, long:"long";
    }
    .parse_args(vec!["--unknown"])
    .unwrap();
}

#[test]
fn test_multi_param() {
    let (args, rest) = opts! {
        param arg1:String, desc:"BLA";
        param arg2:Vec<String>, desc:"BLA";
    }
    .parse_args(vec!["ARG1", "ARG2", "ARG3", "--", "REST"])
    .unwrap();
    assert_eq!(args.arg1, "ARG1");
    assert_eq!(args.arg2, vec!["ARG2".to_string(), "ARG3".to_string()]);
    assert_eq!(rest, vec!["REST".to_string()]);
}

#[test]
fn test_multi_opts() {
    let (args, rest) = opts! {
        opt arg1:String;
        opt arg2:Vec<String>, multi:true;
        opt arg3:Vec<String>;
        opt arg4:Vec<i32> = vec![42];
        opt arg5:Vec<i32> = vec![1,2,3];
    }
    .parse_args(vec![
        "--arg3",
        "aaa",
        "--arg2",
        "ARG1",
        "ARG2",
        "--arg1",
        "ARG3",
        "--arg3=bbb",
        "--arg5=7",
        "--",
        "REST",
    ])
    .unwrap();
    assert_eq!(args.arg1, "ARG3");
    assert_eq!(args.arg2, vec!["ARG1".to_string(), "ARG2".to_string()]);
    assert_eq!(args.arg3, vec!["aaa".to_string(), "bbb".to_string()]);
    assert_eq!(args.arg4, vec![42]);
    assert_eq!(args.arg5, vec![7]);
    assert_eq!(rest, vec!["REST".to_string()]);
}

#[test]
#[should_panic]
fn test_multi_opts_multiple() {
    let (_, _) = opts! {
        opt arg1:Vec<String>, multi:true;
    }
    .parse_args(vec!["--arg1", "a", "b", "--arg1", "x", "y", "--", "REST"])
    .unwrap();
}

#[test]
#[should_panic]
fn test_missing_args() {
    let (_, _) = opts! {
        param arg1:String;
        param arg2:String;
    }
    .parse_args(vec!["ARG1"])
    .unwrap();
}

#[test]
fn test_prefix() {
    let (args, _) = opts! {
        opt l:bool, long:"a-very-long-option";
    }
    .parse_args(vec!["--a-ver"])
    .unwrap();
    assert_eq!(args.l, true);
}

#[test]
fn test_ambiguous_prefix() {
    let r = opts! {
        opt l:bool, long:"a-very-long-option";
        opt l2:bool, long:"a-very-long-option2";
    }
    .parse_args(vec!["--a-ver"]);
    match r {
        Err(AmbiguousPrefix(_, _)) => (),
        _ => panic!("Failed"),
    }
}

#[test]
fn test_exact_prefix() {
    let (args, _) = opts! {
        opt a_very_long_option:bool;
        opt l2:bool, long:"a-very-long-option2";
    }
    .parse_args(vec!["--a-very-long-option"])
    .unwrap();
    assert_eq!(args.a_very_long_option, true);
    assert_eq!(args.l2, false);
}

#[test]
fn test_optional_nodefault() {
    let (args, _) = opts! {
        opt a:Option<String>;
        opt b:Option<i32>;
        opt along:Option<String>;
        opt blong:Option<String>;
        opt clong:Option<String>;
    }
    .parse_args(vec!["-a", "abc", "--along=xyz", "--blong", "ert"])
    .unwrap();
    assert_eq!(args.a, Some("abc".to_string()));
    assert_eq!(args.b, None);
    assert_eq!(args.along, Some("xyz".to_string()));
    assert_eq!(args.blong, Some("ert".to_string()));
    assert_eq!(args.clong, None);
}

#[test]
fn test_optional_default() {
    let (args, _) = opts! {
        opt along:Option<i32> = Some(1);
        opt blong:Option<i32> = Some(2);
        opt clong:Option<i32> = Some(3);
    }
    .parse_args(vec!["--along=42", "--blong"])
    .unwrap();
    assert_eq!(args.along, Some(42));
    assert_eq!(args.blong, Some(2));
    assert_eq!(args.clong, None);
}

#[test]
#[should_panic]
fn test_required_nodefault() {
    let (_, _) = opts! {
        opt a:i32;
        opt b:i32=1;
    }
    .parse_args(vec!["-b", "1"])
    .unwrap();
}

#[test]
fn test_required_default() {
    let (args, _) = opts! {
        opt a:i32=1;
        opt b:i32=2;
    }
    .parse_args(vec!["-a", "42"])
    .unwrap();
    assert_eq!(args.a, 42);
    assert_eq!(args.b, 2);
}

#[test]
#[should_panic]
fn test_no_auto_shorts() {
    let (_, _) = opts! {
        auto_shorts false;
        opt abc:bool;
    }
    .parse_args(vec!["-a"])
    .unwrap();
}

#[test]
fn test_no_auto_shorts_on_shorts() {
    let (args, _) = opts! {
        auto_shorts false;
        opt a:bool;
    }
    .parse_args(vec!["-a"])
    .unwrap();
    assert!(args.a);
}

#[test]
fn test_ambiguous_auto_shorts() {
    let (args, _) = opts! {
        opt abc:bool;
        opt another:bool;
    }
    .parse_args(vec!["-n"])
    .unwrap();
    assert!(!args.abc);
    assert!(args.another);
}

#[test]
fn test_ambiguous_forced_shorts() {
    let (args, _) = opts! {
        opt abc:bool;
        opt another:bool, short:'a';
    }
    .parse_args(vec!["-b"])
    .unwrap();
    assert!(args.abc);
    assert!(!args.another);
}

#[test]
fn test_no_dash_auto_short() {
    let (args, _) = opts! {
        opt another:bool;
        opt a_flag:bool;
    }
    .parse_args(vec!["-f"])
    .unwrap();
    assert!(!args.another);
    assert!(args.a_flag);
}

#[test]
fn test_negative_flag() {
    let (args, _) = opts! {
        opt doit:bool=true;
        opt doit2:bool=true;
    }
    .parse_args(vec!["--no-doit"])
    .unwrap();
    assert!(args.doit == false);
    assert!(args.doit2 == true);
}

#[test]
fn test_utf8_param() {
    let (args, _) = opts! {
        param arg:String;
    }
    .parse_args(vec!["aäöü"])
    .unwrap();
    assert!(args.arg == "aäöü");
}

#[test]
fn test_utf8_opt() {
    let (args, _) = opts! {
        opt a:bool, long:"äfläg", short:'ä';
        opt another:bool, long:"änötherfläg", short:'ö';
    }
    .parse_args(vec!["", "--änöther"])
    .unwrap();
    assert!(args.a);
    assert!(args.another);
}