use crate::{
command::Command, command_enum::CommandEnum, compile_error, fields_parse::impl_parse_args,
unzip::Unzip, Result,
};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::DeriveInput;
pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
let data_enum = get_enum_data(&input)?;
let command_enum = CommandEnum::from_attributes(&input.attrs)?;
let Unzip(var_init, var_info) = data_enum
.variants
.iter()
.map(|variant| {
let command = Command::new(&variant.ident.to_string(), &variant.attrs, &command_enum)?;
let variant_name = &variant.ident;
let self_variant = quote! { Self::#variant_name };
let parse = impl_parse_args(&variant.fields, self_variant, &command.parser);
Ok((parse, command))
})
.collect::<Result<Unzip<Vec<_>, Vec<_>>>>()?;
let type_name = &input.ident;
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
let fn_parse = impl_parse(&var_info, &var_init, &command_enum.command_separator);
let fn_commands = impl_commands(&var_info);
let trait_impl = quote! {
impl teloxide::utils::command::BotCommands for #type_name {
#fn_descriptions
#fn_parse
#fn_commands
}
};
Ok(trait_impl)
}
fn impl_commands(infos: &[Command]) -> proc_macro2::TokenStream {
let commands = infos.iter().filter(|command| command.description_is_enabled()).map(|command| {
let c = command.get_prefixed_command();
let d = command.description().unwrap_or_default();
quote! { BotCommand::new(#c,#d) }
});
quote! {
fn bot_commands() -> ::std::vec::Vec<teloxide::types::BotCommand> {
use teloxide::types::BotCommand;
::std::vec![#(#commands),*]
}
}
}
fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::TokenStream {
let command_descriptions = infos
.iter()
.filter(|command| command.description_is_enabled())
.map(|command @ Command { prefix, name, aliases, ..}| {
let description = command.description().unwrap_or_default();
let aliases = if !command.hidden_aliases { aliases.clone().map(|(aliases, _)| aliases).unwrap_or_default() } else { Default::default() };
quote! { CommandDescription { prefix: #prefix, command: #name, description: #description, aliases: &[#(#aliases),*]} }
});
let warnings = infos.iter().filter_map(|command| command.deprecated_description_off_span()).map(|span| {
quote_spanned! { span =>
const _: () = {
#[deprecated(note="\n`description = \"off\"` is deprecated, use `hide` instead")]
struct Deprecated;
_ = Deprecated;
};
}
});
let global_description = match global.description.as_ref().map(|(d, _)| d) {
Some(gd) => quote! { .global_description(#gd) },
None => quote! {},
};
quote! {
fn descriptions() -> teloxide::utils::command::CommandDescriptions<'static> {
use teloxide::utils::command::{CommandDescriptions, CommandDescription};
use std::borrow::Cow;
#(#warnings)*
CommandDescriptions::new(&[
#(#command_descriptions),*
])
#global_description
}
}
}
fn impl_parse(
infos: &[Command],
variants_initialization: &[proc_macro2::TokenStream],
command_separator: &str,
) -> proc_macro2::TokenStream {
let matching_values = infos.iter().map(|c| c.get_prefixed_command());
let aliases = infos.iter().map(|c| c.get_prefixed_aliases().unwrap_or_default());
quote! {
fn parse(s: &str, bot_name: &str) -> ::std::result::Result<Self, teloxide::utils::command::ParseError> {
use std::str::FromStr;
use teloxide::utils::command::ParseError;
let mut words = s.splitn(2, #command_separator);
let mut full_command = words.next().unwrap().split('@');
let command = full_command.next().unwrap();
let bot_username = full_command.next();
match bot_username {
::std::option::Option::None => {}
::std::option::Option::Some(username) if username.eq_ignore_ascii_case(bot_name) => {}
::std::option::Option::Some(n) => return ::std::result::Result::Err(ParseError::WrongBotName(n.to_owned())),
}
let args = words.next().unwrap_or("").to_owned();
match command {
#(
#matching_values => Ok(#variants_initialization),
)*
#(
c if [#(#aliases),*].contains(&c) => Ok(#variants_initialization),
)*
_ => ::std::result::Result::Err(ParseError::UnknownCommand(command.to_owned())),
}
}
}
}
fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum> {
match &input.data {
syn::Data::Enum(data) => Ok(data),
_ => Err(compile_error("`BotCommands` is only allowed for enums")),
}
}