diff options
Diffstat (limited to 'src/args.rs')
-rw-r--r-- | src/args.rs | 154 |
1 files changed, 144 insertions, 10 deletions
diff --git a/src/args.rs b/src/args.rs index b3bd7ca..5b9ce72 100644 --- a/src/args.rs +++ b/src/args.rs @@ -7,11 +7,14 @@ use ::std::any::Any; use ::std::borrow::Cow; +use ::std::fmt::Display; +use ::std::fmt::Formatter; use ::std::future::Future; use ::std::io::Cursor; use ::std::marker::PhantomData; use ::std::num::ParseFloatError; use ::std::num::ParseIntError; +use ::std::ops::Bound; use ::std::ops::RangeBounds; use ::std::pin::Pin; use ::std::str::FromStr; @@ -28,10 +31,13 @@ pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>) /// An argument parser/validator. /// -/// Note: Iosonism requires arguments to be `Send + Sync`, but for ease when -/// implementing generic argument types, those bounds are not reflected in this -/// trait. Nevertheless, Iosonism doesn't itself use threads, so a [workaround] -/// can be used if one needs non-`Send + Sync` argument types. +/// Note: Iosonism requires argument types to be `Send + Sync`, but for ease +/// when implementing generic argument types, those bounds are not reflected in +/// this trait. Nevertheless, Iosonism doesn't itself use threads, so a +/// [workaround] can be used if one needs non-`Send + Sync` argument types. +/// +/// Additionally, argument types must be `Display`. This *is* reflected in this +/// trait. /// /// [workaround]: https://users.rust-lang.org/t/how-to-check-send-at-runtime-similar-to-how-refcell-checks-borrowing-at-runtime/68269 /// @@ -45,6 +51,7 @@ pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>) /// A very basic `bool` argument type: /// /// ``` +/// use ::std::fmt::Display; /// use ::std::io::Cursor; /// /// use ::iosonism::args::ArgumentType; @@ -64,8 +71,14 @@ pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>) /// reader.read_bool() /// } /// } +/// +/// impl Display for BoolArgumentType { +/// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +/// write!(f, "bool()") +/// } +/// } /// ``` -pub trait ArgumentType<S, E> { +pub trait ArgumentType<S, E>: Display { /// The parsed type of the argument. type Result: Sized + 'static + Any; @@ -92,8 +105,8 @@ pub trait ArgumentType<S, E> { } } -/// Wrapper around `ArgumentType`, but with `Any`. -pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync { +/// Internal wrapper around `ArgumentType`, but with `Any`. +pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync + Display { /// Parses an argument of this type, returning the parsed argument. fn parse<'i>( &self, @@ -111,7 +124,9 @@ pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync { fn get_examples(&self) -> Cow<'static, [&str]>; } -impl<T: ArgumentType<S, E> + Send + Sync, S, E> ArgumentTypeAny<S, E> for T { +/// Any `ArgumentType` that is also `Send` and `Sync` is an `ArgumentTypeAny`. +impl<T, S, E> ArgumentTypeAny<S, E> for T +where T: ArgumentType<S, E> + Send + Sync { fn parse<'i>( &self, reader: &mut Cursor<&'i str>, @@ -132,6 +147,30 @@ impl<T: ArgumentType<S, E> + Send + Sync, S, E> ArgumentTypeAny<S, E> for T { } } +/// Any `dyn ArgumentTypeAny` (note the `dyn`!) is an `ArgumentType`. +impl<S, E> ArgumentType<S, E> for dyn ArgumentTypeAny<S, E> { + type Result = Box<dyn Any>; + + fn parse<'i>( + &self, + reader: &mut Cursor<&'i str>, + ) -> Result<Box<dyn Any>, E> where E: 'i { + self.parse(reader) + } + + fn list_suggestions<'i>( + &self, + context: &CommandContext<'i, S, E>, + builder: SuggestionsBuilder<'i>, + ) -> Pin<Box<dyn Future<Output=Suggestions> + Send + 'i>> { + self.list_suggestions(context, builder) + } + + fn get_examples(&self) -> Cow<'static, [&str]> { + self.get_examples() + } +} + /// A boolean argument. // FIXME add examples/expand docs #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] @@ -174,6 +213,15 @@ where for<'i> E: ReadError<'i, Cursor<&'i str>> } } +/// Formats this `BoolArgumentType`. +/// +/// Always `"bool()"`. +impl Display for BoolArgumentType { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + write!(f, "bool()") + } +} + /// An integer argument. #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] pub struct IntegerArgumentType<T, R: RangeBounds<T>> { @@ -223,7 +271,7 @@ where for<'i> E: ReadError<'i, Cursor<&'i str>>, for<'i> E: RangeError<'i, Cursor<&'i str>, T, R>, R: RangeBounds<T>, - T: PartialOrd<T> + FromStr<Err=ParseIntError> + Any, + T: PartialOrd<T> + FromStr<Err=ParseIntError> + Any + Display, { /// An `IntegerArgumentType` parses an integer type. type Result = T; @@ -249,6 +297,37 @@ where } } +/// Formats this `IntegerArgumentType`. +/// +/// The resulting string follows the syntax `"integer(start,end)"`, with `start` +/// and `end` being one of the below: +/// +/// - `value` if the bound is inclusive. +/// - `value*` if the bound is exclusive. +/// - `-` if it's unbounded. +/// +/// For example, `integer(0*,-)` is an unbounded positive integer, and +/// `integer(1,10)` is an integer between 1 and 10 inclusive. +impl<T: Display, R: RangeBounds<T>> Display for IntegerArgumentType<T, R> { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + let start_bound = self.range.start_bound(); + let end_bound = self.range.end_bound(); + write!(f, "integer(")?; + match start_bound { + Bound::Included(t) => write!(f, "{}", t)?, + Bound::Excluded(t) => write!(f, "{}*", t)?, + Bound::Unbounded => write!(f, "-")?, + } + write!(f, ",")?; + match end_bound { + Bound::Included(t) => write!(f, "{}", t)?, + Bound::Excluded(t) => write!(f, "{}*", t)?, + Bound::Unbounded => write!(f, "-")?, + } + write!(f,")") + } +} + /// A float argument. #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] pub struct FloatArgumentType<T, R: RangeBounds<T>> { @@ -298,7 +377,7 @@ where for<'i> E: ReadError<'i, Cursor<&'i str>>, for<'i> E: RangeError<'i, Cursor<&'i str>, T, R>, R: RangeBounds<T>, - T: PartialOrd<T> + FromStr<Err=ParseFloatError> + Any, + T: PartialOrd<T> + FromStr<Err=ParseFloatError> + Any + Display, { /// A `FloatArgumentType` parses a float type. type Result = T; @@ -324,6 +403,37 @@ where } } +/// Formats this `FloatArgumentType`. +/// +/// The resulting string follows the syntax `"float(start,end)"`, with `start` +/// and `end` being one of the below: +/// +/// - `value` if the bound is inclusive. +/// - `value*` if the bound is exclusive. +/// - `-` if it's unbounded. +/// +/// For example, `float(0*,-)` is an unbounded positive float, and +/// `float(1,10)` is a float between 1.0 and 10.0 inclusive. +impl<T: Display, R: RangeBounds<T>> Display for FloatArgumentType<T, R> { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + let start_bound = self.range.start_bound(); + let end_bound = self.range.end_bound(); + write!(f, "float(")?; + match start_bound { + Bound::Included(t) => write!(f, "{}", t)?, + Bound::Excluded(t) => write!(f, "{}*", t)?, + Bound::Unbounded => write!(f, "-")?, + } + write!(f, ",")?; + match end_bound { + Bound::Included(t) => write!(f, "{}", t)?, + Bound::Excluded(t) => write!(f, "{}*", t)?, + Bound::Unbounded => write!(f, "-")?, + } + write!(f,")") + } +} + /// A string argument. #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] pub struct StringArgumentType(StringMode); @@ -415,3 +525,27 @@ where for<'i> E: ReadError<'i, Cursor<&'i str>> { } } } + +/// Formats this `StringArgumentType`. +/// +/// The resulting string follows the syntax `"string(type)"`, with `type` being +/// one of the below: +/// +/// - `word` if this argument matches a single word. +/// - `"phrase"` if this argument matches a single word or a quoted phrase. +/// - `text ...` if this argument matches any text up to the end of the input. +impl Display for StringArgumentType { + fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { + match self { + Self(StringMode::SingleWord) => { + write!(f, "string(word)") + }, + Self(StringMode::QuotablePhrase) => { + write!(f, "string(\"phrase\")") + }, + Self(StringMode::GreedyPhrase) => { + write!(f, "string(text ...)") + }, + } + } +} |