refactor imports and stuff

This commit is contained in:
Maybe Waffle 2022-08-25 18:35:46 +04:00
parent 5d39e3c06d
commit 5477535834
6 changed files with 164 additions and 162 deletions

View file

@ -1,10 +1,10 @@
use crate::Result;
use syn::{ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
Attribute, LitStr, Token, Attribute, LitStr, Token,
}; };
use crate::Result;
pub(crate) enum CommandAttrName { pub(crate) enum CommandAttrName {
Prefix, Prefix,
Description, Description,

148
src/bot_commands.rs Normal file
View file

@ -0,0 +1,148 @@
use crate::{
attr::CommandAttrs, command::Command, command_enum::CommandEnum,
compile_error, fields_parse::impl_parse_args, unzip::Unzip, 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 enum_attrs = CommandAttrs::from_attributes(&input.attrs)?;
let command_enum = CommandEnum::try_from(enum_attrs)?;
let Unzip(var_init, var_info) = data_enum
.variants
.iter()
.map(|variant| {
let attrs = CommandAttrs::from_attributes(&variant.attrs)?;
let command = Command::try_from(attrs, &variant.ident.to_string())?;
let variant_name = &variant.ident;
let self_variant = quote! { Self::#variant_name };
let parser =
command.parser.as_ref().unwrap_or(&command_enum.parser_type);
let parse = impl_parse_args(&variant.fields, self_variant, 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, &command_enum, &var_init);
let fn_commands = impl_commands(&var_info, &command_enum);
let trait_impl = quote! {
impl BotCommands for #type_name {
#fn_descriptions
#fn_parse
#fn_commands
}
};
Ok(TokenStream::from(trait_impl))
}
fn impl_commands(
infos: &[Command],
global: &CommandEnum,
) -> proc_macro2::TokenStream {
let commands = infos
.iter()
.filter(|command| command.description_is_enabled())
.map(|command| {
let c = command.get_matched_value(global);
let d = command.description.as_deref().unwrap_or_default();
quote! { BotCommand::new(#c,#d) }
});
quote! {
fn bot_commands() -> Vec<teloxide::types::BotCommand> {
use teloxide::types::BotCommand;
vec![#(#commands),*]
}
}
}
fn impl_descriptions(
infos: &[Command],
global: &CommandEnum,
) -> proc_macro2::TokenStream {
let command_descriptions = infos
.iter()
.filter(|command| command.description_is_enabled())
.map(|c| {
let (prefix, command) = c.get_matched_value2(global);
let description = c.description.clone().unwrap_or_default();
quote! { CommandDescription { prefix: #prefix, command: #command, description: #description } }
});
let global_description = match global.description.as_deref() {
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;
CommandDescriptions::new(&[
#(#command_descriptions),*
])
#global_description
}
}
}
fn impl_parse(
infos: &[Command],
global: &CommandEnum,
variants_initialization: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let matching_values = infos.iter().map(|c| c.get_matched_value(global));
quote! {
fn parse<N>(s: &str, bot_name: N) -> Result<Self, teloxide::utils::command::ParseError>
where
N: Into<String>
{
// FIXME: we should probably just call a helper function from `teloxide`, instead of parsing command syntax ourselves
use std::str::FromStr;
use teloxide::utils::command::ParseError;
// 2 is used to only split once (=> in two parts),
// we only need to split the command and the rest of arguments.
let mut words = s.splitn(2, ' ');
// Unwrap: split iterators always have at least one item
let mut full_command = words.next().unwrap().split('@');
let command = full_command.next().unwrap();
let bot_username = full_command.next();
match bot_username {
None => {}
Some(username) if username.eq_ignore_ascii_case(&bot_name.into()) => {}
Some(n) => return Err(ParseError::WrongBotName(n.to_owned())),
}
let args = words.next().unwrap_or("").to_owned();
match command {
#(
#matching_values => Ok(#variants_initialization),
)*
_ => 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")),
}
}

View file

@ -13,9 +13,9 @@ where
Error(TokenStream::from(quote! { compile_error! { #data } })) Error(TokenStream::from(quote! { compile_error! { #data } }))
} }
impl From<Error> for proc_macro::TokenStream { impl From<Error> for proc_macro2::TokenStream {
fn from(Error(e): Error) -> Self { fn from(Error(e): Error) -> Self {
e.into() e
} }
} }

View file

@ -1,5 +1,3 @@
extern crate quote;
use quote::quote; use quote::quote;
use syn::{Fields, FieldsNamed, FieldsUnnamed, Type}; use syn::{Fields, FieldsNamed, FieldsUnnamed, Type};

View file

@ -1,6 +1,9 @@
// TODO: refactor this shit. // TODO: refactor this shit.
extern crate proc_macro;
mod attr; mod attr;
mod bot_commands;
mod command; mod command;
mod command_enum; mod command_enum;
mod error; mod error;
@ -8,162 +11,15 @@ mod fields_parse;
mod rename_rules; mod rename_rules;
mod unzip; mod unzip;
extern crate proc_macro; pub(crate) use error::{compile_error, Result};
extern crate quote; use syn::{parse_macro_input, DeriveInput};
extern crate syn;
use crate::{
attr::CommandAttrs, command::Command, command_enum::CommandEnum,
fields_parse::impl_parse_args, unzip::Unzip,
};
use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
pub(crate) use error::{compile_error, Error, Result}; use crate::bot_commands::bot_commands_impl;
use proc_macro::TokenStream;
#[proc_macro_derive(BotCommands, attributes(command))] #[proc_macro_derive(BotCommands, attributes(command))]
pub fn bot_commands_derive(tokens: TokenStream) -> TokenStream { pub fn bot_commands_derive(tokens: TokenStream) -> TokenStream {
bot_commands_impl(tokens).unwrap_or_else(Error::into) let input = parse_macro_input!(tokens as DeriveInput);
}
bot_commands_impl(input).unwrap_or_else(<_>::into).into()
fn bot_commands_impl(tokens: TokenStream) -> Result<TokenStream, Error> {
let input = syn::parse_macro_input::parse::<DeriveInput>(tokens)?;
let data_enum = get_enum_data(&input)?;
let enum_attrs = CommandAttrs::from_attributes(&input.attrs)?;
let command_enum = CommandEnum::try_from(enum_attrs)?;
let Unzip(var_init, var_info) = data_enum
.variants
.iter()
.map(|variant| {
let attrs = CommandAttrs::from_attributes(&variant.attrs)?;
let command = Command::try_from(attrs, &variant.ident.to_string())?;
let variant_name = &variant.ident;
let self_variant = quote! { Self::#variant_name };
let parser =
command.parser.as_ref().unwrap_or(&command_enum.parser_type);
let parse = impl_parse_args(&variant.fields, self_variant, parser);
Ok((parse, command))
})
.collect::<Result<Unzip<Vec<_>, Vec<_>>, Error>>()?;
let type_name = &input.ident;
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
let fn_parse = impl_parse(&var_info, &command_enum, &var_init);
let fn_commands = impl_commands(&var_info, &command_enum);
let trait_impl = quote! {
impl BotCommands for #type_name {
#fn_descriptions
#fn_parse
#fn_commands
}
};
Ok(TokenStream::from(trait_impl))
}
fn impl_commands(
infos: &[Command],
global: &CommandEnum,
) -> proc_macro2::TokenStream {
let commands = infos
.iter()
.filter(|command| command.description_is_enabled())
.map(|command| {
let c = command.get_matched_value(global);
let d = command.description.as_deref().unwrap_or_default();
quote! { BotCommand::new(#c,#d) }
});
quote! {
fn bot_commands() -> Vec<teloxide::types::BotCommand> {
use teloxide::types::BotCommand;
vec![#(#commands),*]
}
}
}
fn impl_descriptions(
infos: &[Command],
global: &CommandEnum,
) -> proc_macro2::TokenStream {
let command_descriptions = infos
.iter()
.filter(|command| command.description_is_enabled())
.map(|c| {
let (prefix, command) = c.get_matched_value2(global);
let description = c.description.clone().unwrap_or_default();
quote! { CommandDescription { prefix: #prefix, command: #command, description: #description } }
});
let global_description = match global.description.as_deref() {
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;
CommandDescriptions::new(&[
#(#command_descriptions),*
])
#global_description
}
}
}
fn impl_parse(
infos: &[Command],
global: &CommandEnum,
variants_initialization: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let matching_values = infos.iter().map(|c| c.get_matched_value(global));
quote! {
fn parse<N>(s: &str, bot_name: N) -> Result<Self, teloxide::utils::command::ParseError>
where
N: Into<String>
{
// FIXME: we should probably just call a helper function from `teloxide`, instead of parsing command syntax ourselves
use std::str::FromStr;
use teloxide::utils::command::ParseError;
// 2 is used to only split once (=> in two parts),
// we only need to split the command and the rest of arguments.
let mut words = s.splitn(2, ' ');
// Unwrap: split iterators always have at least one item
let mut full_command = words.next().unwrap().split('@');
let command = full_command.next().unwrap();
let bot_username = full_command.next();
match bot_username {
None => {}
Some(username) if username.eq_ignore_ascii_case(&bot_name.into()) => {}
Some(n) => return Err(ParseError::WrongBotName(n.to_owned())),
}
let args = words.next().unwrap_or("").to_owned();
match command {
#(
#matching_values => Ok(#variants_initialization),
)*
_ => 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")),
}
} }

View file

@ -1,12 +1,12 @@
// Some concepts are from Serde. // Some concepts are from Serde.
use crate::error::{compile_error, Result};
use heck::{ use heck::{
ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase,
ToShoutySnakeCase, ToSnakeCase, ToShoutySnakeCase, ToSnakeCase,
}; };
use crate::error::{compile_error, Result};
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub(crate) enum RenameRule { pub(crate) enum RenameRule {
/// -> `lowercase` /// -> `lowercase`