use crate::{
command::Command, command_enum::CommandEnum, compile_error, fields_parse::impl_parse_args,
Result,
};
use proc_macro2::TokenStream;
use quote::quote;
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 mut var_inits = Vec::new();
let mut commands = Vec::new();
for variant in &data_enum.variants {
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_init = impl_parse_args(&variant.fields, self_variant, &command.parser);
var_inits.push(parse_init);
commands.push(command);
}
let type_name = &input.ident;
let fn_descriptions = impl_descriptions(&commands, &command_enum);
let fn_parse = impl_parse(&commands, &var_inits, &command_enum.command_separator);
let fn_bot_commands = impl_bot_commands(&commands);
let expanded = quote! {
impl #type_name {
#fn_parse
#fn_descriptions
#fn_bot_commands
}
};
Ok(expanded)
}
fn impl_bot_commands(infos: &[Command]) -> TokenStream {
let commands = infos.iter().filter(|c| c.is_visible()).map(|c| {
let cmd = &c.name;
let desc = c.description.as_deref().unwrap_or("");
quote! {
rust_tg_bot_raw::types::bot_command::BotCommand::new(#cmd, #desc)
}
});
quote! {
pub fn bot_commands() -> ::std::vec::Vec<rust_tg_bot_raw::types::bot_command::BotCommand> {
::std::vec![#(#commands),*]
}
}
}
fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> TokenStream {
let mut lines: Vec<TokenStream> = Vec::new();
if let Some(ref desc) = global.description {
lines.push(quote! { parts.push(::std::string::String::from(#desc)); });
lines.push(quote! { parts.push(::std::string::String::new()); });
}
for cmd in infos.iter().filter(|c| c.is_visible()) {
let prefixed = cmd.prefixed_command();
let desc = cmd.description.as_deref().unwrap_or("");
if desc.is_empty() {
lines.push(quote! {
parts.push(::std::string::String::from(#prefixed));
});
} else {
lines.push(quote! {
parts.push(::std::format!("{} \u{2014} {}", #prefixed, #desc));
});
}
}
quote! {
pub fn descriptions() -> ::std::string::String {
let mut parts: ::std::vec::Vec<::std::string::String> = ::std::vec::Vec::new();
#(#lines)*
parts.join("\n")
}
}
}
fn impl_parse(
infos: &[Command],
variant_inits: &[TokenStream],
command_separator: &str,
) -> TokenStream {
let match_arms: Vec<TokenStream> = infos
.iter()
.zip(variant_inits.iter())
.map(|(cmd, init)| {
let prefixed = cmd.prefixed_command();
quote! {
#prefixed => ::std::result::Result::Ok(#init),
}
})
.collect();
quote! {
pub fn parse(
s: &str,
bot_name: &str,
) -> ::std::result::Result<Self, ::std::string::String> {
use ::std::str::FromStr;
let mut words = s.splitn(2, #command_separator);
let full_command = words.next().unwrap();
let mut at_split = full_command.split('@');
let command = at_split.next().unwrap();
if let ::std::option::Option::Some(username) = at_split.next() {
if !username.eq_ignore_ascii_case(bot_name) {
return ::std::result::Result::Err(
::std::format!("command addressed to wrong bot: @{}", username),
);
}
}
let args = words.next().unwrap_or("").to_owned();
match command {
#(#match_arms)*
_ => ::std::result::Result::Err(
::std::format!("unknown command: {}", command),
),
}
}
}
}
fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum> {
match &input.data {
syn::Data::Enum(data) => Ok(data),
_ => Err(compile_error("`BotCommands` can only be derived for enums")),
}
}