mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-24 23:57:38 +01:00
commit
34cc9a9f31
6 changed files with 112 additions and 44 deletions
|
@ -9,7 +9,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0.2"
|
quote = "1.0.3"
|
||||||
syn = "1.0.13"
|
syn = "1.0.13"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
39
Readme.md
Normal file
39
Readme.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# teloxide-macros
|
||||||
|
The teloxide's procedural macros.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```rust
|
||||||
|
use teloxide::utils::command::BotCommand;
|
||||||
|
#[derive(BotCommand, PartialEq, Debug)]
|
||||||
|
#[command(rename = "lowercase")]
|
||||||
|
enum AdminCommand {
|
||||||
|
Mute,
|
||||||
|
Ban,
|
||||||
|
}
|
||||||
|
let (command, args) = AdminCommand::parse("/ban 5 h", "bot_name").unwrap();
|
||||||
|
assert_eq!(command, AdminCommand::Ban);
|
||||||
|
assert_eq!(args, vec!["5", "h"]);
|
||||||
|
```
|
||||||
|
## Enum attributes
|
||||||
|
1. `#[command(rename = "rule")]`
|
||||||
|
Rename all commands by rule. Allowed rules are `lowercase`. If you will not
|
||||||
|
use this attribute, commands will be parsed by their original names.
|
||||||
|
|
||||||
|
2. `#[command(prefix = "prefix")]`
|
||||||
|
Change a prefix for all commands (the default is `/`).
|
||||||
|
|
||||||
|
3. `#[command(description = "description")]`
|
||||||
|
Add a sumary description of commands before all commands.
|
||||||
|
|
||||||
|
## Variant attributes
|
||||||
|
1. `#[command(rename = "rule")]`
|
||||||
|
Rename one command by a rule. Allowed rules are `lowercase`, `%some_name%`,
|
||||||
|
where `%some_name%` is any string, a new name.
|
||||||
|
|
||||||
|
2. `#[command(prefix = "prefix")]`
|
||||||
|
Change a prefix for one command (the default is `/`).
|
||||||
|
|
||||||
|
3. `#[command(description = "description")]`
|
||||||
|
Add a description of one command.
|
||||||
|
|
||||||
|
All variant attributes overlap the `enum` attributes.
|
|
@ -1,8 +1,8 @@
|
||||||
|
use crate::enum_attributes::CommandEnum;
|
||||||
use crate::{
|
use crate::{
|
||||||
attr::{Attr, BotCommandAttribute},
|
attr::{Attr, BotCommandAttribute},
|
||||||
rename_rules::rename_by_rule,
|
rename_rules::rename_by_rule,
|
||||||
};
|
};
|
||||||
use crate::enum_attributes::CommandEnum;
|
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
|
@ -40,7 +40,11 @@ impl Command {
|
||||||
} else {
|
} else {
|
||||||
"/"
|
"/"
|
||||||
};
|
};
|
||||||
String::from(prefix) + &self.name
|
if let Some(rule) = &global_parameters.rename_rule {
|
||||||
|
String::from(prefix) + &rename_by_rule(&self.name, rule.as_str())
|
||||||
|
} else {
|
||||||
|
String::from(prefix) + &self.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attr::{Attr, BotCommandAttribute};
|
use crate::attr::{Attr, BotCommandAttribute};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct CommandEnum {
|
pub struct CommandEnum {
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
13
src/fields_parse.rs
Normal file
13
src/fields_parse.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
extern crate quote;
|
||||||
|
|
||||||
|
use quote::quote;
|
||||||
|
use syn::FieldsUnnamed;
|
||||||
|
|
||||||
|
pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__private::TokenStream {
|
||||||
|
let iter = 0..data.unnamed.len();
|
||||||
|
let mut tokens = quote! {};
|
||||||
|
for _ in iter {
|
||||||
|
tokens.extend(quote! { CommandArgument::parse(&mut args)?, });
|
||||||
|
}
|
||||||
|
quote! { (#tokens) }
|
||||||
|
}
|
93
src/lib.rs
93
src/lib.rs
|
@ -1,10 +1,13 @@
|
||||||
mod attr;
|
mod attr;
|
||||||
mod command;
|
mod command;
|
||||||
mod enum_attributes;
|
mod enum_attributes;
|
||||||
|
mod fields_parse;
|
||||||
mod rename_rules;
|
mod rename_rules;
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
extern crate quote;
|
||||||
extern crate syn;
|
extern crate syn;
|
||||||
|
use crate::fields_parse::impl_parse_args_unnamed;
|
||||||
use crate::{
|
use crate::{
|
||||||
attr::{Attr, VecAttrs},
|
attr::{Attr, VecAttrs},
|
||||||
command::Command,
|
command::Command,
|
||||||
|
@ -12,7 +15,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{parse_macro_input, DeriveInput, Variant};
|
use syn::{parse_macro_input, DeriveInput, Fields, Variant};
|
||||||
|
|
||||||
macro_rules! get_or_return {
|
macro_rules! get_or_return {
|
||||||
($($some:tt)*) => {
|
($($some:tt)*) => {
|
||||||
|
@ -36,10 +39,23 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
|
||||||
Err(e) => return compile_error(e),
|
Err(e) => return compile_error(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let variants: Vec<&syn::Variant> = data_enum.variants.iter().map(|attr| attr).collect();
|
let variants: Vec<&syn::Variant> = data_enum.variants.iter().map(|variant| variant).collect();
|
||||||
|
|
||||||
|
let mut vec_impl_create = vec![];
|
||||||
|
for variant in &variants {
|
||||||
|
match &variant.fields {
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
vec_impl_create.push(impl_parse_args_unnamed(fields));
|
||||||
|
}
|
||||||
|
Fields::Unit => {
|
||||||
|
vec_impl_create.push(quote! {});
|
||||||
|
}
|
||||||
|
_ => panic!("only unnamed fields"), // TODO: named fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut variant_infos = vec![];
|
let mut variant_infos = vec![];
|
||||||
for variant in variants.iter() {
|
for variant in &variants {
|
||||||
let mut attrs = Vec::new();
|
let mut attrs = Vec::new();
|
||||||
for attr in &variant.attrs {
|
for attr in &variant.attrs {
|
||||||
match attr.parse_args::<VecAttrs>() {
|
match attr.parse_args::<VecAttrs>() {
|
||||||
|
@ -59,13 +75,11 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let ident = &input.ident;
|
let ident = &input.ident;
|
||||||
|
|
||||||
let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum);
|
|
||||||
let fn_descriptions = impl_descriptions(&variant_infos, &command_enum);
|
let fn_descriptions = impl_descriptions(&variant_infos, &command_enum);
|
||||||
let fn_parse = impl_parse();
|
let fn_parse = impl_parse(&variants, &variant_infos, &command_enum, &vec_impl_create);
|
||||||
|
|
||||||
let trait_impl = quote! {
|
let trait_impl = quote! {
|
||||||
impl BotCommand for #ident {
|
impl BotCommand for #ident {
|
||||||
#fn_try_from
|
|
||||||
#fn_descriptions
|
#fn_descriptions
|
||||||
#fn_parse
|
#fn_parse
|
||||||
}
|
}
|
||||||
|
@ -74,23 +88,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
|
||||||
TokenStream::from(trait_impl)
|
TokenStream::from(trait_impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &CommandEnum) -> impl ToTokens {
|
fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__private::TokenStream {
|
||||||
let matching_values = infos.iter().map(|c| c.get_matched_value(global));
|
|
||||||
let variant_ident = variants.iter().map(|variant| &variant.ident);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
fn try_from(value: &str) -> Option<Self> {
|
|
||||||
match value {
|
|
||||||
#(
|
|
||||||
#matching_values => Some(Self::#variant_ident),
|
|
||||||
)*
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens {
|
|
||||||
let global_description = if let Some(s) = &global.description {
|
let global_description = if let Some(s) = &global.description {
|
||||||
quote! { #s, "\n", }
|
quote! { #s, "\n", }
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,31 +103,44 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens {
|
||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
fn descriptions() -> &'static str {
|
fn descriptions() -> String {
|
||||||
std::concat!(#global_description #(#command, #description, '\n'),*)
|
std::concat!(#global_description #(#command, #description, '\n'),*).to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_parse() -> impl ToTokens {
|
fn impl_parse(
|
||||||
|
variants: &[&Variant],
|
||||||
|
infos: &[Command],
|
||||||
|
global: &CommandEnum,
|
||||||
|
variants_initialization: &[quote::__private::TokenStream],
|
||||||
|
) -> quote::__private::TokenStream {
|
||||||
|
let matching_values = infos.iter().map(|c| c.get_matched_value(global));
|
||||||
|
let variant_ident = variants.iter().map(|variant| &variant.ident);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
fn parse<N>(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)>
|
fn parse<N>(s: &str, bot_name: N) -> Option<Self>
|
||||||
where
|
where
|
||||||
N: Into<String>
|
N: Into<String>
|
||||||
{
|
{
|
||||||
let mut words = s.split_whitespace();
|
let mut words = s.splitn(2, ' ');
|
||||||
let mut splited = words.next()?.split('@');
|
let mut splited = words.next()?.split('@');
|
||||||
let command_raw = splited.next()?;
|
let command_raw = splited.next()?;
|
||||||
let bot = splited.next();
|
let bot = splited.next();
|
||||||
let bot_name = bot_name.into();
|
let bot_name = bot_name.into();
|
||||||
match bot {
|
match bot {
|
||||||
Some(name) if name == bot_name => {}
|
Some(name) if name == bot_name => {}
|
||||||
None => {}
|
None => {}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
let command = Self::try_from(command_raw)?;
|
let mut args = words.next().unwrap_or("").to_string();
|
||||||
Some((command, words.collect()))
|
match command_raw {
|
||||||
}
|
#(
|
||||||
|
#matching_values => Some(Self::#variant_ident #variants_initialization),
|
||||||
|
)*
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue