Compare commits
No commits in common. 'master' and 'progress' have entirely different histories.
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in new issue