diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000..7613aba --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,276 @@ +//! 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, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ValueKind +{ + None, + Equals, + Next(usize), + Rest, +} + +pub struct ArgState +{ + user: T, + + flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback, Box> + '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{} + +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 + where F: FlagCallback + 'static + { + let handle: Box, Box<(dyn error::Error + 'static)>>> = + 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)})); + self.flags.push((kind, desc.into(), value, handle, single, true)); + self.flags.len()-1 + } +} + +#[derive(Debug, Clone)] +enum Either +{ + None, + One(T), + Many(U), +} + +impl Either +{ + pub fn take(&mut self) -> Self + { + std::mem::replace(self, Self::None) + } +} + +#[derive(Debug, Clone)] +pub enum EitherIter +{ + None, + One(std::iter::Once), + Many(std::iter::Fuse), +} + +impl Iterator for EitherIter +where U: Iterator +{ + type Item = T; + fn next(&mut self) -> Option + { + match self { + Self::None => None, + Self::One(one) => one.next(), + Self::Many(many) => many.next(), + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + Self::None => (0, Some(0)), + Self::One(_) => (1, Some(1)), + Self::Many(many) => many.size_hint(), + } + } +} +impl> std::iter::FusedIterator for EitherIter{} +impl> std::iter::ExactSizeIterator for EitherIter +where U: ExactSizeIterator{} + +impl> IntoIterator for Either +{ + type Item= T; + type IntoIter = EitherIter::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>, + + state: &'a mut T, + args: &'a mut (dyn Iterator + '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 + 'a) + { + self.args + } + /// The mutable current args iterator + pub fn args_mut(&mut self) -> &mut (dyn Iterator + '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> + { + 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> + { + self.held.clone().into_iter() + } +} + + +impl ArgState +{ + /// 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>(&mut self, input: I) -> Result>, Error> + where I: Iterator + { + 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 `=` or many `` 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 + } +} + +#[derive(Debug)] +pub struct Error +{ + flag: Kind<'static>, + arg: (usize, String), + desc: Option, + from: Box, +} + +impl error::Error for Error +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(self.from.as_ref()) + } +} + +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) + } else { + Ok(()) + } + } +} diff --git a/src/main.rs b/src/main.rs index 31f65aa..4ada776 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ mod dedup; mod resolve; mod database; +mod flags; mod args; mod config;