diff --git a/src/args/error.rs b/src/args/error.rs new file mode 100644 index 0000000..6cdc305 --- /dev/null +++ b/src/args/error.rs @@ -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, + 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 for ContextualError +{ + #[inline] fn from(from: Error) -> Self + { + from.with_context() + } +} + +impl From for eyre::Report +{ + #[inline] fn from(from: ContextualError) -> Self + { + from.0.from + } +} + diff --git a/src/flags.rs b/src/args/flags.rs similarity index 56% rename from src/flags.rs rename to src/args/flags.rs index 0fd77d6..f7851b8 100644 --- a/src/flags.rs +++ b/src/args/flags.rs @@ -6,44 +6,144 @@ use std::{ error, fmt, }; +use super::error::{ + Error, + ContextualError, +}; +use iterators::*; + +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 any argument (not needing a prefix). + /// 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 [(char, )], bool), + 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(&[], false)` Terminator, - /// An exact match with no prefix. - Exact(&'a str), + /// A single `-`. + /// + /// # Note + /// Shorthand for `Short(&[], false)` + TerminatorShort, + /// An exact match with no prefix and flag for case-sensitivity. + Exact(&'a str, bool), + /// Match everything, + Any, } -impl Kind<'static> +impl<'b> Kind<'b> { 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::Exact(stri) => Kind::Exact(&stri[..]), + Self::TerminatorShort => Kind::TerminatorShort, + Self::Exact(stri, bl) => Kind::Exact(&stri[..], *bl), + Self::Any => Kind::Any, } } } + +/// 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::()), + + Self::Short(slice, false) | + Self::Abrev(&[], slice, _, false) + => cow!(~ { + let mut string = std::iter::once('-') + .chain(slice.iter().map(|x| x.0)) + .collect::(); + 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` Equals, + /// The next `n` arguments are the values 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 { user: T, @@ -72,69 +172,6 @@ impl ArgState } } -#[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. @@ -260,149 +297,6 @@ impl ArgState } } -/// Error without context -#[derive(Debug)] -pub struct Error -{ - flag: Kind<'static>, - arg: (usize, String), - desc: Option, - 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 for ContextualError -{ - #[inline] fn from(from: Error) -> Self - { - from.with_context() - } -} - -impl From for eyre::Report -{ - #[inline] fn from(from: ContextualError) -> Self - { - from.0.from - } -} - #[cfg(test)] mod tests @@ -411,10 +305,10 @@ mod tests #[test] fn errors() -> Result<(), eyre::Report> { - //color_eyre::install()?; + color_eyre::install()?; let err= Error { - flag: Kind::Long(&["test"], false), + 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"), diff --git a/src/args.rs b/src/args/mod.rs similarity index 98% rename from src/args.rs rename to src/args/mod.rs index 6079f18..2a19dd0 100644 --- a/src/args.rs +++ b/src/args/mod.rs @@ -1,6 +1,9 @@ //! Parse args use super::*; +pub mod error; +pub mod flags; + pub fn usage() -> ! { println!(r#"Usage: {program} diff --git a/src/iterators.rs b/src/iterators.rs new file mode 100644 index 0000000..b2a20d0 --- /dev/null +++ b/src/iterators.rs @@ -0,0 +1,140 @@ +//! Useful iterators +use super::*; + + +/// An iterator that can yield `0,1,2+` items +#[derive(Debug, Clone)] +pub 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 bespoke iterator type with an exact size +#[macro_export] macro_rules! over { + (@) => (0usize); + (@ $x:tt $($xs:tt)* ) => (1usize + $crate::over!(@ $($xs)*)); + + ($($value:expr),*) => { + { + use ::std::mem::MaybeUninit; + use ::std::ops::Drop; + struct Arr([MaybeUninit; $crate::over!(@ $($value)*)], usize); + impl Arr + { + const LEN: usize = $crate::over!(@ $($value)*); + } + impl Iterator for Arr + { + type Item = T; + fn next(&mut self) -> Option + { + if self.1 >= self.0.len() { + None + } else { + //take one + let one = unsafe { + ::std::mem::replace(&mut self.0[self.1], MaybeUninit::uninit()).assume_init() + }; + self.1+=1; + Some(one) + } + } + + #[inline] fn size_hint(&self) -> (usize, Option) + { + (Self::LEN, Some(Self::LEN)) + } + } + impl ::std::iter::FusedIterator for Arr{} + impl ::std::iter::ExactSizeIterator for Arr{} + + impl Drop for Arr + { + fn drop(&mut self) { + if ::std::mem::needs_drop::() { + for idx in self.1..self.0.len() { + unsafe { + ::std::mem::replace(&mut self.0[idx], MaybeUninit::uninit()).assume_init(); + } + } + } + } + } + + Arr([$(MaybeUninit::new($value)),*], 0) + } + } +} + +#[cfg(test)] +mod tests +{ + #[test] + fn iter_over() + { + const EXPECT: usize = 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1; + let iter = over![10,9,8,7,6,5,4,3,2,1]; + + assert_eq!(iter.len(), 10); + assert_eq!(iter.sum::(), EXPECT); + + } +} diff --git a/src/main.rs b/src/main.rs index 4ada776..192c53e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,11 +30,11 @@ mod ext; use ext::*; mod util; mod dedup; +mod iterators; mod resolve; mod database; -mod flags; mod args; mod config; diff --git a/src/util.rs b/src/util.rs index f11b172..b62ec31 100644 --- a/src/util.rs +++ b/src/util.rs @@ -135,3 +135,8 @@ pub fn defer(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}); +}