From 0654bbc82dbf6fef0646e455785a9a2a9940f2ce Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Jan 2020 22:47:31 +0200 Subject: [PATCH] now rename in variant overlap rename in enum --- src/utils/parsers.rs | 7 ++- teloxide-macros/src/attr.rs | 4 +- teloxide-macros/src/command.rs | 67 +++++++++++++++++--------- teloxide-macros/src/enum_attributes.rs | 67 +++++++++++++++++--------- teloxide-macros/src/lib.rs | 64 +++++++++++++++++------- teloxide-macros/src/rename_rules.rs | 6 +++ 6 files changed, 149 insertions(+), 66 deletions(-) create mode 100644 teloxide-macros/src/rename_rules.rs diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index dae95d9a..25ea8dcf 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -4,6 +4,7 @@ pub use teloxide_macros::BotCommand; /// Example: /// ``` /// use teloxide::utils::{parse_command_into_enum, BotCommand}; +/// #[command(rename = "lowercase")] /// #[derive(BotCommand, PartialEq, Debug)] /// enum TelegramAdminCommand { /// Ban, @@ -25,6 +26,7 @@ pub trait BotCommand: Sized { /// Example: /// ``` /// use teloxide::utils::{parse_command_into_enum, BotCommand}; +/// #[command(rename = "lowercase")] /// #[derive(BotCommand, PartialEq, Debug)] /// enum TelegramAdminCommand { /// Ban, @@ -108,6 +110,7 @@ mod tests { #[test] fn parse_command_with_args() { + #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start, @@ -122,6 +125,7 @@ mod tests { #[test] fn attribute_prefix() { + #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { #[command(prefix = "!")] @@ -137,6 +141,7 @@ mod tests { #[test] fn many_attributes() { + #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { #[command(prefix = "!", description = "desc")] @@ -150,7 +155,7 @@ mod tests { #[test] fn global_attributes() { - #[command(prefix = "!")] + #[command(prefix = "!", rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { #[command(prefix = "/")] diff --git a/teloxide-macros/src/attr.rs b/teloxide-macros/src/attr.rs index 1e7b2949..89a5b06e 100644 --- a/teloxide-macros/src/attr.rs +++ b/teloxide-macros/src/attr.rs @@ -4,7 +4,8 @@ use syn::{Attribute, LitStr, Token}; pub enum BotCommandAttribute { Prefix, - Description + Description, + RenameRule } impl Parse for BotCommandAttribute { @@ -13,6 +14,7 @@ impl Parse for BotCommandAttribute { match name_arg.to_string().as_str() { "prefix" => Ok(BotCommandAttribute::Prefix), "description" => Ok(BotCommandAttribute::Description), + "rename" => Ok(BotCommandAttribute::RenameRule), _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")) } } diff --git a/teloxide-macros/src/command.rs b/teloxide-macros/src/command.rs index aa9c790c..5e55093f 100644 --- a/teloxide-macros/src/command.rs +++ b/teloxide-macros/src/command.rs @@ -1,38 +1,59 @@ use crate::attr::{Attr, BotCommandAttribute}; use std::convert::TryFrom; +use crate::rename_rules::rename_by_rule; pub struct Command { pub prefix: Option, pub description: Option, + pub name: String, + pub renamed: bool, } impl Command { - fn from_attrs(prefix: Option<&Attr>, description: Option<&Attr>) -> Self { - let prefix = prefix.map(|attr| attr.value()); - let description = description.map(|attr| attr.value()); + pub fn try_from(attrs: &[Attr], name: &str) -> Result { + let attrs = parse_attrs(attrs)?; + let mut new_name = name.to_string(); + let mut renamed = false; - Self { + let prefix = attrs.prefix; + let description = attrs.description; + let rename = attrs.rename; + if let Some(rename_rule) = rename { + new_name = rename_by_rule(name, &rename_rule); + renamed = true; + } + Ok(Self { prefix, - description - } + description, + name: new_name, + renamed, + }) } } -impl TryFrom<&[Attr]> for Command { - type Error = String; - - fn try_from(attrs: &[Attr]) -> Result { - let mut prefix = None; - let mut description = None; - - for attr in attrs { - match attr.name() { - BotCommandAttribute::Prefix => prefix = Some(attr), - BotCommandAttribute::Description => description = Some(attr), - _ => return Err(format!("unexpected attribute")), - } - } - - Ok(Self::from_attrs(prefix, description)) - } +struct CommandAttrs { + prefix: Option, + description: Option, + rename: Option } + +fn parse_attrs(attrs: &[Attr]) -> Result { + let mut prefix = None; + let mut description = None; + let mut rename_rule = None; + + for attr in attrs { + match attr.name() { + BotCommandAttribute::Prefix => prefix = Some(attr.value()), + BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), + _ => return Err(format!("unexpected attribute")), + } + } + + Ok(CommandAttrs { + prefix, + description, + rename: rename_rule + }) +} \ No newline at end of file diff --git a/teloxide-macros/src/enum_attributes.rs b/teloxide-macros/src/enum_attributes.rs index 0f178a53..96373210 100644 --- a/teloxide-macros/src/enum_attributes.rs +++ b/teloxide-macros/src/enum_attributes.rs @@ -1,38 +1,57 @@ use crate::attr::{Attr, BotCommandAttribute}; use std::convert::TryFrom; +use crate::rename_rules::rename_by_rule; pub struct CommandEnum { pub prefix: Option, pub description: Option, + pub rename_rule: Option, } impl CommandEnum { - fn from_attrs(prefix: Option<&Attr>, description: Option<&Attr>) -> Self { - let prefix = prefix.map(|attr| attr.value()); - let description = description.map(|attr| attr.value()); + pub fn try_from(attrs: &[Attr]) -> Result { + let attrs = parse_attrs(attrs)?; - Self { - prefix, - description - } - } -} - -impl TryFrom<&[Attr]> for CommandEnum { - type Error = String; - - fn try_from(attrs: &[Attr]) -> Result { - let mut prefix = None; - let mut description = None; - - for attr in attrs { - match attr.name() { - BotCommandAttribute::Prefix => prefix = Some(attr), - BotCommandAttribute::Description => description = Some(attr), - _ => return Err(format!("unexpected attribute")), + let prefix = attrs.prefix; + let description = attrs.description; + let rename = attrs.rename; + if let Some(rename_rule) = &rename { + match rename_rule.as_str() { + "lowercase" => {}, + _ => return Err(format!("unallowed value")), } } - - Ok(Self::from_attrs(prefix, description)) + Ok(Self { + prefix, + description, + rename_rule: rename + }) } } + +struct CommandAttrs { + prefix: Option, + description: Option, + rename: Option +} + +fn parse_attrs(attrs: &[Attr]) -> Result { + let mut prefix = None; + let mut description = None; + let mut rename_rule = None; + + for attr in attrs { + match attr.name() { + BotCommandAttribute::Prefix => prefix = Some(attr.value()), + BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), + _ => return Err(format!("unexpected attribute")), + } + } + + Ok(CommandAttrs { + prefix, + description, + rename: rename_rule + }) +} \ No newline at end of file diff --git a/teloxide-macros/src/lib.rs b/teloxide-macros/src/lib.rs index 0944822b..b4a822fa 100644 --- a/teloxide-macros/src/lib.rs +++ b/teloxide-macros/src/lib.rs @@ -1,6 +1,7 @@ mod attr; mod command; mod enum_attributes; +mod rename_rules; extern crate proc_macro; extern crate syn; @@ -12,27 +13,24 @@ use crate::command::Command; use std::convert::TryFrom; use crate::attr::{Attr, VecAttrs}; use crate::enum_attributes::CommandEnum; +use crate::rename_rules::rename_by_rule; + +macro_rules! get_or_return { + ($some:tt) => { + match $some { + Ok(elem) => elem, + Err(e) => return e + }; + } +} #[proc_macro_derive(BotCommand, attributes(command))] pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let input = parse_macro_input!(tokens as DeriveInput); - let data_enum = match &input.data { - syn::Data::Enum(data) => data, - _ => return compile_error("TelegramBotCommand allowed only for enums") - }; + let data_enum: &syn::DataEnum = get_or_return!(get_enum_data(&input)); - let mut enum_attrs = Vec::new(); - for attr in &input.attrs { - match attr.parse_args::() { - Ok(mut attrs_) => { - enum_attrs.append(attrs_.data.as_mut()); - }, - Err(e) => { - return compile_error(e.to_compile_error()); - }, - } - } + let mut enum_attrs: Vec = get_or_return!(parse_attributes(&input.attrs)); let command_enum = match CommandEnum::try_from(enum_attrs.as_slice()) { Ok(command_enum) => command_enum, @@ -54,14 +52,24 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { }, } } - match Command::try_from(attrs.as_slice()) { + match Command::try_from(attrs.as_slice(), &variant.ident.to_string()) { Ok(command) => variant_infos.push(command), Err(e) => return compile_error(e), } } let variant_ident = variants.iter().map(|variant| &variant.ident); - let variant_name = variants.iter().map(|variant| variant.ident.to_string().to_lowercase()); + let variant_name = variant_infos.iter().map(|info| { + if info.renamed { + info.name.clone() + } + else if let Some(rename_rule) = &command_enum.rename_rule { + rename_by_rule(&info.name, rename_rule) + } + else { + info.name.clone() + } + }); let variant_prefixes = variant_infos.iter().map(|info| { if let Some(prefix) = &info.prefix { prefix @@ -100,6 +108,28 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { tokens } +fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { + match &input.data { + syn::Data::Enum(data) => Ok(data), + _ => Err(compile_error("TelegramBotCommand allowed only for enums")) + } +} + +fn parse_attributes(input: &Vec) -> Result, TokenStream> { + let mut enum_attrs = Vec::new(); + for attr in &input.attrs { + match attr.parse_args::() { + Ok(mut attrs_) => { + enum_attrs.append(attrs_.data.as_mut()); + }, + Err(e) => { + return Err(compile_error(e.to_compile_error())); + }, + } + } + Ok(enum_attrs) +} + fn compile_error(data: T) -> TokenStream where T: ToTokens diff --git a/teloxide-macros/src/rename_rules.rs b/teloxide-macros/src/rename_rules.rs new file mode 100644 index 00000000..ebba4eef --- /dev/null +++ b/teloxide-macros/src/rename_rules.rs @@ -0,0 +1,6 @@ +pub fn rename_by_rule(input: &str, rule: &str) -> String { + match rule { + "lowercase" => input.to_string().to_lowercase(), + _ => rule.to_string(), + } +}