#![deny(missing_docs)]
pub mod arg;
mod error;
use std::ffi::OsStr;
use arg::{ArgString, ParsedArg};
pub use error::{OptionError, UsageError};
pub struct Args<T> {
args: T,
allow_options: bool,
}
impl<T> Args<T> {
pub fn from(args: T) -> Self {
Args {
args,
allow_options: true,
}
}
pub fn rest(self) -> T {
self.args
}
}
impl<T> Args<T>
where
T: Iterator,
<T as Iterator>::Item: ArgString,
{
pub fn next<'a>(&'a mut self) -> Arg<'a, T> {
let arg = match self.args.next() {
None => return Arg::End,
Some(arg) => arg,
};
if !self.allow_options {
return Arg::Positional(arg);
}
let arg = match arg.parse_arg() {
Err(arg) => return Arg::Error(UsageError::InvalidArgument { arg }),
Ok(arg) => arg,
};
match arg {
ParsedArg::Positional(arg) => Arg::Positional(arg),
ParsedArg::EndOfFlags => {
self.allow_options = false;
match self.args.next() {
None => Arg::End,
Some(arg) => Arg::Positional(arg),
}
}
ParsedArg::Named(name, data) => Arg::Named(NamedArgument {
name,
data,
args: self,
}),
}
}
}
pub enum Arg<'a, T>
where
T: Iterator,
{
Positional(T::Item),
Named(NamedArgument<'a, T>),
End,
Error(UsageError<T::Item>),
}
pub struct NamedArgument<'a, T>
where
T: Iterator,
{
name: String,
data: Option<<T as Iterator>::Item>,
args: &'a mut Args<T>,
}
impl<'a, T> NamedArgument<'a, T>
where
T: Iterator,
<T as Iterator>::Item: ArgString,
{
pub fn parse<U, F>(self, f: F) -> Result<U, UsageError<<T as Iterator>::Item>>
where
for<'b> F: FnOnce(&'b str, Value<'b, T>) -> Result<U, OptionError>,
{
let NamedArgument {
name,
mut data,
args,
} = self;
let mut consumed = false;
let err = match f(
&name,
Value {
data: &mut data,
args,
consumed: &mut consumed,
},
) {
Err(err) => err,
Ok(r) => {
if consumed || data.is_none() {
return Ok(r);
} else {
OptionError::UnexpectedParameter
}
}
};
Err(UsageError::InvalidOption {
name,
value: data,
err,
})
}
}
pub struct Value<'a, T>
where
T: Iterator,
{
data: &'a mut Option<<T as Iterator>::Item>,
args: &'a mut Args<T>,
consumed: &'a mut bool,
}
impl<'a, T> Value<'a, T>
where
T: Iterator,
{
fn value(self) -> Result<&'a T::Item, OptionError> {
*self.consumed = true;
match self.data {
Some(x) => Ok(x),
None => match self.args.args.next() {
Some(x) => Ok(self.data.get_or_insert(x)),
None => Err(OptionError::MissingParameter),
},
}
}
}
impl<'a, T> Value<'a, T>
where
T: Iterator,
<T as Iterator>::Item: ArgString,
{
pub fn as_str(self) -> Result<&'a str, OptionError> {
match self.value()?.to_str() {
Some(x) => Ok(x),
None => Err(OptionError::InvalidUnicode),
}
}
pub fn as_osstr(self) -> Result<&'a OsStr, OptionError> {
self.value().map(ArgString::to_osstr)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::str::FromStr;
#[derive(Debug, PartialEq, Eq)]
struct Parsed {
positional: Vec<String>,
flag: bool,
xvalue: Option<i32>,
}
fn parse_args(args: &'static [&'static str]) -> Result<Parsed, UsageError<String>> {
let mut args = Args::from(args.iter().map(|&s| s.to_owned()));
let mut positional = Vec::new();
let mut flag = false;
let mut xvalue = None;
loop {
match args.next() {
Arg::Positional(x) => positional.push(x),
Arg::Named(arg) => arg.parse(|name, arg| match name {
"flag" => {
flag = true;
Ok(())
}
"x" => {
xvalue = Some(i32::from_str(arg.as_str()?)?);
Ok(())
}
_ => Err(OptionError::Unknown),
})?,
Arg::End => break,
Arg::Error(err) => return Err(err),
}
}
Ok(Parsed {
positional,
flag,
xvalue,
})
}
#[test]
fn success() {
match parse_args(&["abc", "--flag", "-x=10", "--", "--", "--arg"]) {
Err(err) => panic!("err: {:?}", err),
Ok(r) => assert_eq!(
r,
Parsed {
positional: vec!["abc".to_owned(), "--".to_owned(), "--arg".to_owned()],
flag: true,
xvalue: Some(10),
}
),
}
}
#[test]
fn no_param() {
let r = parse_args(&["--x"]);
if let Err(e) = &r {
if let UsageError::InvalidOption { name, value, err } = e {
assert_eq!(name, "x");
assert!(value.is_none());
if let OptionError::MissingParameter = err {
return;
}
}
}
panic!("incorrect result: {:?}", r);
}
#[test]
fn bad_param() {
let r = parse_args(&["-x", "0q"]);
if let Err(e) = &r {
if let UsageError::InvalidOption { name, value, err } = e {
assert_eq!(name, "x");
assert_eq!(value, &Some("0q".to_owned()));
if let OptionError::InvalidValue(_) = err {
return;
}
}
}
panic!("incorrect result: {:?}", r);
}
}