use core::str::FromStr;
use std::error::Error;
pub trait Parser {
type ArgId<T>;
type Parsed: Parsed<Parser = Self>;
fn add_flag(
&mut self,
short: &'static [char],
long: &'static [&'static str],
) -> Self::ArgId<bool>;
fn add_option<T, E>(
&mut self,
short: &'static [char],
long: &'static [&'static str],
) -> Self::ArgId<T>
where
T: FromStr<Err = E> + 'static,
E: 'static + Into<Box<dyn Error>>;
fn add_option_with<T: 'static, E, F>(
&mut self,
short: &'static [char],
long: &'static [&'static str],
parse: F,
) -> Self::ArgId<T>
where
F: 'static + Fn(&str) -> Result<T, E>,
E: 'static + Into<Box<dyn Error>>;
fn parse(&self) -> Result<Self::Parsed, ParsingError>;
}
pub trait Parsed {
type Parser: Parser;
fn get<T: 'static>(&self, arg: &<Self::Parser as Parser>::ArgId<T>) -> Option<&T>;
}
#[derive(Debug)]
pub enum ParsingError {
ParsingFailed,
ValueParsingFailed {
arg_name: String,
error: Box<dyn Error>,
},
UnknownOption { arg_name: String },
MissingValue { arg_name: String },
UnknownValue,
}
impl std::fmt::Display for ParsingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParsingFailed => write!(f, "could not parse arguments"),
Self::ValueParsingFailed { arg_name, error } => write!(
f,
"could not parse value for option argument {}: {}",
arg_name, error
),
Self::UnknownOption { arg_name } => {
write!(f, "found unknown option argument {}", arg_name)
}
Self::MissingValue { arg_name } => {
write!(f, "no value for option argument {}", arg_name)
}
Self::UnknownValue => write!(
f,
"found unknown value which is not an option argument or expected value for one"
),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub trait ParseTest: Parser {
fn new() -> Self;
fn parse_test_args<S: ToString>(
&self,
args: &[(S, Option<S>)],
) -> Result<Self::Parsed, ParsingError>;
}
pub fn flags<P: ParseTest>() {
let mut parser = P::new();
let foo = parser.add_flag(&['f'], &["foo"]);
let bar = parser.add_flag(&['b'], &["bar"]);
let args = parser.parse_test_args::<&str>(&[]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("foo", None)]).unwrap();
assert_eq!(args.get(&foo), Some(true).as_ref());
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("bar", None)]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), Some(true).as_ref());
let args = parser
.parse_test_args(&[("foo", None), ("bar", None)])
.unwrap();
assert_eq!(args.get(&foo), Some(true).as_ref());
assert_eq!(args.get(&bar), Some(true).as_ref());
let args = parser.parse_test_args(&[("f", None)]).unwrap();
assert_eq!(args.get(&foo), Some(true).as_ref());
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("b", None)]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), Some(true).as_ref());
let args = parser.parse_test_args(&[("f", None), ("b", None)]).unwrap();
assert_eq!(args.get(&foo), Some(true).as_ref());
assert_eq!(args.get(&bar), Some(true).as_ref());
}
pub fn flags_unknown<P: ParseTest>() {
let mut parser = P::new();
parser.add_flag(&['f'], &["foo"]);
parser.add_flag(&['b'], &["bar"]);
assert!(matches!(
parser.parse_test_args(&[("baz", None)]),
Err(ParsingError::UnknownOption { arg_name }) if arg_name == "baz"
));
assert!(matches!(
parser.parse_test_args(&[("x", None)]),
Err(ParsingError::UnknownOption { arg_name }) if arg_name == "x",
));
}
pub fn options<P: ParseTest>() {
let mut parser = P::new();
let foo = parser.add_option_with::<_, _, _>(&['f'], &["foo"], str::parse::<i32>);
let bar = parser.add_option::<String, _>(&['b'], &["bar"]);
let args = parser.parse_test_args::<&str>(&[]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("foo", Some("123"))]).unwrap();
assert_eq!(args.get(&foo), Some(123).as_ref());
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("bar", Some("abc"))]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), Some("abc".to_string()).as_ref());
let args = parser
.parse_test_args(&[("foo", Some("123")), ("bar", Some("abc"))])
.unwrap();
assert_eq!(args.get(&foo), Some(123).as_ref());
assert_eq!(args.get(&bar), Some("abc".to_string()).as_ref());
let args = parser.parse_test_args(&[("f", Some("123"))]).unwrap();
assert_eq!(args.get(&foo), Some(123).as_ref());
assert_eq!(args.get(&bar), None);
let args = parser.parse_test_args(&[("b", Some("abc"))]).unwrap();
assert_eq!(args.get(&foo), None);
assert_eq!(args.get(&bar), Some("abc".to_string()).as_ref());
let args = parser
.parse_test_args(&[("f", Some("123")), ("b", Some("abc"))])
.unwrap();
assert_eq!(args.get(&foo), Some(123).as_ref());
assert_eq!(args.get(&bar), Some("abc".to_string()).as_ref());
assert!(matches!(
parser.parse_test_args(&[("foo", Some("abc")), ("123", None)]),
Err(ParsingError::ValueParsingFailed { arg_name, .. })
if arg_name == "foo"
));
}
pub fn options_unknown<P: ParseTest>() {
let mut parser = P::new();
parser.add_option_with::<_, _, _>(&['f'], &["foo"], str::parse::<i32>);
parser.add_option::<String, _>(&['b'], &["bar"]);
assert!(matches!(
parser.parse_test_args(&[("baz", Some("123"))]),
Err(ParsingError::UnknownOption { arg_name }) if arg_name == "baz",
));
assert!(matches!(
parser.parse_test_args(&[("x", Some("123"))]),
Err(ParsingError::UnknownOption { arg_name }) if arg_name == "x"
));
}
pub fn options_missing_value<P: ParseTest>() {
let mut parser = P::new();
parser.add_option_with::<_, _, _>(&['f'], &["foo"], str::parse::<i32>);
assert!(matches!(
parser.parse_test_args(&[("foo", None)]),
Err(ParsingError::MissingValue { arg_name }) if arg_name == "foo"
));
}
}