Skip to content

Commit

Permalink
feat: improve command localization support
Browse files Browse the repository at this point in the history
  • Loading branch information
baptiste0928 committed May 15, 2024
1 parent 8c1f537 commit 9c2b560
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 385 deletions.
41 changes: 0 additions & 41 deletions twilight-interactions-derive/src/command/description.rs

This file was deleted.

1 change: 0 additions & 1 deletion twilight-interactions-derive/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

mod impls;

mod description;
mod model;
mod subcommand;

Expand Down
78 changes: 32 additions & 46 deletions twilight-interactions-derive/src/command/model/create_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ use syn::{spanned::Spanned, DeriveInput, Error, FieldsNamed, Result};

use super::parse::{channel_type, command_option_value, StructField, TypeAttribute};
use crate::{
command::description::get_description,
parse::{
parsers::FunctionPath,
syntax::{find_attr, optional},
},
localization::{description_expr, name_expr},
parse::syntax::{find_attr, optional, parse_doc},
};

/// Implementation of `CreateCommand` derive macro
Expand Down Expand Up @@ -41,18 +38,16 @@ pub fn impl_create_command(input: DeriveInput, fields: Option<FieldsNamed>) -> R
));
}

let desc = get_description(
&attributes.desc_localizations,
&attributes.desc,
input.span(),
&input.attrs,
)?;

