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:
/// ```
/// 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 = "/")]

View file

@ -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"))
}
}

View file

@ -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<String>,
pub description: Option<String>,
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<Self, String> {
let attrs = parse_attrs(attrs)?;
let mut new_name = name.to_string();
let mut renamed = false;
Self {
prefix,
description
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,
name: new_name,
renamed,
})
}
}
impl TryFrom<&[Attr]> for Command {
type Error = String;
struct CommandAttrs {
prefix: Option<String>,
description: Option<String>,
rename: Option<String>
}
fn try_from(attrs: &[Attr]) -> Result<Self, Self::Error> {
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),
BotCommandAttribute::Description => description = Some(attr),
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(Self::from_attrs(prefix, description))
}
Ok(CommandAttrs {
prefix,
description,
rename: rename_rule
})
}

View file

@ -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<String>,
pub description: Option<String>,
pub rename_rule: Option<String>,
}
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<Self, String> {
let attrs = parse_attrs(attrs)?;
Self {
prefix,
description
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 {
prefix,
description,
rename_rule: rename
})
}
}
impl TryFrom<&[Attr]> for CommandEnum {
type Error = String;
struct CommandAttrs {
prefix: Option<String>,
description: Option<String>,
rename: Option<String>
}
fn try_from(attrs: &[Attr]) -> Result<Self, Self::Error> {
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),
BotCommandAttribute::Description => description = Some(attr),
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(Self::from_attrs(prefix, description))
}
Ok(CommandAttrs {
prefix,
description,
rename: rename_rule
})
}

View file

@ -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::<VecAttrs>() {
Ok(mut attrs_) => {
enum_attrs.append(attrs_.data.as_mut());
},
Err(e) => {
return compile_error(e.to_compile_error());
},
}
}
let mut enum_attrs: Vec<Attr> = 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<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
where
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(),
}
}