Skip to content

Commit

Permalink
Add ChoiceParameter trait instead of direct impls
Browse files Browse the repository at this point in the history
Fixes #126
  • Loading branch information
kangalio authored and GnomedDev committed Nov 26, 2023
1 parent 8025fe7 commit bc250b8
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 52 deletions.
2 changes: 2 additions & 0 deletions examples/fluent_localization/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub async fn welcome(
user: serenity::User,
message: WelcomeChoice,
) -> Result<(), Error> {
use poise::ChoiceParameter as _;

ctx.say(format!("<@{}> {}", user.id.0, tr!(ctx, message.name())))
.await?;
Ok(())
Expand Down
66 changes: 16 additions & 50 deletions macros/src/choice_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,76 +64,42 @@ pub fn choice_parameter(input: syn::DeriveInput) -> Result<TokenStream, darling:
}

let enum_ident = &input.ident;
let indices = 0_u64..(variant_idents.len() as _);
let indices = 0..variant_idents.len();
Ok(quote::quote! {
#[poise::async_trait]
impl poise::SlashArgument for #enum_ident {
async fn extract(
_: &poise::serenity_prelude::Context,
_: poise::ApplicationCommandOrAutocompleteInteraction<'_>,
value: &poise::serenity_prelude::json::Value,
) -> ::std::result::Result<Self, poise::SlashArgError> {
use poise::serenity_prelude::json::prelude::*;
let choice_key = value
.as_u64()
.ok_or(poise::SlashArgError::CommandStructureMismatch(
"expected u64",
))?;

match choice_key {
#( #indices => Ok(Self::#variant_idents), )*
_ => Err(poise::SlashArgError::CommandStructureMismatch("out of bounds choice key")),
}
}

fn create(builder: &mut poise::serenity_prelude::CreateApplicationCommandOption) {
builder.kind(poise::serenity_prelude::CommandOptionType::Integer);
}

fn choices() -> Vec<poise::CommandParameterChoice> {
impl poise::ChoiceParameter for #enum_ident {
fn list() -> Vec<poise::CommandParameterChoice> {
vec![ #( poise::CommandParameterChoice {
__non_exhaustive: (),
name: #names.to_string(),
localizations: std::collections::HashMap::from([
#( (#locales.to_string(), #localized_names.to_string()) ),*
]),
__non_exhaustive: (),
}, )* ]
}
}

impl std::str::FromStr for #enum_ident {
type Err = poise::InvalidChoice;

fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
#(
if s.eq_ignore_ascii_case(#names)
#( || s.eq_ignore_ascii_case(#alternative_names) )*
{
Ok(Self::#variant_idents)
} else
)* {
Err(poise::InvalidChoice::default())
fn from_index(index: usize) -> Option<Self> {
match index {
#( #indices => Some(Self::#variant_idents), )*
_ => None,
}
}
}

impl std::fmt::Display for #enum_ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
fn from_name(name: &str) -> Option<Self> {
#( if name.eq_ignore_ascii_case(#names)
#( || name.eq_ignore_ascii_case(#alternative_names) )*
{
return Some(Self::#variant_idents);
} )*
None
}
}

impl #enum_ident {
/// Returns the non-localized name of this choice
pub fn name(&self) -> &'static str {
fn name(&self) -> &'static str {
match self {
#( Self::#variant_idents => #names, )*
}
}

/// Returns the localized name for the given locale, if one is set
pub fn localized_name(&self, locale: &str) -> Option<&'static str> {
fn localized_name(&self, locale: &str) -> Option<&'static str> {
match self {
#( Self::#variant_idents => match locale {
#( #locales => Some(#localized_names), )*
Expand Down
1 change: 1 addition & 0 deletions macros/src/command/slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub fn generate_parameters(inv: &Invocation) -> Result<Vec<proc_macro2::TokenStr
false => quote::quote! { None },
};
// TODO: theoretically a problem that we don't store choices for non slash commands
// TODO: move this to poise::CommandParameter::choices (is there a reason not to?)
let choices = match inv.args.slash_command {
true => quote::quote! { poise::slash_argument_choices!(#type_) },
false => quote::quote! { vec![] },
Expand Down
75 changes: 75 additions & 0 deletions src/choice_parameter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Contains the [`ChoiceParameter`] trait and the blanket [`crate::SlashArgument`] and
//! [`crate::PopArgument`] impl

use crate::serenity_prelude as serenity;

/// This trait is implemented by [`crate::macros::ChoiceParameter`]. See its docs for more
/// information
pub trait ChoiceParameter: Sized {
/// Returns all possible choices for this parameter, in the order they will appear in Discord.
fn list() -> Vec<crate::CommandParameterChoice>;

/// Returns an instance of [`Self`] corresponding to the given index into [`Self::list()`]
fn from_index(index: usize) -> Option<Self>;

/// Parses the name as returned by [`Self::name()`] into an instance of [`Self`]
fn from_name(name: &str) -> Option<Self>;

/// Returns the non-localized name of this choice
fn name(&self) -> &'static str;

/// Returns the localized name for the given locale, if one is set
fn localized_name(&self, locale: &str) -> Option<&'static str>;
}

#[async_trait::async_trait]
impl<T: ChoiceParameter> crate::SlashArgument for T {
async fn extract(
_: &serenity::Context,
_: crate::ApplicationCommandOrAutocompleteInteraction<'_>,
value: &serenity::json::Value,
) -> ::std::result::Result<Self, crate::SlashArgError> {
let choice_key = value
.as_u64()
.ok_or(crate::SlashArgError::CommandStructureMismatch(
"expected u64",
))?;

Self::from_index(choice_key as _).ok_or(crate::SlashArgError::CommandStructureMismatch(
"out of bounds choice key",
))
}

fn create(builder: &mut serenity::CreateApplicationCommandOption) {
builder.kind(serenity::CommandOptionType::Integer);
}

fn choices() -> Vec<crate::CommandParameterChoice> {
Self::list()
}
}

#[async_trait::async_trait]
impl<'a, T: ChoiceParameter> crate::PopArgument<'a> for T {
async fn pop_from(
args: &'a str,
attachment_index: usize,
ctx: &serenity::Context,
msg: &serenity::Message,
) -> Result<(&'a str, usize, Self), (Box<dyn std::error::Error + Send + Sync>, Option<String>)>
{
let (args, attachment_index, s) =
crate::pop_prefix_argument!(String, args, attachment_index, ctx, msg).await?;

Ok((
args,
attachment_index,
Self::from_name(&s).ok_or((
Box::new(crate::InvalidChoice {
__non_exhaustive: (),
}) as Box<dyn std::error::Error + Send + Sync>,
Some(s),
))?,
))
}
}
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ Also, poise is a stat in Dark Souls
*/

pub mod builtins;
pub mod choice_parameter;
pub mod cooldown;
pub mod dispatch;
pub mod event;
Expand All @@ -403,8 +404,8 @@ pub mod macros {

#[doc(no_inline)]
pub use {
cooldown::*, dispatch::*, event::*, framework::*, macros::*, modal::*, prefix_argument::*,
reply::*, slash_argument::*, structs::*, track_edits::*,
choice_parameter::*, cooldown::*, dispatch::*, event::*, framework::*, macros::*, modal::*,
prefix_argument::*, reply::*, slash_argument::*, structs::*, track_edits::*,
};

/// See [`builtins`]
Expand Down

0 comments on commit bc250b8

Please sign in to comment.