let name = match &attributes.name {
Some(name) => name,
let name = match attributes.name {
Some(name) => String::from(name),
None => return Err(Error::new(attr_span, "missing required attribute `name`")),
};
let name_localizations = localization_field(&attributes.name_localizations);

let name_expr = name_expr(&name, &attributes.name_localizations);
let desc_expr = description_expr(&attributes.desc, &attributes.desc_localizations, || {
parse_doc(&input.attrs, input.span())
})?;

let default_permissions = match &attributes.default_permissions {
Some(path) => quote! { ::std::option::Option::Some(#path())},
None => quote! { ::std::option::Option::None },
Expand All @@ -74,12 +69,14 @@ pub fn impl_create_command(input: DeriveInput, fields: Option<FieldsNamed>) -> R

#(#field_options)*

let desc = #desc;
let __command_name = #name_expr;
let __command_desc = #desc_expr;

::twilight_interactions::command::ApplicationCommandData {
name: ::std::convert::From::from(#name),
name_localizations: #name_localizations,
description: desc.0,
description_localizations: desc.1,
name: __command_name.fallback,
name_localizations: __command_name.localizations,
description: __command_desc.fallback,
description_localizations: __command_desc.localizations,
options: __command_options,
default_member_permissions: #default_permissions,
dm_permission: #dm_permission,
Expand All @@ -96,15 +93,15 @@ fn field_option(field: &StructField) -> Result<TokenStream> {
let ty = &field.ty;
let span = field.span;

let desc = get_description(
&field.attributes.desc_localizations,
let name = field.attributes.name_default(field.ident.to_string());
let name_expr = name_expr(&name, &field.attributes.name_localizations);

let desc_expr = description_expr(
&field.attributes.desc,
field.span,
&field.raw_attrs,
&field.attributes.desc_localizations,
|| parse_doc(&field.raw_attrs, span),
)?;

let name = field.attributes.name_default(field.ident.to_string());
let name_localizations = localization_field(&field.attributes.name_localizations);
let required = field.kind.required();
let autocomplete = field.attributes.autocomplete;
let max_value = command_option_value(field.attributes.max_value);
Expand All @@ -119,14 +116,16 @@ fn field_option(field: &StructField) -> Result<TokenStream> {
quote! { ::std::option::Option::Some(::std::vec![#(#items),*]) }
};

Ok(quote_spanned! {span=>
let desc = #desc;
Ok(quote_spanned! {span => {
let __field_desc = #desc_expr;
let __field_name = #name_expr;

__command_options.push(<#ty as ::twilight_interactions::command::CreateOption>::create_option(
::twilight_interactions::command::internal::CreateOptionData {
name: ::std::convert::From::from(#name),
name_localizations: #name_localizations,
description: desc.0,
description_localizations: desc.1,
name: __field_name.fallback,
name_localizations: __field_name.localizations,
description: __field_desc.fallback,
description_localizations: __field_desc.localizations,
required: ::std::option::Option::Some(#required),
autocomplete: #autocomplete,
data: ::twilight_interactions::command::internal::CommandOptionData {
Expand All @@ -138,20 +137,7 @@ fn field_option(field: &StructField) -> Result<TokenStream> {
},
}
));
})
}

fn localization_field(path: &Option<FunctionPath>) -> TokenStream {
match path {
Some(path) => {
quote! {
::std::option::Option::Some(
::twilight_interactions::command::internal::convert_localizations(#path())
)
}
}
None => quote! { ::std::option::Option::None },
}
}})
}

/// Ensure optional options are after required ones
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ use syn::{spanned::Spanned, DeriveInput, Error, Result, Variant};

use super::parse::{ParsedVariant, TypeAttribute};
use crate::{
command::description::get_description,
parse::{
parsers::FunctionPath,
syntax::{find_attr, optional},
},
localization::{description_expr, name_expr},
parse::syntax::{find_attr, optional, parse_doc},
};

/// Implementation of `CreateCommand` derive macro
Expand All @@ -21,7 +18,7 @@ pub fn impl_create_command(
let where_clause = &generics.where_clause;

let variants = ParsedVariant::from_variants(variants, input.span())?;
let attribute = match find_attr(&input.attrs, "command") {
let attributes = match find_attr(&input.attrs, "command") {
Some(attr) => TypeAttribute::parse(attr)?,
None => {
return Err(Error::new_spanned(
Expand All @@ -31,22 +28,20 @@ pub fn impl_create_command(
}
};

let desc = get_description(
&attribute.desc_localizations,
&attribute.desc,
input.span(),
&input.attrs,
)?;
let name = String::from(attributes.name);
let name_expr = name_expr(&name, &attributes.name_localizations);

let desc_expr = description_expr(&attributes.desc, &attributes.desc_localizations, || {
parse_doc(&input.attrs, input.span())
})?;

let capacity = variants.len();
let name = &attribute.name;
let name_localizations = localization_field(&attribute.name_localizations);
let default_permissions = match &attribute.default_permissions {
let default_permissions = match &attributes.default_permissions {
Some(path) => quote! { ::std::option::Option::Some(#path())},
None => quote! { ::std::option::Option::None },
};
let dm_permission = optional(attribute.dm_permission);
let nsfw = optional(attribute.nsfw);
let dm_permission = optional(attributes.dm_permission);
let nsfw = optional(attributes.nsfw);

let variant_options = variants.iter().map(variant_option);

Expand All @@ -55,16 +50,17 @@ pub fn impl_create_command(
const NAME: &'static str = #name;

fn create_command() -> ::twilight_interactions::command::ApplicationCommandData {
let desc = #desc;
let __command_name = #name_expr;
let __command_desc = #desc_expr;
let mut __command_options = ::std::vec::Vec::with_capacity(#capacity);

#(#variant_options)*

::twilight_interactions::command::ApplicationCommandData {
name: ::std::convert::From::from(#name),
name_localizations: #name_localizations,
description: desc.0,
description_localizations: desc.1,
name: __command_name.fallback,
name_localizations: __command_name.localizations,
description: __command_desc.fallback,
description_localizations: __command_desc.localizations,
options: __command_options,
default_member_permissions: #default_permissions,
dm_permission: #dm_permission,
Expand All @@ -76,19 +72,6 @@ pub fn impl_create_command(
})
}

fn localization_field(path: &Option<FunctionPath>) -> TokenStream {
match path {
Some(path) => {
quote! {
::std::option::Option::Some(
::twilight_interactions::command::internal::convert_localizations(#path())
)
}
}
None => quote! { ::std::option::Option::None },
}
}

/// Generate variant option code
fn variant_option(variant: &ParsedVariant) -> TokenStream {
let ty = &variant.inner;
Expand Down
1 change: 1 addition & 0 deletions twilight-interactions-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! information.

mod command;
mod localization;
mod option;
mod parse;

Expand Down
47 changes: 47 additions & 0 deletions twilight-interactions-derive/src/localization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::parse::parsers::{CommandDescription, FunctionPath};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Error, Result};

/// Parse the description and localizations from the command attributes.
///
/// Description can be specified using the `desc` or `desc_localizations`
/// attributes, which are mutually exclusive.
///
/// If no description is found, the documentation comment is parsed from the
/// item attributes.
pub fn description_expr(
desc: &Option<CommandDescription>,
localizations: &Option<FunctionPath>,
default: impl FnOnce() -> Result<String>,
) -> Result<TokenStream> {
let localizations_span = localizations.span();

let description = match (desc, localizations) {
(Some(desc), None) => desc.to_token_stream(),
(None, Some(path)) => quote! { #path()},
(None, None) => default()?.to_token_stream(),
(Some(_), Some(_)) => {
return Err(Error::new(
localizations_span,
"`desc` and `desc_localizations` are mutually exclusive",
))
}
};

Ok(quote_spanned! { localizations_span =>
::twilight_interactions::command::internal::IntoLocalizationsInternal::into_localizations(#description)
})
}

pub fn name_expr(name: &str, name_localizations: &Option<FunctionPath>) -> TokenStream {
let localizations_span = name_localizations.span();
let name_localizations = match name_localizations {
Some(path) => quote! { ::std::option::Option::Some(#path())},
None => quote! { ::std::option::Option::None },
};

quote_spanned! { localizations_span =>
::twilight_interactions::command::internal::IntoLocalizationsInternal::into_localizations((#name, #name_localizations))
}
}
7 changes: 4 additions & 3 deletions twilight-interactions-derive/src/option/command_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ fn parsed_init(kind: ChoiceKind) -> TokenStream {

/// Expression in match block
fn match_expr(kind: ChoiceKind) -> TokenStream {
match kind {
ChoiceKind::String => quote!(__parsed.as_str()),
_ => quote!(&__parsed),
if kind == ChoiceKind::String {
quote! { __parsed.as_str() }
} else {
quote! { &__parsed }
}
}

Expand Down
25 changes: 10 additions & 15 deletions twilight-interactions-derive/src/option/create_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, DeriveInput, Error, Ident, Result};

use crate::localization::name_expr;

use super::parse::{ChoiceKind, ChoiceValue, ParsedVariant};

pub fn impl_create_option(input: DeriveInput) -> Result<TokenStream> {
Expand Down Expand Up @@ -57,17 +59,9 @@ pub fn dummy_create_option(ident: Ident, error: Error) -> TokenStream {

/// Generate push instruction for a given variant
fn choice_variant(variant: &ParsedVariant) -> TokenStream {
let name = &variant.attribute.name;
let name_localizations = match &variant.attribute.name_localizations {
Some(path) => {
quote! {
::std::option::Option::Some(
::twilight_interactions::command::internal::convert_localizations(#path())
)
}
}
None => quote! { ::std::option::Option::None },
};
let name = String::from(variant.attribute.name.clone());
let name_expr = name_expr(&name, &variant.attribute.name_localizations);

let value = match &variant.attribute.value {
ChoiceValue::String(val) => quote! { ::std::convert::From::from(#val) },
ChoiceValue::Int(val) => val.to_token_stream(),
Expand All @@ -79,14 +73,15 @@ fn choice_variant(variant: &ParsedVariant) -> TokenStream {
ChoiceKind::Number => quote! { Number },
};

quote! {
quote! { {
let __choice_name = #name_expr;
__choices.push(
::twilight_model::application::command::CommandOptionChoice {
name: ::std::convert::From::from(#name),
name_localizations: #name_localizations,
name: __choice_name.fallback,
name_localizations: __choice_name.localizations,
value: ::twilight_model::application::command::CommandOptionChoiceValue::#type_path(#value),
});
}
} }
}

/// Generate command option
Expand Down
Loading

0 comments on commit 9c2b560

Please sign in to comment.