diff --git a/crates/teloxide-macros/src/command_attr.rs b/crates/teloxide-macros/src/command_attr.rs index 1e01aed1..dcc9f148 100644 --- a/crates/teloxide-macros/src/command_attr.rs +++ b/crates/teloxide-macros/src/command_attr.rs @@ -7,7 +7,8 @@ use crate::{ }; use proc_macro2::Span; -use syn::Attribute; +use quote::quote_spanned; +use syn::{parse::Parser, spanned::Spanned, Attribute}; /// All attributes that can be used for `derive(BotCommands)` pub(crate) struct CommandAttrs { @@ -48,9 +49,40 @@ enum CommandAttrKind { impl CommandAttrs { pub fn from_attributes(attributes: &[Attribute]) -> Result { use CommandAttrKind::*; + // Convert the `doc` attribute into `command(description = "...")` + let attributes = attributes + .iter() + .map(|attr| { + if attr.path.is_ident("doc") { + // Extract the token literal from the doc attribute. + let description = attr + .tokens + .clone() + .into_iter() + .nth(1) + .map(|t| { + // remove first and last quotes only, is expected to be a string literal + let mut s = t.to_string(); + s.remove(0); + s.pop(); + s.trim().replace(r"\\n", "\n") + }) + .unwrap_or_default(); + // Convert the doc attribute into a command description attribute. + let sp = attr.span(); + let attr = Attribute::parse_outer + .parse2(quote_spanned!(sp => #[command(description = #description)])) + .unwrap(); + // SAFETY: `parse_outer` always returns a single attribute. + attr.into_iter().next().unwrap() + } else { + attr.clone() + } + }) + .collect::>(); fold_attrs( - attributes, + attributes.iter(), is_command_attribute, CommandAttr::parse, Self {