mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-10 20:12:25 +01:00
now rename in variant overlap rename in enum
This commit is contained in:
parent
d490ed9bc0
commit
0654bbc82d
6 changed files with 149 additions and 66 deletions
|
@ -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 = "/")]
|
||||||
|
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
6
teloxide-macros/src/rename_rules.rs
Normal file
6
teloxide-macros/src/rename_rules.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue