now rename in variant overlap rename in enum

This commit is contained in:
p0lunin 2020-01-25 22:47:31 +02:00
parent d490ed9bc0
commit 0654bbc82d
6 changed files with 149 additions and 66 deletions

View file

@ -4,6 +4,7 @@ pub use teloxide_macros::BotCommand;
/// Example: /// Example:
/// ``` /// ```
/// use teloxide::utils::{parse_command_into_enum, BotCommand}; /// use teloxide::utils::{parse_command_into_enum, BotCommand};
/// #[command(rename = "lowercase")]
/// #[derive(BotCommand, PartialEq, Debug)] /// #[derive(BotCommand, PartialEq, Debug)]
/// enum TelegramAdminCommand { /// enum TelegramAdminCommand {
/// Ban, /// Ban,
@ -25,6 +26,7 @@ pub trait BotCommand: Sized {
/// Example: /// Example:
/// ``` /// ```
/// use teloxide::utils::{parse_command_into_enum, BotCommand}; /// use teloxide::utils::{parse_command_into_enum, BotCommand};
/// #[command(rename = "lowercase")]
/// #[derive(BotCommand, PartialEq, Debug)] /// #[derive(BotCommand, PartialEq, Debug)]
/// enum TelegramAdminCommand { /// enum TelegramAdminCommand {
/// Ban, /// Ban,
@ -108,6 +110,7 @@ mod tests {
#[test] #[test]
fn parse_command_with_args() { fn parse_command_with_args() {
#[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands { enum DefaultCommands {
Start, Start,
@ -122,6 +125,7 @@ mod tests {
#[test] #[test]
fn attribute_prefix() { fn attribute_prefix() {
#[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands { enum DefaultCommands {
#[command(prefix = "!")] #[command(prefix = "!")]
@ -137,6 +141,7 @@ mod tests {
#[test] #[test]
fn many_attributes() { fn many_attributes() {
#[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands { enum DefaultCommands {
#[command(prefix = "!", description = "desc")] #[command(prefix = "!", description = "desc")]
@ -150,7 +155,7 @@ mod tests {
#[test] #[test]
fn global_attributes() { fn global_attributes() {
#[command(prefix = "!")] #[command(prefix = "!", rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands { enum DefaultCommands {
#[command(prefix = "/")] #[command(prefix = "/")]

View file

@ -4,7 +4,8 @@ use syn::{Attribute, LitStr, Token};
pub enum BotCommandAttribute { pub enum BotCommandAttribute {
Prefix, Prefix,
Description Description,
RenameRule
} }
impl Parse for BotCommandAttribute { impl Parse for BotCommandAttribute {
@ -13,6 +14,7 @@ impl Parse for BotCommandAttribute {
match name_arg.to_string().as_str() { match name_arg.to_string().as_str() {
"prefix" => Ok(BotCommandAttribute::Prefix), "prefix" => Ok(BotCommandAttribute::Prefix),
"description" => Ok(BotCommandAttribute::Description), "description" => Ok(BotCommandAttribute::Description),
"rename" => Ok(BotCommandAttribute::RenameRule),
_ => Err(syn::Error::new(name_arg.span(), "unexpected argument")) _ => Err(syn::Error::new(name_arg.span(), "unexpected argument"))
} }
} }

View file

@ -1,38 +1,59 @@
use crate::attr::{Attr, BotCommandAttribute}; use crate::attr::{Attr, BotCommandAttribute};
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::rename_rules::rename_by_rule;
pub struct Command { pub struct Command {
pub prefix: Option<String>, pub prefix: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub name: String,
pub renamed: bool,
} }
impl Command { impl Command {
fn from_attrs(prefix: Option<&Attr>, description: Option<&Attr>) -> Self { pub fn try_from(attrs: &[Attr], name: &str) -> Result<Self, String> {
let prefix = prefix.map(|attr| attr.value()); let attrs = parse_attrs(attrs)?;
let description = description.map(|attr| attr.value()); 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, prefix,
description description,
} name: new_name,
renamed,
})
} }
} }
impl TryFrom<&[Attr]> for Command { struct CommandAttrs {
type Error = String; prefix: Option<String>,
description: Option<String>,
fn try_from(attrs: &[Attr]) -> Result<Self, Self::Error> { rename: Option<String>
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))
}
} }
fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs, String> {
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
})
}

View file

@ -1,38 +1,57 @@
use crate::attr::{Attr, BotCommandAttribute}; use crate::attr::{Attr, BotCommandAttribute};
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::rename_rules::rename_by_rule;
pub struct CommandEnum { pub struct CommandEnum {
pub prefix: Option<String>, pub prefix: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub rename_rule: Option<String>,
} }
impl CommandEnum { impl CommandEnum {
fn from_attrs(prefix: Option<&Attr>, description: Option<&Attr>) -> Self { pub fn try_from(attrs: &[Attr]) -> Result<Self, String> {
let prefix = prefix.map(|attr| attr.value()); let attrs = parse_attrs(attrs)?;
let description = description.map(|attr| attr.value());
Self { let prefix = attrs.prefix;
prefix, let description = attrs.description;
description let rename = attrs.rename;
} if let Some(rename_rule) = &rename {
} match rename_rule.as_str() {
} "lowercase" => {},
_ => return Err(format!("unallowed value")),
impl TryFrom<&[Attr]> for CommandEnum {
type Error = String;
fn try_from(attrs: &[Attr]) -> Result<Self, Self::Error> {
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 {
Ok(Self::from_attrs(prefix, description)) prefix,
description,
rename_rule: rename
})
} }
} }
struct CommandAttrs {
prefix: Option<String>,
description: Option<String>,
rename: Option<String>
}
fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs, String> {
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
})
}

View file

@ -1,6 +1,7 @@
mod attr; mod attr;
mod command; mod command;
mod enum_attributes; mod enum_attributes;
mod rename_rules;
extern crate proc_macro; extern crate proc_macro;
extern crate syn; extern crate syn;
@ -12,27 +13,24 @@ use crate::command::Command;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::attr::{Attr, VecAttrs}; use crate::attr::{Attr, VecAttrs};
use crate::enum_attributes::CommandEnum; 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))] #[proc_macro_derive(BotCommand, attributes(command))]
pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as DeriveInput); let input = parse_macro_input!(tokens as DeriveInput);
let data_enum = match &input.data { let data_enum: &syn::DataEnum = get_or_return!(get_enum_data(&input));
syn::Data::Enum(data) => data,
_ => return compile_error("TelegramBotCommand allowed only for enums")
};
let mut enum_attrs = Vec::new(); let mut enum_attrs: Vec<Attr> = get_or_return!(parse_attributes(&input.attrs));
for attr in &input.attrs {
match attr.parse_args::<VecAttrs>() {
Ok(mut attrs_) => {
enum_attrs.append(attrs_.data.as_mut());
},
Err(e) => {
return compile_error(e.to_compile_error());
},
}
}
let command_enum = match CommandEnum::try_from(enum_attrs.as_slice()) { let command_enum = match CommandEnum::try_from(enum_attrs.as_slice()) {
Ok(command_enum) => command_enum, 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), Ok(command) => variant_infos.push(command),
Err(e) => return compile_error(e), Err(e) => return compile_error(e),
} }
} }
let variant_ident = variants.iter().map(|variant| &variant.ident); 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| { let variant_prefixes = variant_infos.iter().map(|info| {
if let Some(prefix) = &info.prefix { if let Some(prefix) = &info.prefix {
prefix prefix
@ -100,6 +108,28 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
tokens 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<syn::Attribute>) -> Result<Vec<Attr>, TokenStream> {
let mut enum_attrs = Vec::new();
for attr in &input.attrs {
match attr.parse_args::<VecAttrs>() {
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<T>(data: T) -> TokenStream fn compile_error<T>(data: T) -> TokenStream
where where
T: ToTokens T: ToTokens

View file

@ -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(),
}
}