Compare commits

...

5 Commits
master ... args

7
Cargo.lock generated

@ -1,5 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ad-hoc-iter"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5068a429476033d1940f21e21d317afae2fc3a82f412d5d8fe08c13f100a00e8"
[[package]]
name = "addr2line"
version = "0.13.0"
@ -890,6 +896,7 @@ checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
name = "videl"
version = "0.1.0"
dependencies = [
"ad-hoc-iter",
"base64",
"cfg-if",
"chrono",

@ -32,6 +32,7 @@ termprogress = "0.3.4"
uuid = {version = "0.8.1", features=["v4"]}
lazy_format = "1.8.3"
recolored = "1.9.3"
ad-hoc-iter = {version = "^0.2.2", features = ["maybe-many"]}
[build-dependencies]
rustc_version = "0.2"

@ -0,0 +1,151 @@
//! Errors for arg parsing
use super::*;
use flags::*;
use std::{
error,
fmt,
};
/// Error without context
#[derive(Debug)]
pub struct Error
{
pub(super) flag: Kind<'static>,
pub(super) arg: (usize, String),
pub(super) desc: Option<String>,
pub(super) from: eyre::Report,
}
/// An argument parsing error with context
#[repr(transparent)]
#[derive(Debug)]
pub struct ContextualError(Error);
#[derive(Debug, Clone, Copy)]
pub struct ErrorContext<'a>
{
/// The error-throwing flag
pub flag: &'a Kind<'static>,
/// The argument string
pub arg: &'a String,
/// The index of the argument string
pub arg_idx: &'a usize,
/// Message from the failing flag callback, if any
pub desc: Option<&'a String>,
/// The error report
pub from: &'a eyre::Report,
}
impl ContextualError
{
/// 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)> {
self.from.source()
}
}
impl fmt::Display for Error
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
writeln!(f, "Failed to parse args")
}
}
impl fmt::Display for ContextualError
{
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
self.0.fmt(f)
}
}
impl Error
{
/// Consume into an `eyre::Report`.
pub(super) 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 || flag.to_string().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 {
err
}.unwrap_err()
}
/// Add context to this error
pub fn with_context(self) -> ContextualError
{
ContextualError(Self {
flag: self.flag.clone(),
arg: self.arg.clone(),
desc: self.desc.clone(),
from: self.into_report(),
})
}
}
impl From<Error> for ContextualError
{
#[inline] fn from(from: Error) -> Self
{
from.with_context()
}
}
impl From<ContextualError> for eyre::Report
{
#[inline] fn from(from: ContextualError) -> Self
{
from.0.from
}
}

