#![deny(missing_docs)]
pub use gumdrop_derive::*;
use std::error::Error as StdError;
use std::fmt;
use std::slice::Iter;
use std::str::Chars;
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
#[derive(Debug)]
enum ErrorKind {
FailedParse(String, String),
FailedParseDefault{
option: &'static str,
value: &'static str,
err: String,
},
InsufficientArguments{
option: String,
expected: usize,
found: usize,
},
MissingArgument(String),
MissingCommand,
MissingRequired(String),
MissingRequiredCommand,
MissingRequiredFree,
UnexpectedArgument(String),
UnexpectedSingleArgument(String, usize),
UnexpectedFree(String),
UnrecognizedCommand(String),
UnrecognizedLongOption(String),
UnrecognizedShortOption(char),
}
pub struct Parser<'a, S: 'a> {
args: Iter<'a, S>,
cur: Option<Chars<'a>>,
style: ParsingStyle,
terminated: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Opt<'a> {
Short(char),
Long(&'a str),
LongWithArg(&'a str, &'a str),
Free(&'a str),
}
pub trait Options {
fn parse<S: AsRef<str>>(parser: &mut Parser<S>) -> Result<Self, Error> where Self: Sized;
fn command(&self) -> Option<&dyn Options>;
fn command_name(&self) -> Option<&'static str> { None }
fn help_requested(&self) -> bool { false }
fn parse_args<S: AsRef<str>>(args: &[S], style: ParsingStyle) -> Result<Self, Error>
where Self: Sized {
Self::parse(&mut Parser::new(args, style))
}
fn parse_args_or_exit(style: ParsingStyle) -> Self where Self: Sized {
use std::env::args;
use std::process::exit;
let args = args().collect::<Vec<_>>();
let opts = Self::parse_args(&args[1..], style).unwrap_or_else(|e| {
eprintln!("{}: {}", args[0], e);
exit(2);
});
if opts.help_requested() {
let mut command = &opts as &dyn Options;
let mut command_str = String::new();
loop {
if let Some(new_command) = command.command() {
command = new_command;
if let Some(name) = new_command.command_name() {
command_str.push(' ');
command_str.push_str(name);
}
} else {
break;
}
}
eprintln!("Usage: {}{} [OPTIONS]", args[0], command_str);
eprintln!();
eprintln!("{}", command.self_usage());
if let Some(cmds) = command.self_command_list() {
eprintln!();
eprintln!("Available commands:");
eprintln!("{}", cmds);
}
exit(0);
}
opts
}
fn parse_args_default_or_exit() -> Self where Self: Sized {
Self::parse_args_or_exit(ParsingStyle::default())
}
fn parse_args_default<S: AsRef<str>>(args: &[S]) -> Result<Self, Error> where Self: Sized {
Self::parse(&mut Parser::new(args, ParsingStyle::default()))
}
fn parse_command<S: AsRef<str>>(name: &str, parser: &mut Parser<S>) -> Result<Self, Error> where Self: Sized;
fn usage() -> &'static str where Self: Sized;
fn self_usage(&self) -> &'static str;
fn command_usage(command: &str) -> Option<&'static str> where Self: Sized;
fn command_list() -> Option<&'static str> where Self: Sized;
fn self_command_list(&self) -> Option<&'static str>;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParsingStyle {
AllOptions,
StopAtFirstFree,
}
impl Error {
pub fn failed_parse(opt: Opt, err: String) -> Error {
Error{kind: ErrorKind::FailedParse(opt.to_string(), err)}
}
pub fn failed_parse_default(option: &'static str,
value: &'static str, err: String) -> Error {
Error{kind: ErrorKind::FailedParseDefault{option, value, err}}
}
pub fn failed_parse_with_name(name: String, err: String) -> Error {
Error{kind: ErrorKind::FailedParse(name, err)}
}
pub fn insufficient_arguments(opt: Opt, expected: usize, found: usize) -> Error {
Error{kind: ErrorKind::InsufficientArguments{
option: opt.to_string(),
expected: expected,
found: found,
}}
}
pub fn unexpected_argument(opt: Opt) -> Error {
Error{kind: ErrorKind::UnexpectedArgument(opt.to_string())}
}
pub fn unexpected_single_argument(opt: Opt, n: usize) -> Error {
Error{kind: ErrorKind::UnexpectedSingleArgument(opt.to_string(), n)}
}
pub fn missing_argument(opt: Opt) -> Error {
Error{kind: ErrorKind::MissingArgument(opt.to_string())}
}
pub fn missing_command() -> Error {
Error{kind: ErrorKind::MissingCommand}
}
pub fn missing_required(opt: &str) -> Error {
Error{kind: ErrorKind::MissingRequired(opt.to_owned())}
}
pub fn missing_required_command() -> Error {
Error{kind: ErrorKind::MissingRequiredCommand}
}
pub fn missing_required_free() -> Error {
Error{kind: ErrorKind::MissingRequiredFree}
}
pub fn unexpected_free(arg: &str) -> Error {
Error{kind: ErrorKind::UnexpectedFree(arg.to_owned())}
}
pub fn unrecognized_command(name: &str) -> Error {
Error{kind: ErrorKind::UnrecognizedCommand(name.to_owned())}
}
pub fn unrecognized_option(opt: Opt) -> Error {
match opt {
Opt::Short(short) => Error::unrecognized_short(short),
Opt::Long(long) | Opt::LongWithArg(long, _) =>
Error::unrecognized_long(long),
Opt::Free(_) => panic!("`Error::unrecognized_option` called with `Opt::Free` value")
}
}
pub fn unrecognized_long(opt: &str) -> Error {
Error{kind: ErrorKind::UnrecognizedLongOption(opt.to_owned())}
}
pub fn unrecognized_short(opt: char) -> Error {
Error{kind: ErrorKind::UnrecognizedShortOption(opt)}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ErrorKind::*;
match &self.kind {
FailedParse(opt, arg) => write!(f, "invalid argument to option `{}`: {}", opt, arg),
FailedParseDefault{option, value, err} => write!(f, "invalid default value for `{}` ({:?}): {}", option, value, err),
InsufficientArguments{option, expected, found} =>
write!(f, "insufficient arguments to option `{}`: expected {}; found {}",
option, expected, found),
MissingArgument(opt) => write!(f, "missing argument to option `{}`", opt),
MissingCommand => f.write_str("missing command name"),
MissingRequired(opt) => write!(f, "missing required option `{}`", opt),
MissingRequiredCommand => f.write_str("missing required command"),
MissingRequiredFree => f.write_str("missing required free argument"),
UnexpectedArgument(opt) => write!(f, "option `{}` does not accept an argument", opt),
UnexpectedSingleArgument(opt, n) =>
write!(f, "option `{}` expects {} arguments; found 1", opt, n),
UnexpectedFree(arg) => write!(f, "unexpected free argument `{}`", arg),
UnrecognizedCommand(cmd) => write!(f, "unrecognized command `{}`", cmd),
UnrecognizedLongOption(opt) => write!(f, "unrecognized option `--{}`", opt),
UnrecognizedShortOption(opt) => write!(f, "unrecognized option `-{}`", opt),
}
}
}
impl StdError for Error {
fn description(&self) -> &str {
"failed to parse arguments"
}
}
impl<'a, S: 'a + AsRef<str>> Parser<'a, S> {
pub fn new(args: &'a [S], style: ParsingStyle) -> Parser<'a, S> {
Parser{
args: args.iter(),
cur: None,
style: style,
terminated: false,
}
}
pub fn next_opt(&mut self) -> Option<Opt<'a>> {
if let Some(mut cur) = self.cur.take() {
if let Some(opt) = cur.next() {
self.cur = Some(cur);
return Some(Opt::Short(opt));
}
}
if self.terminated {
return self.args.next().map(|s| Opt::Free(s.as_ref()));
}
match self.args.next().map(|s| s.as_ref()) {
Some(arg @ "-") => {
if self.style == ParsingStyle::StopAtFirstFree {
self.terminated = true;
}
Some(Opt::Free(arg))
}
Some("--") => {
self.terminated = true;
self.args.next().map(|s| Opt::Free(s.as_ref()))
}
Some(long) if long.starts_with("--") => {
match long.find('=') {
Some(pos) => Some(Opt::LongWithArg(
&long[2..pos], &long[pos + 1..])),
None => Some(Opt::Long(&long[2..]))
}
}
Some(short) if short.starts_with('-') => {
let mut chars = short[1..].chars();
let res = chars.next().map(Opt::Short);
self.cur = Some(chars);
res
}
Some(free) => {
if self.style == ParsingStyle::StopAtFirstFree {
self.terminated = true;
}
Some(Opt::Free(free))
}
None => None
}
}
pub fn next_arg(&mut self) -> Option<&'a str> {
if let Some(cur) = self.cur.take() {
let arg = cur.as_str();
if !arg.is_empty() {
return Some(arg);
}
}
self.args.next().map(|s| s.as_ref())
}
}
impl<'a, S: 'a> Clone for Parser<'a, S> {
fn clone(&self) -> Parser<'a, S> {
Parser{
args: self.args.clone(),
cur: self.cur.clone(),
style: self.style,
terminated: self.terminated,
}
}
}
impl<'a> Opt<'a> {
#[doc(hidden)]
pub fn to_string(&self) -> String {
match *self {
Opt::Short(ch) => format!("-{}", ch),
Opt::Long(s) => format!("--{}", s),
Opt::LongWithArg(opt, _) => format!("--{}", opt),
Opt::Free(_) => "free".to_owned()
}
}
}
impl Default for ParsingStyle {
fn default() -> ParsingStyle {
ParsingStyle::AllOptions
}
}
pub fn parse_args<T: Options>(args: &[String], style: ParsingStyle) -> Result<T, Error> {
T::parse_args(args, style)
}
pub fn parse_args_default<T: Options>(args: &[String]) -> Result<T, Error> {
T::parse_args_default(args)
}
pub fn parse_args_or_exit<T: Options>(style: ParsingStyle) -> T {
T::parse_args_or_exit(style)
}
pub fn parse_args_default_or_exit<T: Options>() -> T {
T::parse_args_default_or_exit()
}
#[cfg(test)]
mod test {
use super::{Opt, Parser, ParsingStyle};
use assert_matches::assert_matches;
#[test]
fn test_parser() {
let args = &["-a", "b", "-cde", "arg", "-xfoo", "--long", "--opt=val",
"--", "y", "-z"];
let mut p = Parser::new(args, ParsingStyle::AllOptions);
assert_matches!(p.next_opt(), Some(Opt::Short('a')));
assert_matches!(p.next_opt(), Some(Opt::Free("b")));
assert_matches!(p.next_opt(), Some(Opt::Short('c')));
assert_matches!(p.next_opt(), Some(Opt::Short('d')));
assert_matches!(p.next_opt(), Some(Opt::Short('e')));
assert_matches!(p.next_arg(), Some("arg"));
assert_matches!(p.next_opt(), Some(Opt::Short('x')));
assert_matches!(p.next_arg(), Some("foo"));
assert_matches!(p.next_opt(), Some(Opt::Long("long")));
assert_matches!(p.next_opt(), Some(Opt::LongWithArg("opt", "val")));
assert_matches!(p.next_opt(), Some(Opt::Free("y")));
assert_matches!(p.next_opt(), Some(Opt::Free("-z")));
assert_matches!(p.next_opt(), None);
}
#[test]
fn test_parsing_style() {
let args = &["-a", "b", "-c", "--d"];
let mut p = Parser::new(args, ParsingStyle::AllOptions);
assert_matches!(p.next_opt(), Some(Opt::Short('a')));
assert_matches!(p.next_opt(), Some(Opt::Free("b")));
assert_matches!(p.next_opt(), Some(Opt::Short('c')));
assert_matches!(p.next_opt(), Some(Opt::Long("d")));
assert_matches!(p.next_opt(), None);
let mut p = Parser::new(args, ParsingStyle::StopAtFirstFree);
assert_matches!(p.next_opt(), Some(Opt::Short('a')));
assert_matches!(p.next_opt(), Some(Opt::Free("b")));
assert_matches!(p.next_opt(), Some(Opt::Free("-c")));
assert_matches!(p.next_opt(), Some(Opt::Free("--d")));
assert_matches!(p.next_opt(), None);
}
}