use std::{ convert::Infallible, num::{ParseFloatError, ParseIntError}, str::{FromStr, ParseBoolError}, string::FromUtf8Error, }; use log::error; use thiserror::Error; use self::either::Either; use super::hex::HexError; pub mod either; mod traits; pub use traits::*; #[derive(Debug, Clone, Error, strum::EnumIs)] pub enum ParseError { #[error("no more items left in parser")] Empty, #[error("missing expected value")] ValueMissing, #[error("invalid command: [{0}]")] InvalidCommand(String), #[error("invalid value [{value}], expected [{expected}]")] InvalidValue { value: String, expected: &'static str, }, #[error("hex decoding error: [{0}]")] HexError(#[from] HexError), #[error("primitive type parsing error: [{0}]")] PrimitiveError(String), } impl From for ParseError { fn from(_: Infallible) -> Self { // nonsense unreachable!() } } macro_rules! from_primitive { ($($err:ty,)*) => { $( impl From<$err> for ParseError { fn from(value: $err) -> Self { ParseError::PrimitiveError(value.to_string()) } } )* }; } from_primitive!( ParseBoolError, ParseIntError, FromUtf8Error, ParseFloatError, ); impl From for ParseError { fn from(value: strum::ParseError) -> Self { ParseError::InvalidCommand(value.to_string()) } } pub struct ArgParser where I: Iterator, { inner: I, } impl Clone for ArgParser where I: Iterator + Clone, { fn clone(&self) -> Self { Self { inner: self.inner.clone(), } } } impl ArgParser where I: Iterator, { pub fn from_strings(src: I) -> Self { Self { inner: src } } #[inline(always)] pub fn next_string(&mut self) -> Option { self.inner.next() } #[inline(always)] pub fn inner(self) -> I { self.inner } /// Gets the next string, then tries the `try_this` function on it. /// If that function returns [Err], it runs the `then_this` function /// and returns that result. pub fn try_first( &mut self, try_this: F1, then_this: F2, on_error: &str, ) -> Result, ParseError> where F1: FnOnce(&String) -> Result, F2: FnOnce(&String) -> Result, E: Into, { let next = self.must_string(on_error)?; if let Ok(t1) = try_this(&next) { return Ok(Either::Left(t1)); } match then_this(&next) { Ok(t2) => Ok(Either::Right(t2)), Err(err) => { let err = err.into(); error!("{on_error}: {err}"); Err(err) } } } /// Get the next string, or [ParseError::Empty] #[inline(always)] pub fn must_string(&mut self, on_error: &str) -> Result { self.inner .next() .ok_or(ParseError::Empty) .inspect_err(|err| error!("{on_error}: {err}")) } pub fn next_from_str, T: FromStr>( &mut self, on_error: &str, ) -> Result { Ok(self.must_string(on_error)?.parse().map_err(|err: E| { let err = err.into(); error!("{on_error}: {err}"); err })?) } /// Consumes the remainder of the iterator and uses the [FromCommandArgs] /// trait to parse it into the desired type pub fn collect_command(mut self, on_error: &str) -> Result { C::from_command_args(&self.must_string(on_error)?, self.inner) } /// Consumes the remainder of the iterator and uses the [FromStrings] /// trait to parse it into the desired type pub fn collect_from_strings(self, on_error: &str) -> Result { T::from_strings(self.inner).inspect_err(|err| { error!("{on_error}: {err}"); }) } /// Consumes the remainder of the iterator and uses the [FromStringsHint] /// trait to parse it into the desired type. /// /// Takes a `hint` that may aid in the parsing of that string pub fn collect_from_strings_hint>( self, hint: H, on_error: &str, ) -> Result { T::from_strings_hint(self.inner, hint).inspect_err(|err| { error!("{on_error}: {err}"); }) } #[inline(always)] pub fn collect>(self) -> B { self.inner.collect() } /// Get the next [String] from the iterator and tries to parse it with [FromStr] /// if there's any remaining items. Otherwise just returns [None] pub fn optional_next_from_str, T: FromStr>( &mut self, on_error: &str, ) -> Result, ParseError> { match self.next_string() { Some(next) => Ok(Some(next.parse().map_err(|err: E| { let err = err.into(); error!("{on_error}: {err}"); err })?)), None => Ok(None), } } /// Consumes the iterator and takes every remaining element, parses them with /// [FromStr] and returns them in a [Vec] pub fn collect_from_str, T: FromStr>( self, on_error: &str, ) -> Result, ParseError> { self.inner .map(|item| T::from_str(&item)) .collect::, _>>() .map_err(|err| { let err = err.into(); error!("{on_error}: {err}"); err }) } /// Consumes the iterator and takes every remaining element, checks if any match /// `flag`, and returns the strings in the iterator that don't match `flag`, /// as well as a boolean on whether `flag` was found. pub fn collect_strings_with_flag(self, flag: &str) -> (Vec, bool) { let mut out = Vec::new(); let mut found = false; for item in self.inner { if item == flag { found = true; continue; } out.push(item); } (out, found) } }