mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-14 11:44:04 +01:00
Migrate teloxide-macros to the separate repository
This commit is contained in:
parent
4aeddbd7b4
commit
825c2c7499
7 changed files with 0 additions and 378 deletions
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "teloxide-macros"
|
||||
version = "0.1.2"
|
||||
description = "The teloxide's macros for internal usage"
|
||||
authors = ["p0lunin <dmytro.polunin@gmail.com>"]
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.2"
|
||||
syn = "1.0.13"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2020 teloxide
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,64 +0,0 @@
|
|||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
LitStr, Token,
|
||||
};
|
||||
|
||||
pub enum BotCommandAttribute {
|
||||
Prefix,
|
||||
Description,
|
||||
RenameRule,
|
||||
}
|
||||
|
||||
impl Parse for BotCommandAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let name_arg: syn::Ident = input.parse()?;
|
||||
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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Attr {
|
||||
name: BotCommandAttribute,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Parse for Attr {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let name = input.parse::<BotCommandAttribute>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let value = input.parse::<LitStr>()?.value();
|
||||
|
||||
Ok(Self { name, value })
|
||||
}
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
pub fn name(&self) -> &BotCommandAttribute {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> String {
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VecAttrs {
|
||||
pub data: Vec<Attr>,
|
||||
}
|
||||
|
||||
impl Parse for VecAttrs {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let mut data = vec![];
|
||||
while !input.is_empty() {
|
||||
data.push(input.parse()?);
|
||||
if !input.is_empty() {
|
||||
input.parse::<Token![,]>()?;
|
||||
}
|
||||
}
|
||||
Ok(Self { data })
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
use crate::{
|
||||
attr::{Attr, BotCommandAttribute},
|
||||
rename_rules::rename_by_rule,
|
||||
};
|
||||
|
||||
pub struct Command {
|
||||
pub prefix: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
pub renamed: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
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;
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return Err("unexpected attribute".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CommandAttrs { prefix, description, rename: rename_rule })
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
use crate::attr::{Attr, BotCommandAttribute};
|
||||
|
||||
pub struct CommandEnum {
|
||||
pub prefix: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub rename_rule: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandEnum {
|
||||
pub fn try_from(attrs: &[Attr]) -> Result<Self, String> {
|
||||
let attrs = parse_attrs(attrs)?;
|
||||
|
||||
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("disallowed value".to_owned()),
|
||||
}
|
||||
}
|
||||
Ok(Self { 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()),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return Err("unexpected attribute".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CommandAttrs { prefix, description, rename: rename_rule })
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
mod attr;
|
||||
mod command;
|
||||
mod enum_attributes;
|
||||
mod rename_rules;
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate syn;
|
||||
use crate::{
|
||||
attr::{Attr, VecAttrs},
|
||||
command::Command,
|
||||
enum_attributes::CommandEnum,
|
||||
rename_rules::rename_by_rule,
|
||||
};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
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: &syn::DataEnum = get_or_return!(get_enum_data(&input));
|
||||
|
||||
let 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,
|
||||
Err(e) => return compile_error(e),
|
||||
};
|
||||
|
||||
let variants: Vec<&syn::Variant> =
|
||||
data_enum.variants.iter().map(|attr| attr).collect();
|
||||
|
||||
let mut variant_infos = vec![];
|
||||
for variant in variants.iter() {
|
||||
let mut attrs = Vec::new();
|
||||
for attr in &variant.attrs {
|
||||
match attr.parse_args::<VecAttrs>() {
|
||||
Ok(mut attrs_) => {
|
||||
attrs.append(attrs_.data.as_mut());
|
||||
}
|
||||
Err(e) => {
|
||||
return compile_error(e.to_compile_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = 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
|
||||
} else if let Some(prefix) = &command_enum.prefix {
|
||||
prefix
|
||||
} else {
|
||||
"/"
|
||||
}
|
||||
});
|
||||
let variant_str1 = variant_prefixes
|
||||
.zip(variant_name)
|
||||
.map(|(prefix, command)| prefix.to_string() + command.as_str());
|
||||
let variant_str2 = variant_str1.clone();
|
||||
let variant_description = variant_infos.iter().map(|info| {
|
||||
info.description
|
||||
.as_deref()
|
||||
.map(|e| format!(" - {}", e))
|
||||
.unwrap_or(String::new())
|
||||
});
|
||||
|
||||
let ident = &input.ident;
|
||||
|
||||
let global_description = if let Some(s) = &command_enum.description {
|
||||
quote! { #s, "\n", }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
impl BotCommand for #ident {
|
||||
fn try_from(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
#(
|
||||
#variant_str1 => Some(Self::#variant_ident),
|
||||
)*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
fn descriptions() -> String {
|
||||
std::concat!(#global_description #(#variant_str2, #variant_description, '\n'),*).to_string()
|
||||
}
|
||||
fn parse<N>(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)>
|
||||
where
|
||||
N: Into<String>
|
||||
{
|
||||
let mut words = s.split_whitespace();
|
||||
let mut splited = words.next()?.split('@');
|
||||
let command_raw = splited.next()?;
|
||||
let bot = splited.next();
|
||||
let bot_name = bot_name.into();
|
||||
match bot {
|
||||
Some(name) if name == bot_name => {}
|
||||
None => {}
|
||||
_ => return None,
|
||||
}
|
||||
let command = Self::try_from(command_raw)?;
|
||||
Some((command, words.collect()))
|
||||
}
|
||||
}
|
||||
};
|
||||
//for debug
|
||||
//println!("{}", &expanded.to_string());
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
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: &[syn::Attribute],
|
||||
) -> Result<Vec<Attr>, TokenStream> {
|
||||
let mut enum_attrs = Vec::new();
|
||||
for attr in input.iter() {
|
||||
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,
|
||||
{
|
||||
TokenStream::from(quote! { compile_error!(#data) })
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
pub fn rename_by_rule(input: &str, rule: &str) -> String {
|
||||
match rule {
|
||||
"lowercase" => input.to_string().to_lowercase(),
|
||||
_ => rule.to_string(),
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue