#![deny(missing_docs)]
#[cfg(test)] #[macro_use] extern crate assert_matches;
use std::error::Error as StdError;
use std::fmt;
use std::slice::Iter;
use std::str::Chars;
pub fn parse_args<T: Options>(args: &[String], style: ParsingStyle) -> Result<T, Error> {
T::parse(&mut Parser::new(args, style))
}
pub fn parse_args_default<T: Options>(args: &[String]) -> Result<T, Error> {
T::parse(&mut Parser::new(args, ParsingStyle::default()))
}
#[derive(Debug)]
pub struct Error {
inner: InnerError,
}
impl Error {
pub fn failed_parse(opt: Opt, err: String) -> Error {
Error{inner: InnerError::FailedParse(opt.to_string(), err)}
}
pub fn unexpected_argument(opt: Opt) -> Error {
Error{inner: InnerError::UnexpectedArgument(opt.to_string())}
}
pub fn missing_argument(opt: Opt) -> Error {
Error{inner: InnerError::MissingArgument(opt.to_string())}
}
pub fn missing_command() -> Error {
Error{inner: InnerError::MissingCommand}
}
pub fn unexpected_free(arg: &str) -> Error {
Error{inner: InnerError::UnexpectedFree(arg.to_owned())}
}
pub fn unrecognized_command(name: &str) -> Error {
Error{inner: InnerError::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{inner: InnerError::UnrecognizedLongOption(opt.to_owned())}
}
pub fn unrecognized_short(opt: char) -> Error {
Error{inner: InnerError::UnrecognizedShortOption(opt)}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::InnerError::*;
match self.inner {
FailedParse(ref opt, ref arg) => write!(f, "invalid argument to option `{}`: {}", opt, arg),
MissingArgument(ref opt) => write!(f, "missing argument to option `{}`", opt),
MissingCommand => f.write_str("missing command name"),
UnexpectedArgument(ref opt) => write!(f, "option `{}` does not accept an argument", opt),
UnexpectedFree(ref arg) => write!(f, "unexpected free argument `{}`", arg),
UnrecognizedCommand(ref cmd) => write!(f, "unrecognized command `{}`", cmd),
UnrecognizedLongOption(ref 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"
}
}
#[derive(Debug)]
enum InnerError {
FailedParse(String, String),
MissingArgument(String),
MissingCommand,
UnexpectedArgument(String),
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,
}
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,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Opt<'a> {
Short(char),
Long(&'a str),
LongWithArg(&'a str, &'a str),
Free(&'a str),
}
impl<'a> Opt<'a> {
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()
}
}
}
pub trait Options: Sized {
fn parse<S: AsRef<str>>(parser: &mut Parser<S>) -> Result<Self, Error>;
fn help_requested(&self) -> bool { false }
fn parse_args<S: AsRef<str>>(args: &[S], style: ParsingStyle) -> Result<Self, Error> {
Self::parse(&mut Parser::new(args, style))
}
fn parse_args_default<S: AsRef<str>>(args: &[S]) -> Result<Self, Error> {
Self::parse(&mut Parser::new(args, ParsingStyle::default()))
}
fn parse_command<S: AsRef<str>>(name: &str, parser: &mut Parser<S>) -> Result<Self, Error>;
fn usage() -> &'static str;
fn command_usage(command: &str) -> Option<&'static str>;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParsingStyle {
AllOptions,
StopAtFirstFree,
}
impl Default for ParsingStyle {
fn default() -> ParsingStyle {
ParsingStyle::AllOptions
}
}
#[cfg(test)]
mod test {
use super::{Opt, Parser, ParsingStyle};
#[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);
}
}