@ -0,0 +1,472 @@
//! CLI flags module
use super::*;
use std::{
borrow::Cow,
any::Any,
error,
fmt,
};
use super::error::{
Error,
ContextualError,
};
use ad_hoc_iter::{
maybe::*,
};
pub type ShortFlag= (char,);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind<'a>
{
/// Any number of these long-form arguments starting with `--`, and flag for case-sensitivity.
/// Can be empty to match just `--`
Long(&'a [&'a str], bool),
/// Any number of these short-form (single character) arguments together or apart (starting with `-`).
/// Can be empty for a single `-` character, and a flag for case-sensitivity.
Short(&'a [ShortFlag], bool),
/// A combination of any long + short flags
Abrev(&'a [&'a str], &'a [ShortFlag], bool, bool),
/// A single `--`.
///
/// # Note
/// Shorthand for `Long(&[], _)`
Terminator,
/// A single `-`.
///
/// # Note
/// Shorthand for `Short(&[], _)`
TerminatorShort,
/// An exact match with no prefix and flag for case-sensitivity.
Exact(&'a str, bool),
/// Match everything.
Any,
}
impl<'b> Kind<'b>
{
/// Check if string `value` matches this `Kind`.
///
/// Returns the matched part.
/// # Note
/// In this case of `Short` (or `Abrev` short match), return the rest of the argument string *not* including the matched character
///
/// # Short case-sensitivity
/// Single character matching case sensitivity can produce unexpected results if the lowercase transform of that character yields multiple characters. They are all checked for match.
pub fn is_match<'a, 'c>(&'a self, value: &'b str) -> Option<&'a str>
where 'c: 'a,
'a: 'b
{
match self {
Self::Long(&[], _) |
Self::Abrev(&[], &[], _, _) |
Self::Terminator if value == "--" => Some("--"),
Self::Short(&[], _) |
Self::TerminatorShort if value == "-" => Some("-"),
Self::Abrev(any, _, true, _) |
Self::Long(any, true) if value.starts_with("--") => {
let value = &value[2..];
for &which in any.iter() {
if which == value {
return Some(which);
}
}
None
},
Self::Abrev(any, _, false, _) |
Self::Long(any, false) if value.starts_with("--") => {
let value = value[2..].to_lowercase();
for (orig, which) in any.iter().map(|x| (x, x.to_lowercase())) {
if which == value {
return Some(orig);
}
}
None
},
Self::Abrev(_, any, _, true) |
Self::Short(any, true) if value.starts_with("-") => {
let mut value = value[1..].chars();
let iter = &mut value;
for can_be in iter {
if any.contains(&(can_be,)) {
return Some(value.as_str());
}
}
None
},
Self::Abrev(_, any, _, false) |
Self::Short(any, false) if value.starts_with("-") => {
let mut value = value[1..].chars();
let iter = &mut value;
let any: Vec<_> = any.iter().flat_map(|(chr,)| chr.to_lowercase()).collect();
for can_be in iter.flat_map(|x| x.to_lowercase()) {
if any.contains(&can_be) {
return Some(value.as_str());
}
}
None
},
Self::Exact(exact, false) if value.to_lowercase() == exact.to_lowercase() => Some(value),
Self::Exact(exact, true) if &value == exact => Some(value),
Self::Any => Some(value),
_ => None,
}
}
/// Create a reference to this `Kind` with a lower lifetime.
pub fn as_ref<'a>(&'a self) -> Kind<'a>
{
match &self {
Self::Abrev(strs, chrs, sl, cl) => Kind::Abrev(strs,chrs, *sl, *cl),
Self::Long(strs, bl) => Kind::Long(strs, *bl),
Self::Short(chrs, bl) => Kind::Short(chrs, *bl),
Self::Terminator => Kind::Terminator,
Self::TerminatorShort => Kind::TerminatorShort,
Self::Exact(stri, bl) => Kind::Exact(&stri[..], *bl),
Self::Any => Kind::Any,
}
}
/// Split `Kind::Abrev` into short and long
pub fn split_abrev(&self) -> Result<(Kind<'_>, Kind<'_>), &Self>
{
match self {
Self::Abrev(long, short, lcase, scase) => {
Ok((Kind::Long(long,*lcase), Kind::Short(short, *scase)))
},
e => Err(e)
}
}
}
/// Helper for `Cow` variants
macro_rules! cow
{
(& $ex:expr) => (Cow::Borrowed($ex.into()));
(~ $ex:expr) => (Cow::Owned($ex.into()));
}
impl<'a> fmt::Display for Kind<'a>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
//TODO: Change for this to write directly to `f` instead of allocating temporary strings and `Cow`ing to it.
write!(f, "{}", match self {
Self::Terminator |
Self::Long(&[], _) |
Self::Abrev(&[], &[], _, _) => cow!(& "--"),
Self::TerminatorShort |
Self::Short(&[], _) => cow!(& "-"),
Self::Exact(ex, case) => cow!(~ format!("`{}`{}", ex, if *case {""} else {"i"})),
Self::Any => cow!(& "..."),
Self::Long(slice, false) |
Self::Abrev(slice, &[], false, _)
=> cow!(~ slice.iter()
.map(|string| format!("--{}", string))
.join(", ")),
Self::Long(slice, true) |
Self::Abrev(slice, &[], true, _)
=> cow!(~ {
let mut string = slice.iter()
.map(|string| format!("--{}", string))
.join(", ");
string.push_str(" (case sensitive)");
string
}),
Self::Short(slice, true) |
Self::Abrev(&[], slice, _, true)
=> cow!(~ std::iter::once('-')
.chain(slice.iter().map(|x| x.0))
.collect::<String>()),
Self::Short(slice, false) |
Self::Abrev(&[], slice, _, false)
=> cow!(~ {
let mut string = std::iter::once('-')
.chain(slice.iter().map(|x| x.0))
.collect::<String>();
string.push_str(" (case insensitive)");
string
}),
Self::Abrev(long, short, lcase, scase) => return write!(f, "{}; {}", Self::Long(long, *lcase), Self::Short(short, *scase)),
})
}
}
/// What value(s) (if any) does this argument need
#[derive(Debug, Clone, PartialEq)]
pub enum ValueKind
{
/// No values, this is a boolean flag
None,
/// A value in the form of `--option=value`.
///
/// If user provides without an `=value` part, the `held` state will be empty.
Equals,
/// The next `n` arguments are the values
///
/// If user provides less than `n` arguments after this one, arg parsing will fail with an error.
Next(usize),
/// The whole rest of the iterator is the values
Rest,
}
impl Default for ValueKind
{
#[inline]
fn default() -> Self
{
Self::None
}
}
pub struct ArgState<T=()>
{
user: T,
flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback<T, Box<dyn Any + 'static>, eyre::Report> + 'static)>, bool, bool)>,
}
pub trait FlagCallback<U, T,E>: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>{}
impl<U, F, T, E> FlagCallback<U, T,E> for F
where F: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>,
T: Any + 'static,
E: Into<eyre::Report> {}
impl<U> ArgState<U>
{
/// Push a handle to the state
pub fn push<T: Any + 'static,E: Into<eyre::Report>, F>(&mut self, kind: Kind<'static>, desc: impl Into<Cow<'static, str>>, value: ValueKind, mut handle: F, single: bool) -> usize
where F: FlagCallback<U, T,E> + 'static
{
let handle: Box<dyn FlagCallback<U, Box<(dyn Any + 'static)>, eyre::Report>> =
Box::new(move |state, index, value| handle(state, index, value)
.map(|x| -> Box<(dyn Any + 'static)> {Box::new(x)})
.map_err(Into::into));
self.flags.push((kind, desc.into(), value, handle, single, true));
self.flags.len()-1
}
}
/// A handle to mutate the arg state.
pub struct StateHandle<'a, T>
{
held: MaybeMany<String, Vec<String>>,
state: &'a mut T,
args: &'a mut (dyn Iterator<Item=String> + 'a),
chk: &'a mut bool,
idx: usize,
}
impl<'a, T> StateHandle<'a,T>
{
/// The user defined state
pub fn state(&self) -> &T
{
self.state
}
/// The mutable user defined state
pub fn state_mut(&mut self) -> &mut T
{
self.state
}
/// The current args iterator
pub fn args(&self) -> &(dyn Iterator<Item=String> + 'a)
{
self.args
}
/// The mutable current args iterator
pub fn args_mut(&mut self) -> &mut (dyn Iterator<Item = String> + 'a)
{
self.args
}
/// Is this callback enabled?
///
/// It will always start as `true`. This can be mutated to `false` to disable further checks for this argument.
pub fn enabled_mut(&mut self) -> &mut bool
{
self.chk
}
/// Is this callback enabled?
///
/// This should always be `true`.
pub fn enabled(&mut self) -> bool
{
*self.chk
}
/// The index of the current arg
pub fn index(&self) -> usize
{
self.idx
}
/// The held values taken from the argument iterator as specified by the argument's definition.
///
/// # Note
/// This moves the values, if you want to call this more than once, use `clone_held`.
/// If called more than once will yield no values
pub fn extract_held(&mut self) -> MaybeManyIter<String, std::vec::IntoIter<String>>
{
self.held.take().into_iter()
}
/// Clone the held values taken from the argument iterator for this invokation.
///
/// # Note
/// This clones all the values, instead of moving like `extract_held`, so multiple calls to this will yield the same clones.
/// However, if `extract_held` has been called, this will yield no values.
pub fn clone_held(&self) -> MaybeManyIter<String, std::vec::IntoIter<String>>
{
self.held.clone().into_iter()
}
}
impl<U> ArgState<U>
{
/// Parse this argument iterator.
///
/// # 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<I,T: Into<String>>(&mut self, input: I) -> Result<Vec<Box<dyn Any+'static>>, ContextualError>
where I: Iterator<Item=T>
{
let mut output = Vec::with_capacity(self.flags.len());
let mut input = input.map(Into::into).fuse();
let mut i=0;
while let Some(arg) = input.next()
{
for (kind, desc, value, callback, single, chk) in self.flags.iter_mut().filter(|(_,_,_,_,_,chk)| *chk)
{
if *single {
*chk = false;
}
let (arg, held) = {
let arg = &arg[..];
if let ValueKind::Equals = &value {
match arg.split_once("=") {
Some((a,v)) => (a, MaybeMany::one(v.to_owned())),
_ => (arg, MaybeMany::None),
}
} else {
(arg, MaybeMany::None)
}
};
if let Some(kind_match) = kind.is_match(&arg[..]) {
let held = {
match value {
ValueKind::Next(n) => {
let n = *n;
let mut held = held.map_single_into_many(|x| vec![x]);
let taken = held.on_many(|vec| {
let l = vec.len();
vec.extend((&mut input).take(n));
vec.len() - l
}).unwrap();
if taken != n {
return Err(Error{flag: kind.clone(),
from: Err::<!,_>(eyre!("Not enough args"))
.with_note(|| format!("Expected {} items, only got {}", n, taken))
.with_suggestion(|| format!("See `-help` for {}", arg)).unwrap_err(),
desc: Some(desc.clone().into_owned()),
arg: (i, arg.to_owned())})?;
}
held
},
ValueKind::Rest => {
let mut held = held.map_single_into_many(|x| vec![x]);
held.on_many(|vec| vec.extend(&mut input));
held
},
_ => held.map_none(),
}
};
output.push(callback(&mut StateHandle{
state: &mut self.user,
args: &mut input,
chk,
idx: i,
held, //This will be for either one `=<value>` or many `<values...>` from args as according to `value`.
}, i, kind_match)
.map_err(|err| Error{flag: kind.clone(), from: err, desc: Some(desc.clone().into_owned()), arg: (i, arg.to_owned())})?);
}
}
i+=1;
}
Ok(output)
}
/// Consume into the user-provided state value
#[inline] pub fn into_inner(self) -> U
{
self.user
}
}
#[cfg(test)]
mod tests
{
use super::*;
use ad_hoc_iter::iter;
#[test]
fn errors() -> Result<(), eyre::Report>
{
color_eyre::install()?;
let err= Error {
flag: Kind::Abrev(&["test", "real-test", "more-tests"], &[('t',), ('R',)], false, true),
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(())
}
#[test]
fn kind_is_match()
{
let kd = Kind::Abrev(&["logging:high", "verbose", "verbose-mode"], &[('V',)], true, true);
let args = iter!["--logging:high", "--verbose", "-abcVdfg"];
for arg in args
{
assert!(kd.is_match(arg).is_some());
}
}
}

@ -1,6 +1,9 @@
//! Parse args
use super::*;
pub mod error;
pub mod flags;
pub fn usage() -> !
{
println!(r#"Usage: {program} <files...>

@ -1,429 +0,0 @@
//! CLI flags module
use super::*;
use std::{
borrow::Cow,
any::Any,
error,
fmt,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind<'a>
{
/// Any number of these long-form arguments starting with `--`, and flag for case-sensitivity.
/// Can be empty to match any argument (not needing a prefix).
Long(&'a [&'a str], bool),
/// Any number of these short-form (single character) arguments together or apart (starting with `-`).
/// Can be empty for a single `-` character, and a flag for case-sensitivity.
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)]
pub enum ValueKind
{
None,
Equals,
Next(usize),
Rest,
}
pub struct ArgState<T=()>
{
user: T,
flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback<T, Box<dyn Any + 'static>, eyre::Report> + 'static)>, bool, bool)>,
}
pub trait FlagCallback<U, T,E>: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>{}
impl<U, F, T, E> FlagCallback<U, T,E> for F
where F: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>,
T: Any + 'static,
E: Into<eyre::Report> {}
impl<U> ArgState<U>
{
/// Push a handle to the state
pub fn push<T: Any + 'static,E: Into<eyre::Report>, F>(&mut self, kind: Kind<'static>, desc: impl Into<Cow<'static, str>>, value: ValueKind, mut handle: F, single: bool) -> usize
where F: FlagCallback<U, T,E> + 'static
{
let handle: Box<dyn FlagCallback<U, Box<(dyn Any + 'static)>, eyre::Report>> =
Box::new(move |state, index, value| handle(state, index, value)
.map(|x| -> Box<(dyn Any + 'static)> {Box::new(x)})
.map_err(Into::into));
self.flags.push((kind, desc.into(), value, handle, single, true));
self.flags.len()-1
}
}
#[derive(Debug, Clone)]
enum Either<T,U>
{
None,
One(T),
Many(U),
}
impl<T,U> Either<T,U>
{
pub fn take(&mut self) -> Self
{
std::mem::replace(self, Self::None)
}
}
#[derive(Debug, Clone)]
pub enum EitherIter<T,U>
{
None,
One(std::iter::Once<T>),
Many(std::iter::Fuse<U>),
}
impl<T,U> Iterator for EitherIter<T,U>
where U: Iterator<Item=T>
{
type Item = T;
fn next(&mut self) -> Option<Self::Item>
{
match self {
Self::None => None,
Self::One(one) => one.next(),
Self::Many(many) => many.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::None => (0, Some(0)),
Self::One(_) => (1, Some(1)),
Self::Many(many) => many.size_hint(),
}
}
}
impl<T,U: Iterator<Item=T>> std::iter::FusedIterator for EitherIter<T,U>{}
impl<T,U: Iterator<Item=T>> std::iter::ExactSizeIterator for EitherIter<T,U>
where U: ExactSizeIterator{}
impl<T, U: IntoIterator<Item=T>> IntoIterator for Either<T, U>
{
type Item= T;
type IntoIter = EitherIter<T, <U as IntoIterator>::IntoIter>;
fn into_iter(self) -> Self::IntoIter
{
match self {
Self::None => EitherIter::None,
Self::One(one) => EitherIter::One(std::iter::once(one)),
Self::Many(many) => EitherIter::Many(many.into_iter().fuse())
}
}
}
/// A handle to mutate the arg state.
pub struct StateHandle<'a, T>
{
held: Either<String, Vec<String>>,
state: &'a mut T,
args: &'a mut (dyn Iterator<Item=String> + 'a),
chk: &'a mut bool,
idx: usize,
}
impl<'a, T> StateHandle<'a,T>
{
/// The user defined state
pub fn state(&self) -> &T
{
self.state
}
/// The mutable user defined state
pub fn state_mut(&mut self) -> &mut T
{
self.state
}
/// The current args iterator
pub fn args(&self) -> &(dyn Iterator<Item=String> + 'a)
{
self.args
}
/// The mutable current args iterator
pub fn args_mut(&mut self) -> &mut (dyn Iterator<Item = String> + 'a)
{
self.args
}
/// Is this callback enabled?
///
/// It will always start as `true`. This can be mutated to `false` to disable further checks for this argument.
pub fn enabled_mut(&mut self) -> &mut bool
{
self.chk
}
/// Is this callback enabled?
///
/// This should always be `true`.
pub fn enabled(&mut self) -> bool
{
*self.chk
}
/// The index of the current arg
pub fn index(&self) -> usize
{
self.idx
}
/// The held values taken from the argument iterator as specified by the argument's definition.
///
/// # Note
/// This moves the values, if you want to call this more than once, use `clone_held`.
/// If called more than once will yield no values
pub fn extract_held(&mut self) -> EitherIter<String, std::vec::IntoIter<String>>
{
self.held.take().into_iter()
}
/// Clone the held values taken from the argument iterator for this invokation.
///
/// # Note
/// This clones all the values, instead of moving like `extract_held`, so multiple calls to this will yield the same clones.
/// However, if `extract_held` has been called, this will yield no values.
pub fn clone_held(&self) -> EitherIter<String, std::vec::IntoIter<String>>
{
self.held.clone().into_iter()
}
}
impl<U> ArgState<U>
{
/// Parse this argument iterator.
///
/// # 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<I,T: Into<String>>(&mut self, input: I) -> Result<Vec<Box<dyn Any+'static>>, ContextualError>
where I: Iterator<Item=T>
{
let mut output = Vec::with_capacity(self.flags.len());
let mut input = input.map(Into::into).fuse();
let mut i=0;
while let Some(arg) = input.next()
{
for (kind, desc, value, callback, single, chk) in self.flags.iter_mut().filter(|(_,_,_,_,_,chk)| *chk)
{
if *single {
*chk = false;
}
//TODO: Check Kind matches `arg`, check `value` matches `input` and get the values.
output.push(callback(&mut StateHandle{
state: &mut self.user,
args: &mut input,
chk,
idx: i,
held: Either::None, //TODO: This will be for either one `=<value>` or many `<values...>` from args as according to `value`.
}, i, &arg[..])
.map_err(|err| Error{flag: kind.clone(), from: err, desc: Some(desc.clone().into_owned()), arg: (i, arg.clone())})?);
}
i+=1;
}
Ok(output)
}
/// Consume into the user-provided state value
#[inline] pub fn into_inner(self) -> U
{
self.user
}
}
/// Error without context
#[derive(Debug)]
pub struct Error
{
flag: Kind<'static>,
arg: (usize, String),
desc: Option<String>,
from: eyre::Report,
}
/// An argument parsing error with context
#[repr(transparent)]
#[derive(Debug)]
pub struct ContextualError(Error);
#[derive(Debug, Clone, Copy)]
pub struct ErrorContext<'a>
{
/// The error-throwing flag
pub flag: &'a Kind<'static>,
/// The argument string
pub arg: &'a String,
/// The index of the argument string
pub arg_idx: &'a usize,
/// Message from the failing flag callback, if any
pub desc: Option<&'a String>,
/// The error report
pub from: &'a eyre::Report,
}
impl ContextualError
{
/// 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)> {
self.from.source()
}
}
impl fmt::Display for Error
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
writeln!(f, "Failed to parse args")
}
}
impl fmt::Display for ContextualError
{
#[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 {
err
}.unwrap_err()
}
/// Add context to this error
pub fn with_context(self) -> ContextualError
{
ContextualError(Self {
flag: self.flag.clone(),
arg: self.arg.clone(),
desc: self.desc.clone(),
from: self.into_report(),
})
}
}
impl From<Error> for ContextualError
{
#[inline] fn from(from: Error) -> Self
{
from.with_context()
}
}
impl From<ContextualError> for eyre::Report
{
#[inline] fn from(from: ContextualError) -> 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(())
}
}

@ -1,5 +1,6 @@
#![cfg_attr(nightly, feature(never_type))]
#![cfg_attr(nightly, feature(drain_filter))]
#![cfg_attr(nightly, feature(str_split_once))]
#![allow(dead_code)]
@ -34,7 +35,6 @@ mod dedup;
mod resolve;
mod database;
mod flags;
mod args;
mod config;

@ -135,3 +135,8 @@ pub fn defer<F: FnOnce()>(fun: F) -> impl std::ops::Drop
}
DropWrap(Some(fun))
}
/// Simplification for single expression `if x {y} else {z}`
#[macro_export] macro_rules! tern {
($if:expr; $then:expr, $else:expr) => (if $if {$then} else {$else});
}

Loading…
Cancel
Save