summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2021-12-11 14:41:30 -0300
committerSoniEx2 <endermoneymod@gmail.com>2021-12-11 14:41:30 -0300
commit81ecde02593b82e141ac51bb7778cfb9fad6c76e (patch)
treecfb2bcd38cfa412bc8a86b82cb662ca0f04cd4c5
parentea604e09b8338b9fdb66c3921ea3dc955168e79b (diff)
Require ArgumentType: Display
-rw-r--r--src/args.rs154
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 ...)")
+            },
+        }
+    }
+}