#[derive(Debug)]
pub enum ParseResult {
ContinueSuccess,
ExitSuccess,
ExitError,
}
pub enum ParseControl {
Continue,
Stop,
Quit,
}
type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>;
#[derive(Debug)]
pub enum ParseError<'a> {
UnknownOption(&'a str),
UnexpectedToken(&'a str),
ExpectArgument(&'a str),
UnexpectedArgument(&'a str),
ArgumentError(&'static str, &'a str, ParseErrorKind),
RequiredPositional(&'static str),
RequiredParameter(&'static str),
}
#[derive(Debug)]
pub enum ParseErrorKind {
IntegerEmpty,
IntegerRange,
InvalidInteger,
InvalidFloat,
}
impl core::fmt::Display for ParseError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnknownOption(o) => write!(f, "Unrecognised option '{o}'"),
Self::UnexpectedToken(t) => write!(f, "Unexpected positional argument '{t}'"),
Self::ExpectArgument(o) => write!(f, "Option '{o}' requires an argument"),
Self::UnexpectedArgument(o) => write!(f, "Flag '{o}' doesn't take an argument"),
Self::ArgumentError(o, a, ParseErrorKind::IntegerRange)
=> write!(f, "Argument '{a}' out of range for option '{o}'"),
Self::ArgumentError(o, a, ParseErrorKind::InvalidInteger | ParseErrorKind::InvalidFloat)
=> write!(f, "Invalid argument '{a}' for option '{o}'"),
Self::ArgumentError(o, _, ParseErrorKind::IntegerEmpty)
=> write!(f, "Argument for option '{o}' cannot be empty"),
Self::RequiredPositional(o) => write!(f, "Missing required positional argument '{o}'"),
Self::RequiredParameter(o) => write!(f, "Missing required option '{o}'"),
}
}
}
impl From<core::num::ParseIntError> for ParseError<'_> {
fn from(err: core::num::ParseIntError) -> Self {
use core::num::IntErrorKind;
Self::ArgumentError("", "", match err.kind() {
IntErrorKind::Empty => ParseErrorKind::IntegerEmpty,
IntErrorKind::PosOverflow | IntErrorKind::NegOverflow | IntErrorKind::Zero
=> ParseErrorKind::IntegerRange,
IntErrorKind::InvalidDigit | _ => ParseErrorKind::InvalidInteger,
})
}
}
impl From<core::num::ParseFloatError> for ParseError<'_> {
fn from(_err: core::num::ParseFloatError) -> Self {
Self::ArgumentError("", "", ParseErrorKind::InvalidFloat)
}
}
impl core::error::Error for ParseError<'_> {}
struct ParserState<ID: 'static> {
positional_index: usize,
expects_arg: Option<(&'static str, &'static Opt<ID>)>,
required_param_presences: RequiredParamsBitSet,
}
impl<ID> Default for ParserState<ID> {
fn default() -> Self {
Self {
positional_index: 0,
expects_arg: None,
required_param_presences: Default::default(),
}
}
}
impl<ID: 'static> Opts<ID> {
pub fn parse<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
mut handler: impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl>,
error: impl FnOnce(&str, ParseError),
) -> ParseResult {
let mut state = ParserState::default();
for arg in args {
match self.next(&mut state, arg.as_ref(), program_name, &mut handler) {
Ok(ParseControl::Continue) => {}
Ok(ParseControl::Stop) => { break; }
Ok(ParseControl::Quit) => { return ParseResult::ExitSuccess; }
Err(err) => {
error(program_name, err);
return ParseResult::ExitError;
}
}
}
if let Some((name, _)) = state.expects_arg.take() {
error(program_name, ParseError::ExpectArgument(name));
return ParseResult::ExitError;
}
let mut required_flag_idx = 0;
for (i, option) in self.iter().enumerate() {
match option.r#type {
OptType::Positional => if i >= state.positional_index && option.is_required() {
error(program_name, ParseError::RequiredPositional(option.first_name()));
return ParseResult::ExitError;
}
OptType::Flag | OptType::Value => if option.is_required() {
if !state.required_param_presences.get(required_flag_idx) {
error(program_name, ParseError::RequiredParameter(option.first_name()));
return ParseResult::ExitError;
}
required_flag_idx += 1;
}
}
}
ParseResult::ContinueSuccess
}
fn next<'a, 'b>(&self, state: &mut ParserState<ID>, token: &'b str, program_name: &str,
handler: &mut impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl>
) -> HandlerResult<'b, ParseControl> where 'a: 'b {
let mut call_handler = |option: &Opt<ID>, name, value| {
match handler(program_name, &option.id, option, name, value) {
Err(ParseError::ArgumentError("", "", kind))
=> Err(ParseError::ArgumentError(name, value, kind)),
Err(err) => Err(err),
Ok(ctl) => Ok(ctl),
}
};
if let Some((name, option)) = state.expects_arg.take() {
call_handler(option, name, token)
} else {
if self.flag_chars.chars().any(|c| token.starts_with(c)) {
let (option_str, value_str) = token.split_once("=")
.map_or((token, None), |(k, v)| (k, Some(v)));
let mut required_idx = 0;
let (name, option) = self.iter()
.filter(|opt| matches!(opt.r#type, OptType::Flag | OptType::Value)).find_map(|opt| {
if let Some(name) = opt.match_name(option_str, 1) {
Some((name, opt))
} else {
if opt.is_required() {
required_idx += 1
}
None
}
}).ok_or(ParseError::UnknownOption(option_str))?;
if option.is_required() {
state.required_param_presences.insert(required_idx, true);
}
match (&option.r#type, value_str) {
(OptType::Flag, None) => call_handler(option, name, ""),
(OptType::Value, Some(value)) => call_handler(option, name, value),
(OptType::Value, None) => {
state.expects_arg = Some((name, option));
Ok(ParseControl::Continue)
}
(OptType::Flag, Some(_)) => Err(ParseError::UnexpectedArgument(option_str)),
(OptType::Positional, _) => unreachable!("Won't parse a positional argument as an option"),
}
} else {
for (i, option) in self.options[state.positional_index..].iter().enumerate() {
if matches!(option.r#type, OptType::Positional) {
handler(program_name, &option.id, option, option.first_name(), token)?;
state.positional_index += i + 1;
return Ok(ParseControl::Continue);
}
}
Err(ParseError::UnexpectedToken(token))
}
}
}
}