mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 22:46:39 +01:00
Refactor attributes AGAIN
This commit is contained in:
parent
26eba3eb14
commit
ff08854ca9
5 changed files with 80 additions and 71 deletions
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
command::Command, command_attr::CommandAttrs, command_enum::CommandEnum,
|
command::Command, command_enum::CommandEnum, compile_error,
|
||||||
compile_error, fields_parse::impl_parse_args, unzip::Unzip, Result,
|
fields_parse::impl_parse_args, unzip::Unzip, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
@ -9,22 +9,23 @@ use syn::DeriveInput;
|
||||||
|
|
||||||
pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
|
pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
|
||||||
let data_enum = get_enum_data(&input)?;
|
let data_enum = get_enum_data(&input)?;
|
||||||
let enum_attrs = CommandAttrs::from_attributes(&input.attrs)?;
|
let command_enum = CommandEnum::from_attributes(&input.attrs)?;
|
||||||
let command_enum = CommandEnum::try_from(enum_attrs)?;
|
|
||||||
|
|
||||||
let Unzip(var_init, var_info) = data_enum
|
let Unzip(var_init, var_info) = data_enum
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
let attrs = CommandAttrs::from_attributes(&variant.attrs)?;
|
let command = Command::new(
|
||||||
let command = Command::try_from(attrs, &variant.ident.to_string())?;
|
&variant.ident.to_string(),
|
||||||
|
&variant.attrs,
|
||||||
|
&command_enum,
|
||||||
|
)?;
|
||||||
|
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
let self_variant = quote! { Self::#variant_name };
|
let self_variant = quote! { Self::#variant_name };
|
||||||
|
|
||||||
let parser =
|
let parse =
|
||||||
command.parser.as_ref().unwrap_or(&command_enum.parser_type);
|
impl_parse_args(&variant.fields, self_variant, &command.parser);
|
||||||
let parse = impl_parse_args(&variant.fields, self_variant, parser);
|
|
||||||
|
|
||||||
Ok((parse, command))
|
Ok((parse, command))
|
||||||
})
|
})
|
||||||
|
@ -32,8 +33,8 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
|
||||||
|
|
||||||
let type_name = &input.ident;
|
let type_name = &input.ident;
|
||||||
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
|
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
|
||||||
let fn_parse = impl_parse(&var_info, &command_enum, &var_init);
|
let fn_parse = impl_parse(&var_info, &var_init);
|
||||||
let fn_commands = impl_commands(&var_info, &command_enum);
|
let fn_commands = impl_commands(&var_info);
|
||||||
|
|
||||||
let trait_impl = quote! {
|
let trait_impl = quote! {
|
||||||
impl teloxide::utils::command::BotCommands for #type_name {
|
impl teloxide::utils::command::BotCommands for #type_name {
|
||||||
|
@ -46,15 +47,12 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
|
||||||
Ok(trait_impl)
|
Ok(trait_impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_commands(
|
fn impl_commands(infos: &[Command]) -> proc_macro2::TokenStream {
|
||||||
infos: &[Command],
|
|
||||||
global: &CommandEnum,
|
|
||||||
) -> proc_macro2::TokenStream {
|
|
||||||
let commands = infos
|
let commands = infos
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|command| command.description_is_enabled())
|
.filter(|command| command.description_is_enabled())
|
||||||
.map(|command| {
|
.map(|command| {
|
||||||
let c = command.get_matched_value(global);
|
let c = command.get_prefixed_command();
|
||||||
let d = command.description.as_deref().unwrap_or_default();
|
let d = command.description.as_deref().unwrap_or_default();
|
||||||
quote! { BotCommand::new(#c,#d) }
|
quote! { BotCommand::new(#c,#d) }
|
||||||
});
|
});
|
||||||
|
@ -74,10 +72,9 @@ fn impl_descriptions(
|
||||||
let command_descriptions = infos
|
let command_descriptions = infos
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|command| command.description_is_enabled())
|
.filter(|command| command.description_is_enabled())
|
||||||
.map(|c| {
|
.map(|Command { prefix, name, description, ..}| {
|
||||||
let (prefix, command) = c.get_matched_value2(global);
|
let description = description.clone().unwrap_or_default();
|
||||||
let description = c.description.clone().unwrap_or_default();
|
quote! { CommandDescription { prefix: #prefix, command: #name, description: #description } }
|
||||||
quote! { CommandDescription { prefix: #prefix, command: #command, description: #description } }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let global_description = match global.description.as_deref() {
|
let global_description = match global.description.as_deref() {
|
||||||
|
@ -100,10 +97,9 @@ fn impl_descriptions(
|
||||||
|
|
||||||
fn impl_parse(
|
fn impl_parse(
|
||||||
infos: &[Command],
|
infos: &[Command],
|
||||||
global: &CommandEnum,
|
|
||||||
variants_initialization: &[proc_macro2::TokenStream],
|
variants_initialization: &[proc_macro2::TokenStream],
|
||||||
) -> proc_macro2::TokenStream {
|
) -> proc_macro2::TokenStream {
|
||||||
let matching_values = infos.iter().map(|c| c.get_matched_value(global));
|
let matching_values = infos.iter().map(|c| c.get_prefixed_command());
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
fn parse<N>(s: &str, bot_name: N) -> Result<Self, teloxide::utils::command::ParseError>
|
fn parse<N>(s: &str, bot_name: N) -> Result<Self, teloxide::utils::command::ParseError>
|
||||||
|
|
|
@ -1,55 +1,54 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
command_attr::CommandAttrs, command_enum::CommandEnum,
|
command_attr::CommandAttrs, command_enum::CommandEnum,
|
||||||
fields_parse::ParserType, rename_rules::RenameRule, Result,
|
fields_parse::ParserType, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
pub prefix: Option<String>,
|
/// Prefix of this command, for example "/".
|
||||||
|
pub prefix: String,
|
||||||
|
/// Description for the command.
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub parser: Option<ParserType>,
|
/// Name of the command, with all renames already applied.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Parser for arguments of this command.
|
||||||
|
pub parser: ParserType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn try_from(attrs: CommandAttrs, name: &str) -> Result<Self> {
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
attributes: &[syn::Attribute],
|
||||||
|
global_options: &CommandEnum,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let attrs = CommandAttrs::from_attributes(attributes)?;
|
||||||
let CommandAttrs {
|
let CommandAttrs {
|
||||||
prefix,
|
prefix,
|
||||||
description,
|
description,
|
||||||
rename_rule,
|
rename_rule,
|
||||||
parser,
|
parser,
|
||||||
|
// FIXME: error on/do not ignore separator
|
||||||
separator: _,
|
separator: _,
|
||||||
} = attrs;
|
} = attrs;
|
||||||
|
|
||||||
let name = rename_rule.unwrap_or(RenameRule::Identity).apply(name);
|
let name = rename_rule
|
||||||
|
.map(|(rr, _)| rr)
|
||||||
|
.unwrap_or(global_options.rename_rule)
|
||||||
|
.apply(name);
|
||||||
|
|
||||||
|
let prefix = prefix
|
||||||
|
.map(|(p, _)| p)
|
||||||
|
.unwrap_or_else(|| global_options.prefix.clone());
|
||||||
|
let description = description.map(|(d, _)| d);
|
||||||
|
let parser = parser
|
||||||
|
.map(|(p, _)| p)
|
||||||
|
.unwrap_or_else(|| global_options.parser_type.clone());
|
||||||
|
|
||||||
Ok(Self { prefix, description, parser, name })
|
Ok(Self { prefix, description, parser, name })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String {
|
pub fn get_prefixed_command(&self) -> String {
|
||||||
let prefix = if let Some(prefix) = &self.prefix {
|
let Self { prefix, name, .. } = self;
|
||||||
prefix
|
format!("{prefix}{name}")
|
||||||
} else if let Some(prefix) = &global_parameters.prefix {
|
|
||||||
prefix
|
|
||||||
} else {
|
|
||||||
"/"
|
|
||||||
};
|
|
||||||
|
|
||||||
String::from(prefix) + &global_parameters.rename_rule.apply(&self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_matched_value2(
|
|
||||||
&self,
|
|
||||||
global_parameters: &CommandEnum,
|
|
||||||
) -> (String, String) {
|
|
||||||
let prefix = if let Some(prefix) = &self.prefix {
|
|
||||||
prefix
|
|
||||||
} else if let Some(prefix) = &global_parameters.prefix {
|
|
||||||
prefix
|
|
||||||
} else {
|
|
||||||
"/"
|
|
||||||
};
|
|
||||||
|
|
||||||
(String::from(prefix), global_parameters.rename_rule.apply(&self.name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn description_is_enabled(&self) -> bool {
|
pub(crate) fn description_is_enabled(&self) -> bool {
|
||||||
|
|
|
@ -9,22 +9,31 @@ use crate::{
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use syn::Attribute;
|
use syn::Attribute;
|
||||||
|
|
||||||
/// Attributes for `BotCommands` derive macro.
|
/// All attributes that can be used for `derive(BotCommands)`
|
||||||
pub(crate) struct CommandAttrs {
|
pub(crate) struct CommandAttrs {
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<(String, Span)>,
|
||||||
pub description: Option<String>,
|
pub description: Option<(String, Span)>,
|
||||||
pub rename_rule: Option<RenameRule>,
|
pub rename_rule: Option<(RenameRule, Span)>,
|
||||||
pub parser: Option<ParserType>,
|
pub parser: Option<(ParserType, Span)>,
|
||||||
pub separator: Option<String>,
|
pub separator: Option<(String, Span)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An attribute for `BotCommands` derive macro.
|
/// A single k/v attribute for `BotCommands` derive macro.
|
||||||
pub(crate) struct CommandAttr {
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```text
|
||||||
|
/// #[command(prefix = "!", rename_rule = "snake_case")]
|
||||||
|
/// /^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^---- CommandAttr { kind: RenameRule(SnakeCase) }
|
||||||
|
/// |
|
||||||
|
/// CommandAttr { kind: Prefix("!") }
|
||||||
|
/// ```
|
||||||
|
struct CommandAttr {
|
||||||
kind: CommandAttrKind,
|
kind: CommandAttrKind,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum CommandAttrKind {
|
/// Kind of [`CommandAttr`].
|
||||||
|
enum CommandAttrKind {
|
||||||
Prefix(String),
|
Prefix(String),
|
||||||
Description(String),
|
Description(String),
|
||||||
RenameRule(RenameRule),
|
RenameRule(RenameRule),
|
||||||
|
@ -49,13 +58,13 @@ impl CommandAttrs {
|
||||||
},
|
},
|
||||||
|mut this, attr| {
|
|mut this, attr| {
|
||||||
fn insert<T>(
|
fn insert<T>(
|
||||||
opt: &mut Option<T>,
|
opt: &mut Option<(T, Span)>,
|
||||||
x: T,
|
x: T,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match opt {
|
match opt {
|
||||||
slot @ None => {
|
slot @ None => {
|
||||||
*slot = Some(x);
|
*slot = Some((x, sp));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
|
|
|
@ -5,14 +5,15 @@ use crate::{
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct CommandEnum {
|
pub(crate) struct CommandEnum {
|
||||||
pub prefix: Option<String>,
|
pub prefix: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub rename_rule: RenameRule,
|
pub rename_rule: RenameRule,
|
||||||
pub parser_type: ParserType,
|
pub parser_type: ParserType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandEnum {
|
impl CommandEnum {
|
||||||
pub fn try_from(attrs: CommandAttrs) -> Result<Self> {
|
pub fn from_attributes(attributes: &[syn::Attribute]) -> Result<Self> {
|
||||||
|
let attrs = CommandAttrs::from_attributes(attributes)?;
|
||||||
let CommandAttrs {
|
let CommandAttrs {
|
||||||
prefix,
|
prefix,
|
||||||
description,
|
description,
|
||||||
|
@ -20,18 +21,22 @@ impl CommandEnum {
|
||||||
parser,
|
parser,
|
||||||
separator,
|
separator,
|
||||||
} = attrs;
|
} = attrs;
|
||||||
let mut parser = parser.unwrap_or(ParserType::Default);
|
|
||||||
|
let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default);
|
||||||
|
|
||||||
// FIXME: Error on unused separator
|
// FIXME: Error on unused separator
|
||||||
if let (ParserType::Split { separator }, Some(s)) =
|
if let (ParserType::Split { separator }, Some((s, _))) =
|
||||||
(&mut parser, &separator)
|
(&mut parser, &separator)
|
||||||
{
|
{
|
||||||
*separator = Some(s.clone())
|
*separator = Some(s.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
prefix,
|
prefix: prefix.map(|(p, _)| p).unwrap_or_else(|| "/".to_owned()),
|
||||||
description,
|
description: description.map(|(d, _)| d),
|
||||||
rename_rule: rename_rule.unwrap_or(RenameRule::Identity),
|
rename_rule: rename_rule
|
||||||
|
.map(|(rr, _)| rr)
|
||||||
|
.unwrap_or(RenameRule::Identity),
|
||||||
parser_type: parser,
|
parser_type: parser,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Fields, FieldsNamed, FieldsUnnamed, Type};
|
use syn::{Fields, FieldsNamed, FieldsUnnamed, Type};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum ParserType {
|
pub(crate) enum ParserType {
|
||||||
Default,
|
Default,
|
||||||
Split { separator: Option<String> },
|
Split { separator: Option<String> },
|
||||||
|
|
Loading…
Reference in a new issue