From dd8ed72b269153bad4b2844c8fbdaa73eb1f9b76 Mon Sep 17 00:00:00 2001 From: Avril Date: Sun, 8 Nov 2020 17:35:03 +0000 Subject: [PATCH] flag parsing --- src/flags.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 162 insertions(+), 14 deletions(-) diff --git a/src/flags.rs b/src/flags.rs index 7613aba..98198f0 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -18,6 +18,21 @@ pub enum Kind<'a> Short(&'a [(char, )], bool), /// A single `--`. Terminator, + /// An exact match with no prefix. + Exact(&'a str), +} + +impl Kind<'static> +{ + pub fn as_ref<'a>(&'a self) -> Kind<'a> + { + match &self { + Self::Long(strs, bl) => Kind::Long(strs, *bl), + Self::Short(chrs, bl) => Kind::Short(chrs, *bl), + Self::Terminator => Kind::Terminator, + Self::Exact(stri) => Kind::Exact(&stri[..]), + } + } } #[derive(Debug, Clone, PartialEq)] @@ -33,25 +48,25 @@ pub struct ArgState { user: T, - flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback, Box> + 'static)>, bool, bool)>, + flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback, eyre::Report> + 'static)>, bool, bool)>, } pub trait FlagCallback: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result{} impl FlagCallback for F where F: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result, T: Any + 'static, - E: 'static{} + E: Into {} impl ArgState { /// Push a handle to the state - pub fn push(&mut self, kind: Kind<'static>, desc: impl Into>, value: ValueKind, mut handle: F, single: bool) -> usize + pub fn push, F>(&mut self, kind: Kind<'static>, desc: impl Into>, value: ValueKind, mut handle: F, single: bool) -> usize where F: FlagCallback + 'static { - let handle: Box, Box<(dyn error::Error + 'static)>>> = + let handle: Box, eyre::Report>> = Box::new(move |state, index, value| handle(state, index, value) .map(|x| -> Box<(dyn Any + 'static)> {Box::new(x)}) - .map_err(|x| -> Box<(dyn error::Error +'static)> {Box::new(x)})); + .map_err(Into::into)); self.flags.push((kind, desc.into(), value, handle, single, true)); self.flags.len()-1 } @@ -208,7 +223,7 @@ impl ArgState /// # Note /// This mutates the argument state container indefinately, and multiple calls to it will keep the mutated state. /// So this might not behave as expected (e.g. callbacks marked `single` that were fired in the first call will not be fired in the second, etc.) - pub fn parse>(&mut self, input: I) -> Result>, Error> + pub fn parse>(&mut self, input: I) -> Result>, CtxError> where I: Iterator { let mut output = Vec::with_capacity(self.flags.len()); @@ -245,19 +260,78 @@ impl ArgState } } +/// Error without context #[derive(Debug)] pub struct Error { flag: Kind<'static>, arg: (usize, String), desc: Option, - from: Box, + from: eyre::Report, +} + +/// An argument parsing error with context +#[repr(transparent)] +#[derive(Debug)] +pub struct CtxError(Error); + +#[derive(Debug, Clone, Copy)] +pub struct ErrorContext<'a> +{ + pub flag: &'a Kind<'static>, + pub arg: &'a String, + pub arg_idx: &'a usize, + pub desc: Option<&'a String>, + pub from: &'a eyre::Report, +} + +impl CtxError +{ + /// The inner error + #[inline] pub fn inner(&self) -> &Error + { + &self.0 + } + /// Strips the context and returns the inner error + #[inline] fn into_inner(self) -> Error + { + self.0 + } + /// The context of this error + pub fn context(&self) -> ErrorContext<'_> + { + ErrorContext { + flag: &self.0.flag, + arg: &self.0.arg.1, + arg_idx: &self.0.arg.0, + desc: self.0.desc.as_ref(), + from: &self.0.from + } + } + + /// Which argument was the error thrown for + pub fn what(&self) -> (usize, &str) + { + (self.0.arg.0, &self.0.arg.1[..]) + } + + /// Where did the parsing fail? + pub fn which(&self) -> Kind<'_> + { + self.0.flag.as_ref() + } + + /// The message reported by the failing callback, if any + pub fn message(&self) -> Option<&str> + { + self.0.desc.as_ref().map(|x| &x[..]) + } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { - Some(self.from.as_ref()) + self.from.source() } } @@ -265,12 +339,86 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Failed to parse args: {}", self.from)?; - writeln!(f, "\tArg {} which was {:?} failed on parsing for `{:?}`", self.arg.0, self.arg.1, self.flag)?; - if let Some(desc) = &self.desc { - writeln!(f, "\n\tUsage of {:?}: {}", self.flag, desc) + writeln!(f, "Failed to parse args") + } +} + +impl fmt::Display for CtxError +{ + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + self.0.fmt(f) + } +} + + +impl Error +{ + /// Consume into an `eyre::Report`. + fn into_report(self) -> eyre::Report + { + let Error {flag, desc, from ,arg: (idx, arg)} = self; + let err = Err::(from) + //.wrap_err(eyre!("Failed to parse args")) + .with_section(move || format!("{:?}", flag).header("While attempting to parse for")) + .with_note(move || idx.to_string().header("Argument index was")) + .with_note(move || format!("{:?}", arg).header("Argument was")); + if let Some(desc) = desc { + err.with_suggestion(move || desc) } else { - Ok(()) - } + err + }.unwrap_err() + } + + /// Add context to this error + pub fn with_context(self) -> CtxError + { + CtxError(Self { + flag: self.flag.clone(), + arg: self.arg.clone(), + desc: self.desc.clone(), + from: self.into_report(), + }) + } +} + +impl From for CtxError +{ + fn from(from: Error) -> Self + { + from.with_context() + } +} + +impl From for eyre::Report +{ + fn from(from: CtxError) -> Self + { + from.0.from + } +} + + +#[cfg(test)] +mod tests +{ + use super::*; + #[test] + fn errors() -> Result<(), eyre::Report> + { + //color_eyre::install()?; + + let err= Error { + flag: Kind::Long(&["test"], false), + arg: (0, "owo".to_owned()), + desc: Some("expected between 1-10".to_string()), + from: eyre!("Invalid number").wrap_err("Parsing failure"), + }; + println!("No ctx: `{} {:?}`\n", err, err); + let err = err.with_context(); + println!("With ctx: `{} {:?}`\n", err, err); + + //return Err(err)?; + Ok(()) } }