diff options
Diffstat (limited to 'src/framework')
| -rw-r--r-- | src/framework/standard/args.rs | 238 | ||||
| -rw-r--r-- | src/framework/standard/help_commands.rs | 33 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 27 |
3 files changed, 277 insertions, 21 deletions
diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index f59908a..6186cd4 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -83,6 +83,17 @@ impl Args { } /// Removes the first element, parses it to a specific type if necessary, returns. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.single::<i32>().unwrap(), 42); + /// assert_eq!(args, ["69"]); + /// ``` pub fn single<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -95,8 +106,49 @@ impl Args { .parse::<T>()?) } + /// Like [`single`], but does "zero-copy" parsing. + /// + /// Refer to [`FromStrZc`]'s example on how to use this method. + /// + /// [`single`]: #method.single + /// [`FromStrZc`]: trait.FromStrZc.html + pub fn single_zc<'a, T: FromStrZc<'a> + 'a>(&'a mut self) -> Result<T, T::Err> + where T::Err: StdError { + + // This is a hack as to mitigate some nasty lifetime errors. + // + // (Culprit `Vec::remove`s return type) + fn get_and_remove(b: &mut Vec<String>) -> Option<&str> { + struct GetThenRemove<'a>(&'a mut Vec<String>); + + impl<'a> Drop for GetThenRemove<'a> { + fn drop(&mut self) { + if !self.0.is_empty() { + self.0.remove(0); + } + } + } + + GetThenRemove(b).0.get(0).map(|s| s.as_str()) + } + + let a = get_and_remove(&mut self.delimiter_split).ok_or(Error::Eos)?; + Ok(FromStrZc::from_str(a)?) + } + /// Like [`single`], but doesn't remove the element. /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let args = Args::new("42 69", " "); + /// + /// assert_eq!(args.single_n::<i32>().unwrap(), 42); + /// assert_eq!(args, ["42", "69"]); + /// ``` + /// /// [`single`]: #method.single pub fn single_n<T: FromStr>(&self) -> Result<T, T::Err> where T::Err: StdError { @@ -111,10 +163,31 @@ impl Args { } /// Skips if there's a first element, but also returns it. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.skip().unwrap(), "42"); + /// assert_eq!(args, ["69"]); + /// ``` pub fn skip(&mut self) -> Option<String> { self.delimiter_split.shift() } /// Like [`skip`], but allows for multiple at once. /// + /// # Examples + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69 88 99", " "); + /// + /// assert_eq!(*args.skip_for(3).unwrap(), ["42".to_string(), "69".to_string(), "88".to_string()]); + /// assert_eq!(args, ["99"]); + /// ``` + /// /// [`skip`]: #method.skip pub fn skip_for(&mut self, i: u32) -> Option<Vec<String>> { let mut vec = Vec::with_capacity(i as usize); @@ -127,7 +200,18 @@ impl Args { } /// Like [`single`], but takes quotes into account. - /// + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new(r#""42"#, " "); + /// + /// assert_eq!(args.single_quoted::<i32>().unwrap(), 42); + /// assert!(args.is_empty()); + /// ``` + /// /// [`single`]: #method.single pub fn single_quoted<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { @@ -162,20 +246,62 @@ impl Args { } /// Empty outs the internal vector while parsing (if necessary) and returning them + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(*args.list::<i32>().unwrap(), [42, 69]); + /// ``` pub fn list<T: FromStr>(mut self) -> Result<Vec<T>, T::Err> where T::Err: StdError { Iter::<T>::new(&mut self).collect() } /// Provides an iterator of items: (`T: FromStr`) `Result<T, T::Err>`. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("3 4", " "); + /// + /// assert_eq!(*args.iter::<i32>().map(|num| num.unwrap().pow(2)).collect::<Vec<_>>(), [9, 16]); + /// assert!(args.is_empty()); + /// ``` pub fn iter<T: FromStr>(&mut self) -> Iter<T> where T::Err: StdError { Iter::new(self) } /// This method is just `internal_vector.join(delimiter)` + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("42 69", " "); + /// + /// assert_eq!(args.full(), "42 69"); + /// ``` pub fn full(&self) -> String { self.delimiter_split.join(&self.delimiter) } /// Returns the first argument that can be converted and removes it from the list. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("c47 69", " "); + /// + /// assert_eq!(args.find::<i32>().unwrap(), 69); + /// assert_eq!(args, ["c47"]); + /// ``` pub fn find<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -201,6 +327,17 @@ impl Args { } /// Returns the first argument that can be converted and does not remove it from the list. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let args = Args::new("c47 69", " "); + /// + /// assert_eq!(args.find_n::<i32>().unwrap(), 69); + /// assert_eq!(args, ["c47", "69"]); + /// ``` pub fn find_n<T: FromStr>(&self) -> Result<T, T::Err> where T::Err: StdError { if self.delimiter_split.is_empty() { @@ -215,12 +352,111 @@ impl Args { } } +/// A version of `FromStr` that allows for "zero-copy" parsing. +/// +/// # Examples +/// +/// ```rust,ignore +/// use serenity::framework::standard::{Args, FromStrZc}; +/// use std::fmt; +/// +/// struct NameDiscrim<'a>(&'a str, Option<&'a str>); +/// +/// #[derive(Debug)] +/// struct Error(&'static str); +/// +/// impl std::error::Error for Error { +/// fn description(&self) -> &str { self.0 } +/// } +/// +/// impl fmt::Display for Error { +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } +/// } +/// +/// impl<'a> FromStrZc<'a> for NameDiscrim<'a> { +/// type Err = Error; +/// +/// fn from_str(s: &'a str) -> Result<NameDiscrim<'a>, Error> { +/// let mut it = s.split("#"); +/// let name = it.next().ok_or(Error("name must be specified"))?; +/// let discrim = it.next(); +/// Ok(NameDiscrim(name, discrim)) +/// } +/// } +/// +/// let mut args = Args::new("abc#1234", " "); +/// let NameDiscrim(name, discrim) = args.single_zc::<NameDiscrim>().unwrap(); +/// +/// assert_eq!(name, "abc"); +/// assert_eq!(discrim, Some("1234")); +/// ``` +pub trait FromStrZc<'a>: Sized { + type Err; + + fn from_str(s: &'a str) -> ::std::result::Result<Self, Self::Err>; +} + +impl<'a, T: FromStr> FromStrZc<'a> for T { + type Err = T::Err; + + fn from_str(s: &'a str) -> ::std::result::Result<Self, Self::Err> { + <T as FromStr>::from_str(s) + } +} + impl ::std::ops::Deref for Args { type Target = [String]; fn deref(&self) -> &Self::Target { &self.delimiter_split } } +impl<'a> PartialEq<[&'a str]> for Args { + fn eq(&self, other: &[&str]) -> bool { + let mut b = true; + + for (s, o) in self.delimiter_split.iter().zip(other.iter()) { + if s != o { + b = false; + break; + } + } + + b + } +} + +macro_rules! impl_slices { + ($($num:expr),*) => { + impl<'a> PartialEq<[&'a str; 0]> for Args { + fn eq(&self, _: &[&str; 0]) -> bool { + self.delimiter_split.is_empty() + } + } + + $( + impl<'a> PartialEq<[&'a str; $num]> for Args { + fn eq(&self, other: &[&str; $num]) -> bool { + <Args as PartialEq<[&str]>>::eq(self, &other[..]) + } + } + )* + } +} + +impl_slices! { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32 +} + +impl PartialEq for Args { + fn eq(&self, other: &Self) -> bool { + self.delimiter_split == other.delimiter_split + } +} + +impl Eq for Args {} + use std::marker::PhantomData; /// Provides `list`'s functionality, but as an iterator. diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index ce3e406..ef7732b 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -62,9 +62,9 @@ pub fn has_all_requirements(cmd: &Command, msg: &Message) -> bool { if let Ok(permissions) = member.permissions() { if cmd.allowed_roles.is_empty() { - return permissions.administrator() || has_correct_permissions(&cmd, &msg); + return permissions.administrator() || has_correct_permissions(cmd, msg); } else { - return permissions.administrator() || (has_correct_roles(&cmd, &guild, &member) && has_correct_permissions(cmd, msg)); + return permissions.administrator() || (has_correct_roles(cmd, &guild, member) && has_correct_permissions(cmd, msg)); } } } @@ -111,22 +111,20 @@ pub fn with_embeds(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((command_name, cmd)); - } - else { + } else { break; } }, CommandOrAlias::Alias(ref name) => { - let actual_command = group.commands.get(name).unwrap(); + let actual_command = &group.commands[name]; match *actual_command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((name, cmd)); - } - else { + } else { break; } }, @@ -229,12 +227,9 @@ pub fn with_embeds(_: &mut Context, for name in command_names { let cmd = &commands[name]; - if cmd.help_available { - - if cmd.help_available && has_all_requirements(&cmd, &msg) { - let _ = write!(desc, "`{}`\n", name); - has_commands = true; - } + if cmd.help_available && has_all_requirements(cmd, msg) { + let _ = write!(desc, "`{}`\n", name); + has_commands = true; } } @@ -288,7 +283,7 @@ pub fn plain(_: &mut Context, if name == with_prefix || name == *command_name { match *command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((command_name, cmd)); } else { @@ -296,11 +291,11 @@ pub fn plain(_: &mut Context, } }, CommandOrAlias::Alias(ref name) => { - let actual_command = group.commands.get(name).unwrap(); + let actual_command = &group.commands[name]; match *actual_command { CommandOrAlias::Command(ref cmd) => { - if has_all_requirements(&cmd, &msg) { + if has_all_requirements(cmd, msg) { found = Some((name, cmd)); } else { @@ -389,7 +384,7 @@ pub fn plain(_: &mut Context, for name in command_names { let cmd = &commands[name]; - if cmd.help_available && has_all_requirements(&cmd, &msg) { + if cmd.help_available && has_all_requirements(cmd, msg) { let _ = write!(group_help, "`{}` ", name); } } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 6504c6a..36570af 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -14,7 +14,7 @@ pub use self::command::CommandOrAlias; pub use self::configuration::Configuration; pub use self::create_command::CreateCommand; pub use self::create_group::CreateGroup; -pub use self::args::{Args, Error as ArgError}; +pub use self::args::{Args, Iter, FromStrZc, Error as ArgError}; use self::command::{AfterHook, BeforeHook}; use std::collections::HashMap; @@ -138,6 +138,31 @@ pub enum DispatchError { WebhookAuthor, } +use std::fmt; + +impl fmt::Debug for DispatchError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::DispatchError::*; + + match *self { + CheckFailed(..) => write!(f, "DispatchError::CheckFailed"), + CommandDisabled(ref s) => f.debug_tuple("DispatchError::CommandDisabled").field(&s).finish(), + BlockedUser => write!(f, "DispatchError::BlockedUser"), + BlockedGuild => write!(f, "DispatchError::BlockedGuild"), + LackOfPermissions(ref perms) => f.debug_tuple("DispatchError::LackOfPermissions").field(&perms).finish(), + RateLimited(ref num) => f.debug_tuple("DispatchError::RateLimited").field(&num).finish(), + OnlyForDM => write!(f, "DispatchError::OnlyForDM"), + OnlyForOwners => write!(f, "DispatchError::OnlyForOwners"), + OnlyForGuilds => write!(f, "DispatchError::OnlyForGuilds"), + LackingRole => write!(f, "DispatchError::LackingRole"), + NotEnoughArguments { ref min, ref given } => f.debug_struct("DispatchError::NotEnoughArguments").field("min", &min).field("given", &given).finish(), + TooManyArguments { ref max, ref given } => f.debug_struct("DispatchError::TooManyArguments").field("max", &max).field("given", &given).finish(), + IgnoredBot => write!(f, "DispatchError::IgnoredBot"), + WebhookAuthor => write!(f, "DispatchError::WebhookAuthor"), + } + } +} + type DispatchErrorHook = Fn(Context, Message, DispatchError) + Send + Sync + 'static; /// A utility for easily managing dispatches to commands. |