mirror of
https://github.com/teloxide/teloxide.git
synced 2025-01-03 17:52:12 +01:00
Somewhat rework attribute parsing to make it easier to handle #[doc]
and more complex stuff
This turned out to be a lot worse than I anticipated, but it works, ok? :')
This commit is contained in:
parent
31f53f58fc
commit
2162fbdf5c
2 changed files with 141 additions and 53 deletions
|
@ -1,10 +1,10 @@
|
||||||
use crate::{error::compile_error_at, Result};
|
use crate::{error::compile_error_at, Result};
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::{Delimiter, Span};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseBuffer, ParseStream},
|
parse::{Parse, ParseStream, Parser},
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
Attribute, Ident, Lit, LitStr, Path, Token,
|
Attribute, Ident, Lit, Path, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn fold_attrs<A, R>(
|
pub(crate) fn fold_attrs<A, R>(
|
||||||
|
@ -17,31 +17,38 @@ pub(crate) fn fold_attrs<A, R>(
|
||||||
attrs
|
attrs
|
||||||
.filter(filter)
|
.filter(filter)
|
||||||
.flat_map(|attribute| {
|
.flat_map(|attribute| {
|
||||||
// FIXME: don't allocate here
|
let Some(key) = attribute.path.get_ident().cloned()
|
||||||
if crate::command_attr::is_doc_comment(&attribute) {
|
else { return vec![Err(compile_error_at("expected an ident", attribute.path.span()))] };
|
||||||
vec![parse(Attr {
|
|
||||||
key: Ident::new("description", Span::call_site()),
|
match (|input: ParseStream<'_>| Attrs::parse_with_key(input, key))
|
||||||
value: AttrValue::Lit(
|
.parse(attribute.tokens.into())
|
||||||
LitStr::new(
|
{
|
||||||
&crate::command_attr::parse_doc_comment(&attribute)
|
Ok(ok) => ok.0.into_iter().map(&parse).collect(),
|
||||||
.expect("it is doc comment"),
|
Err(err) => vec![Err(err.into())],
|
||||||
Span::call_site(),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
})]
|
|
||||||
} else {
|
|
||||||
match attribute.parse_args_with(|input: &ParseBuffer| {
|
|
||||||
input.parse_terminated::<_, Token![,]>(Attr::parse)
|
|
||||||
}) {
|
|
||||||
Ok(ok) => ok.into_iter().map(&parse).collect(),
|
|
||||||
Err(err) => vec![Err(err.into())],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.try_fold(init, |acc, r| r.and_then(|r| f(acc, r)))
|
.try_fold(init, |acc, r| f(acc, r?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper to parse a set of attributes.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```text
|
||||||
|
/// #[blahblah(key = "puff", value = 12, nope, inner(what = some::path))]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The code above will produce
|
||||||
|
/// ```test
|
||||||
|
/// [
|
||||||
|
/// Attr { key: [key, blahblah], value: "puff" },
|
||||||
|
/// Attr { key: [value, blahblah], value: 12 },
|
||||||
|
/// Attr { key: [nope, blahblah], value: none },
|
||||||
|
/// Attr { key: [what, inner, blahblah], value: some::path },
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct Attrs(Vec<Attr>);
|
||||||
|
|
||||||
/// An attribute key-value pair.
|
/// An attribute key-value pair.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
|
@ -49,8 +56,17 @@ pub(crate) fn fold_attrs<A, R>(
|
||||||
/// #[blahblah(key = "puff", value = 12, nope)]
|
/// #[blahblah(key = "puff", value = 12, nope)]
|
||||||
/// ^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^
|
/// ^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct Attr {
|
pub(crate) struct Attr {
|
||||||
pub key: Ident,
|
/// The key captures the full "path" in the reverse order, for example here:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// #[blahblah(key = "puff")]
|
||||||
|
/// ^^^^^^^^^^^^
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The `key` will be `[key, blahblah]`. See [Attrs] for more examples.
|
||||||
|
pub key: Vec<Ident>,
|
||||||
pub value: AttrValue,
|
pub value: AttrValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,16 +77,53 @@ pub(crate) struct Attr {
|
||||||
/// #[blahblah(key = "puff", value = 12, nope)]
|
/// #[blahblah(key = "puff", value = 12, nope)]
|
||||||
/// ^^^^^^ ^^ ^-- (None pseudo-value)
|
/// ^^^^^^ ^^ ^-- (None pseudo-value)
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) enum AttrValue {
|
pub(crate) enum AttrValue {
|
||||||
Path(Path),
|
Path(Path),
|
||||||
Lit(Lit),
|
Lit(Lit),
|
||||||
None(Span),
|
None(Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Attr {
|
impl Parse for Attrs {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Attrs> {
|
||||||
let key = input.parse::<Ident>()?;
|
let key = input.parse::<Ident>()?;
|
||||||
|
|
||||||
|
Attrs::parse_with_key(input, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attrs {
|
||||||
|
fn parse_with_key(input: ParseStream, key: Ident) -> syn::Result<Attrs> {
|
||||||
|
// Parse an attribute group
|
||||||
|
let attrs = input.step(|cursor| {
|
||||||
|
if let Some((group, _sp, next_cursor)) = cursor.group(Delimiter::Parenthesis) {
|
||||||
|
if !next_cursor.eof() {
|
||||||
|
return Err(syn::Error::new(next_cursor.span(), "unexpected tokens"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut attrs =
|
||||||
|
(|input: ParseStream<'_>| input.parse_terminated::<_, Token![,]>(Attrs::parse))
|
||||||
|
.parse(group.token_stream().into())?
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|mut l, r| {
|
||||||
|
l.0.extend(r.0);
|
||||||
|
l
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
attrs.0.iter_mut().for_each(|attr| attr.key.push(key.clone()));
|
||||||
|
|
||||||
|
Ok((Some(attrs), next_cursor))
|
||||||
|
} else {
|
||||||
|
Ok((None, *cursor))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(attrs) = attrs {
|
||||||
|
return Ok(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a single attribute
|
||||||
let value = match input.peek(Token![=]) {
|
let value = match input.peek(Token![=]) {
|
||||||
true => {
|
true => {
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
|
@ -79,13 +132,18 @@ impl Parse for Attr {
|
||||||
false => AttrValue::None(input.span()),
|
false => AttrValue::None(input.span()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { key, value })
|
Ok(Attrs(vec![Attr { key: vec![key], value }]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attr {
|
impl Attr {
|
||||||
pub(crate) fn span(&self) -> Span {
|
pub(crate) fn span(&self) -> Span {
|
||||||
self.key.span().join(self.value.span()).unwrap_or_else(|| self.key.span())
|
self.key().span().join(self.value.span()).unwrap_or_else(|| self.key().span())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key(&self) -> &Ident {
|
||||||
|
// It's an invariant of the type that `self.key` is non-empty
|
||||||
|
self.key.first().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,22 +111,63 @@ impl CommandAttr {
|
||||||
use CommandAttrKind::*;
|
use CommandAttrKind::*;
|
||||||
|
|
||||||
let sp = attr.span();
|
let sp = attr.span();
|
||||||
let Attr { key, value } = attr;
|
let Attr { mut key, value } = attr;
|
||||||
let kind = match &*key.to_string() {
|
|
||||||
"prefix" => Prefix(value.expect_string()?),
|
let outermost_key = key.pop().unwrap(); // `Attr`'s invariants ensure `key.len() > 0`
|
||||||
"description" => Description(value.expect_string()?),
|
|
||||||
"rename_rule" => {
|
let kind = match &*outermost_key.to_string() {
|
||||||
RenameRule(value.expect_string().and_then(|r| self::RenameRule::parse(&r))?)
|
"doc" => {
|
||||||
|
if let Some(unexpected_key) = key.last() {
|
||||||
|
return Err(compile_error_at(
|
||||||
|
"`doc` can't have nested attributes",
|
||||||
|
unexpected_key.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(awiteb): flag here that this is a doc comment
|
||||||
|
Description(value.expect_string()?)
|
||||||
}
|
}
|
||||||
"rename" => Rename(value.expect_string()?),
|
|
||||||
"parse_with" => ParseWith(ParserType::parse(value)?),
|
"command" => {
|
||||||
"separator" => Separator(value.expect_string()?),
|
let Some(attr) = key.pop()
|
||||||
"hide" => value.expect_none("hide").map(|_| Hide)?,
|
else {
|
||||||
|
return Err(compile_error_at(
|
||||||
|
"expected an attribute name",
|
||||||
|
outermost_key.span(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(unexpected_key) = key.last() {
|
||||||
|
return Err(compile_error_at(
|
||||||
|
&format!("{attr} can't have nested attributes"),
|
||||||
|
unexpected_key.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &*attr.to_string() {
|
||||||
|
"prefix" => Prefix(value.expect_string()?),
|
||||||
|
"description" => Description(value.expect_string()?),
|
||||||
|
"rename_rule" => {
|
||||||
|
RenameRule(value.expect_string().and_then(|r| self::RenameRule::parse(&r))?)
|
||||||
|
}
|
||||||
|
"rename" => Rename(value.expect_string()?),
|
||||||
|
"parse_with" => ParseWith(ParserType::parse(value)?),
|
||||||
|
"separator" => Separator(value.expect_string()?),
|
||||||
|
"hide" => value.expect_none("hide").map(|_| Hide)?,
|
||||||
|
_ => {
|
||||||
|
return Err(compile_error_at(
|
||||||
|
"unexpected attribute name (expected one of `prefix`, `description`, \
|
||||||
|
`rename`, `parse_with`, `separator` and `hide`",
|
||||||
|
attr.span(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(compile_error_at(
|
return Err(compile_error_at(
|
||||||
"unexpected attribute name (expected one of `prefix`, `description`, \
|
"unexpected attribute (expected `command` or `doc`)",
|
||||||
`rename`, `parse_with`, `separator` and `hide`",
|
outermost_key.span(),
|
||||||
key.span(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -148,14 +189,3 @@ pub(crate) fn is_doc_comment(a: &Attribute) -> bool {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_doc_comment(attr: &Attribute) -> Option<String> {
|
|
||||||
if is_doc_comment(attr) {
|
|
||||||
if let syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) =
|
|
||||||
attr.parse_meta().ok()?
|
|
||||||
{
|
|
||||||
return Some(s.value().trim().to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue