mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-03 17:52:12 +01:00
refactor attributes once again
This commit is contained in:
parent
25f9bff97a
commit
c8514823d7
8 changed files with 302 additions and 129 deletions
195
src/attr.rs
195
src/attr.rs
|
@ -1,96 +1,153 @@
|
|||
use crate::Result;
|
||||
use crate::{error::compile_error_at, Result};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
Attribute, LitStr, Token,
|
||||
spanned::Spanned,
|
||||
Attribute, Ident, Lit, Path, Token,
|
||||
};
|
||||
|
||||
pub(crate) enum CommandAttrName {
|
||||
Prefix,
|
||||
Description,
|
||||
Rename,
|
||||
ParseWith,
|
||||
Separator,
|
||||
pub(crate) fn fold_attrs<A, R>(
|
||||
attrs: &[Attribute],
|
||||
filter: fn(&Attribute) -> bool,
|
||||
parse: impl Fn(Attr) -> Result<R>,
|
||||
init: A,
|
||||
f: impl Fn(A, R) -> Result<A>,
|
||||
) -> Result<A> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|&a| filter(a))
|
||||
.flat_map(|attribute| {
|
||||
// FIXME: don't allocate here
|
||||
let attrs =
|
||||
match attribute.parse_args_with(|input: &ParseBuffer| {
|
||||
input.parse_terminated::<_, Token![,]>(Attr::parse)
|
||||
}) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return vec![Err(err.into())],
|
||||
};
|
||||
|
||||
attrs.into_iter().map(&parse).collect()
|
||||
})
|
||||
.try_fold(init, |acc, r| r.and_then(|r| f(acc, r)))
|
||||
}
|
||||
|
||||
impl Parse for CommandAttrName {
|
||||
/// An attribute key-value pair.
|
||||
///
|
||||
/// For example:
|
||||
/// ```text
|
||||
/// #[blahblah(key = "puff", value = 12, nope)]
|
||||
/// ^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^
|
||||
/// ```
|
||||
pub(crate) struct Attr {
|
||||
pub key: Ident,
|
||||
pub value: AttrValue,
|
||||
}
|
||||
|
||||
/// Value of an attribute.
|
||||
///
|
||||
/// For example:
|
||||
/// ```text
|
||||
/// #[blahblah(key = "puff", value = 12, nope)]
|
||||
/// ^^^^^^ ^^ ^-- (None pseudo-value)
|
||||
/// ```
|
||||
pub(crate) enum AttrValue {
|
||||
Path(Path),
|
||||
Lit(Lit),
|
||||
None(Span),
|
||||
}
|
||||
|
||||
impl Parse for Attr {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name_arg: syn::Ident = input.parse()?;
|
||||
let key = input.parse::<Ident>()?;
|
||||
|
||||
match name_arg.to_string().as_str() {
|
||||
"prefix" => Ok(CommandAttrName::Prefix),
|
||||
"description" => Ok(CommandAttrName::Description),
|
||||
"rename" => Ok(CommandAttrName::Rename),
|
||||
"parse_with" => Ok(CommandAttrName::ParseWith),
|
||||
"separator" => Ok(CommandAttrName::Separator),
|
||||
_ => Err(syn::Error::new(
|
||||
name_arg.span(),
|
||||
"unexpected attribute name (expected one of `prefix`, \
|
||||
`description`, `rename`, `parse_with`, `separator`",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CommandAttr {
|
||||
pub name: CommandAttrName,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl Parse for CommandAttr {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse::<CommandAttrName>()?;
|
||||
|
||||
// FIXME: this should support value-less attrs, as well as
|
||||
// non-string-literal values
|
||||
let value = match input.peek(Token![=]) {
|
||||
true => {
|
||||
input.parse::<Token![=]>()?;
|
||||
let value = input.parse::<LitStr>()?.value();
|
||||
input.parse::<AttrValue>()?
|
||||
}
|
||||
false => AttrValue::None(input.span()),
|
||||
};
|
||||
|
||||
Ok(Self { name, value })
|
||||
Ok(Self { key, value })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CommandAttrs(Vec<CommandAttr>);
|
||||
|
||||
impl CommandAttrs {
|
||||
pub fn from_attributes(attributes: &[Attribute]) -> Result<Self> {
|
||||
let mut attrs = Vec::new();
|
||||
|
||||
for attribute in attributes.iter().filter(is_command_attribute) {
|
||||
let attrs_ = attribute.parse_args_with(|input: &ParseBuffer| {
|
||||
input.parse_terminated::<_, Token![,]>(CommandAttr::parse)
|
||||
})?;
|
||||
|
||||
attrs.extend(attrs_);
|
||||
}
|
||||
|
||||
Ok(Self(attrs))
|
||||
impl Attr {
|
||||
pub(crate) fn span(&self) -> Span {
|
||||
self.key.span().join(self.value.span()).unwrap_or(self.key.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a CommandAttrs {
|
||||
type Item = &'a CommandAttr;
|
||||
impl AttrValue {
|
||||
/// Unwraps this value if it's a string literal.
|
||||
pub fn expect_string(self) -> Result<String> {
|
||||
self.expect("a string", |this| match this {
|
||||
AttrValue::Lit(Lit::Str(s)) => Ok(s.value()),
|
||||
_ => Err(this),
|
||||
})
|
||||
}
|
||||
|
||||
type IntoIter = std::slice::Iter<'a, CommandAttr>;
|
||||
// /// Unwraps this value if it's a path.
|
||||
// pub fn expect_path(self) -> Result<Path> {
|
||||
// self.expect("a path", |this| match this {
|
||||
// AttrValue::Path(p) => Ok(p),
|
||||
// _ => Err(this),
|
||||
// })
|
||||
// }
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
fn expect<T>(
|
||||
self,
|
||||
expected: &str,
|
||||
f: impl FnOnce(Self) -> Result<T, Self>,
|
||||
) -> Result<T> {
|
||||
f(self).map_err(|this| {
|
||||
compile_error_at(
|
||||
&format!("expected {expected}, found {}", this.descr()),
|
||||
this.span(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn descr(&self) -> &'static str {
|
||||
use Lit::*;
|
||||
|
||||
match self {
|
||||
Self::None(_) => "nothing",
|
||||
Self::Lit(l) => match l {
|
||||
Str(_) | ByteStr(_) => "a string",
|
||||
Char(_) => "a character",
|
||||
Byte(_) | Int(_) => "an integer",
|
||||
Float(_) => "a floating point integer",
|
||||
Bool(_) => "a boolean",
|
||||
Verbatim(_) => ":shrug:",
|
||||
},
|
||||
Self::Path(_) => "a path",
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for CommandAttrs {
|
||||
type Item = CommandAttr;
|
||||
|
||||
type IntoIter = std::vec::IntoIter<CommandAttr>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
/// Returns span of the value
|
||||
///
|
||||
/// ```text
|
||||
/// #[blahblah(key = "puff", value = 12, nope )]
|
||||
/// ^^^^^^ ^^ ^
|
||||
/// ```
|
||||
fn span(&self) -> Span {
|
||||
match self {
|
||||
Self::Path(p) => p.span(),
|
||||
Self::Lit(l) => l.span(),
|
||||
Self::None(sp) => *sp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_command_attribute(a: &&Attribute) -> bool {
|
||||
match a.path.get_ident() {
|
||||
Some(ident) => ident == "command",
|
||||
_ => false,
|
||||
impl Parse for AttrValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let this = match input.peek(Lit) {
|
||||
true => Self::Lit(input.parse()?),
|
||||
false => Self::Path(input.parse()?),
|
||||
};
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
attr::CommandAttrs, command::Command, command_enum::CommandEnum,
|
||||
command::Command, command_attr::CommandAttrs, command_enum::CommandEnum,
|
||||
compile_error, fields_parse::impl_parse_args, unzip::Unzip, Result,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use crate::{
|
||||
attr::{self, CommandAttr, CommandAttrName},
|
||||
command_enum::CommandEnum,
|
||||
fields_parse::ParserType,
|
||||
rename_rules::RenameRule,
|
||||
Result,
|
||||
command_attr::CommandAttrs, command_enum::CommandEnum,
|
||||
fields_parse::ParserType, rename_rules::RenameRule, Result,
|
||||
};
|
||||
|
||||
pub(crate) struct Command {
|
||||
|
@ -14,8 +11,7 @@ pub(crate) struct Command {
|
|||
}
|
||||
|
||||
impl Command {
|
||||
pub fn try_from(attrs: attr::CommandAttrs, name: &str) -> Result<Self> {
|
||||
let attrs = parse_attrs(attrs)?;
|
||||
pub fn try_from(attrs: CommandAttrs, name: &str) -> Result<Self> {
|
||||
let CommandAttrs {
|
||||
prefix,
|
||||
description,
|
||||
|
@ -24,7 +20,7 @@ impl Command {
|
|||
separator: _,
|
||||
} = attrs;
|
||||
|
||||
let name = rename_rule.apply(name);
|
||||
let name = rename_rule.unwrap_or(RenameRule::Identity).apply(name);
|
||||
|
||||
Ok(Self { prefix, description, parser, name })
|
||||
}
|
||||
|
@ -60,33 +56,3 @@ impl Command {
|
|||
self.description != Some("off".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CommandAttrs {
|
||||
pub prefix: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub rename_rule: RenameRule,
|
||||
pub parser: Option<ParserType>,
|
||||
pub separator: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_attrs(attrs: attr::CommandAttrs) -> Result<CommandAttrs> {
|
||||
let mut prefix = None;
|
||||
let mut description = None;
|
||||
let mut rename_rule = RenameRule::Identity;
|
||||
let mut parser = None;
|
||||
let mut separator = None;
|
||||
|
||||
for CommandAttr { name, value } in attrs {
|
||||
match name {
|
||||
CommandAttrName::Prefix => prefix = Some(value),
|
||||
CommandAttrName::Description => description = Some(value),
|
||||
CommandAttrName::Rename => rename_rule = RenameRule::parse(&value)?,
|
||||
CommandAttrName::ParseWith => {
|
||||
parser = Some(ParserType::parse(&value))
|
||||
}
|
||||
CommandAttrName::Separator => separator = Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CommandAttrs { prefix, description, rename_rule, parser, separator })
|
||||
}
|
||||
|
|
115
src/command_attr.rs
Normal file
115
src/command_attr.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
use crate::{
|
||||
attr::{fold_attrs, Attr},
|
||||
error::compile_error_at,
|
||||
fields_parse::ParserType,
|
||||
rename_rules::RenameRule,
|
||||
Result,
|
||||
};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::Attribute;
|
||||
|
||||
/// Attributes for `BotCommands` derive macro.
|
||||
pub(crate) struct CommandAttrs {
|
||||
pub prefix: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub rename_rule: Option<RenameRule>,
|
||||
pub parser: Option<ParserType>,
|
||||
pub separator: Option<String>,
|
||||
}
|
||||
|
||||
/// An attribute for `BotCommands` derive macro.
|
||||
pub(crate) struct CommandAttr {
|
||||
kind: CommandAttrKind,
|
||||
sp: Span,
|
||||
}
|
||||
|
||||
pub(crate) enum CommandAttrKind {
|
||||
Prefix(String),
|
||||
Description(String),
|
||||
Rename(RenameRule),
|
||||
ParseWith(ParserType),
|
||||
Separator(String),
|
||||
}
|
||||
|
||||
impl CommandAttrs {
|
||||
pub fn from_attributes(attributes: &[Attribute]) -> Result<Self> {
|
||||
use CommandAttrKind::*;
|
||||
|
||||
fold_attrs(
|
||||
attributes,
|
||||
is_command_attribute,
|
||||
CommandAttr::parse,
|
||||
Self {
|
||||
prefix: None,
|
||||
description: None,
|
||||
rename_rule: None,
|
||||
parser: None,
|
||||
separator: None,
|
||||
},
|
||||
|mut this, attr| {
|
||||
fn insert<T>(
|
||||
opt: &mut Option<T>,
|
||||
x: T,
|
||||
sp: Span,
|
||||
) -> Result<()> {
|
||||
match opt {
|
||||
slot @ None => {
|
||||
*slot = Some(x);
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => {
|
||||
Err(compile_error_at("duplicate attribute", sp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match attr.kind {
|
||||
Prefix(p) => insert(&mut this.prefix, p, attr.sp),
|
||||
Description(d) => insert(&mut this.description, d, attr.sp),
|
||||
Rename(r) => insert(&mut this.rename_rule, r, attr.sp),
|
||||
ParseWith(p) => insert(&mut this.parser, p, attr.sp),
|
||||
Separator(s) => insert(&mut this.separator, s, attr.sp),
|
||||
}?;
|
||||
|
||||
Ok(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandAttr {
|
||||
fn parse(attr: Attr) -> Result<Self> {
|
||||
use CommandAttrKind::*;
|
||||
|
||||
let sp = attr.span();
|
||||
let Attr { key, value } = attr;
|
||||
let kind = match &*key.to_string() {
|
||||
"prefix" => Prefix(value.expect_string()?),
|
||||
"description" => Description(value.expect_string()?),
|
||||
"rename" => Rename(
|
||||
value.expect_string().and_then(|r| RenameRule::parse(&r))?,
|
||||
),
|
||||
"parse_with" => {
|
||||
ParseWith(value.expect_string().map(|p| ParserType::parse(&p))?)
|
||||
}
|
||||
"separator" => Separator(value.expect_string()?),
|
||||
_ => {
|
||||
return Err(compile_error_at(
|
||||
"unexpected attribute name (expected one of `prefix`, \
|
||||
`description`, `rename`, `parse_with` and `separator`",
|
||||
key.span(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self { kind, sp })
|
||||
}
|
||||
}
|
||||
|
||||
fn is_command_attribute(a: &Attribute) -> bool {
|
||||
match a.path.get_ident() {
|
||||
Some(ident) => ident == "command",
|
||||
_ => false,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
attr, command::parse_attrs, fields_parse::ParserType,
|
||||
command_attr::CommandAttrs, fields_parse::ParserType,
|
||||
rename_rules::RenameRule, Result,
|
||||
};
|
||||
|
||||
|
@ -12,14 +12,17 @@ pub(crate) struct CommandEnum {
|
|||
}
|
||||
|
||||
impl CommandEnum {
|
||||
pub fn try_from(attrs: attr::CommandAttrs) -> Result<Self> {
|
||||
let attrs = parse_attrs(attrs)?;
|
||||
pub fn try_from(attrs: CommandAttrs) -> Result<Self> {
|
||||
let CommandAttrs {
|
||||
prefix,
|
||||
description,
|
||||
rename_rule,
|
||||
parser,
|
||||
separator,
|
||||
} = attrs;
|
||||
let mut parser = parser.unwrap_or(ParserType::Default);
|
||||
|
||||
let prefix = attrs.prefix;
|
||||
let description = attrs.description;
|
||||
let rename = attrs.rename_rule;
|
||||
let separator = attrs.separator;
|
||||
let mut parser = attrs.parser.unwrap_or(ParserType::Default);
|
||||
// FIXME: Error on unused separator
|
||||
if let (ParserType::Split { separator }, Some(s)) =
|
||||
(&mut parser, &separator)
|
||||
{
|
||||
|
@ -28,7 +31,7 @@ impl CommandEnum {
|
|||
Ok(Self {
|
||||
prefix,
|
||||
description,
|
||||
rename_rule: rename,
|
||||
rename_rule: rename_rule.unwrap_or(RenameRule::Identity),
|
||||
parser_type: parser,
|
||||
})
|
||||
}
|
||||
|
|
32
src/error.rs
32
src/error.rs
|
@ -1,4 +1,4 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
@ -13,6 +13,36 @@ where
|
|||
Error(quote! { compile_error! { #data } })
|
||||
}
|
||||
|
||||
pub(crate) fn compile_error_at(msg: &str, sp: Span) -> Error {
|
||||
use proc_macro2::{
|
||||
Delimiter, Group, Ident, Literal, Punct, Spacing, TokenTree,
|
||||
};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
// compile_error! { $msg }
|
||||
let ts = TokenStream::from_iter(vec![
|
||||
TokenTree::Ident(Ident::new("compile_error", sp)),
|
||||
TokenTree::Punct({
|
||||
let mut punct = Punct::new('!', Spacing::Alone);
|
||||
punct.set_span(sp);
|
||||
punct
|
||||
}),
|
||||
TokenTree::Group({
|
||||
let mut group = Group::new(Delimiter::Brace, {
|
||||
TokenStream::from_iter(vec![TokenTree::Literal({
|
||||
let mut string = Literal::string(msg);
|
||||
string.set_span(sp);
|
||||
string
|
||||
})])
|
||||
});
|
||||
group.set_span(sp);
|
||||
group
|
||||
}),
|
||||
]);
|
||||
|
||||
Error(ts)
|
||||
}
|
||||
|
||||
impl From<Error> for proc_macro2::TokenStream {
|
||||
fn from(Error(e): Error) -> Self {
|
||||
e
|
||||
|
|
|
@ -9,6 +9,7 @@ pub(crate) enum ParserType {
|
|||
}
|
||||
|
||||
impl ParserType {
|
||||
// FIXME: use path for custom
|
||||
pub fn parse(data: &str) -> Self {
|
||||
match data {
|
||||
"default" => ParserType::Default,
|
||||
|
|
|
@ -5,6 +5,7 @@ extern crate proc_macro;
|
|||
mod attr;
|
||||
mod bot_commands;
|
||||
mod command;
|
||||
mod command_attr;
|
||||
mod command_enum;
|
||||
mod error;
|
||||
mod fields_parse;
|
||||
|
|
Loading…
Reference in a new issue