From 0d02c48afdd2d3798b4989a719672af26ffe7076 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Tue, 29 Aug 2023 09:12:27 +0300 Subject: [PATCH] Possibility of using more than one attribute for the description --- crates/teloxide-macros/src/command_attr.rs | 50 ++++++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/teloxide-macros/src/command_attr.rs b/crates/teloxide-macros/src/command_attr.rs index 3bc25bdc..3adf71d7 100644 --- a/crates/teloxide-macros/src/command_attr.rs +++ b/crates/teloxide-macros/src/command_attr.rs @@ -50,24 +50,23 @@ impl CommandAttrs { pub fn from_attributes(attributes: &[Attribute]) -> Result { use CommandAttrKind::*; - let docs = attributes - .iter() - .filter_map(|attr| parse_doc_comment(attr).map(|doc| (doc, attr.span()))); + let docs = attributes.iter().filter_map(|attr| { + parse_doc_comment(attr) + .or_else(|| parse_command_description(attr)) + .map(|doc| (doc, attr.span())) + }); let mut attributes = attributes.to_vec(); if docs.clone().count() != 0 { - if let Some(des_sp) = get_descripation_span(&attributes) { - return Err(compile_error_at( - "You cannot use doc comments and #[command(description = \"...\")] \ - simultaneously\nYou can use only one of them", - des_sp, - )); - } + // Remove all command description attributes, to avoid duplication + attributes.retain(|attr| !is_command_description(attr)); let description = docs.clone().map(|(doc, _)| doc).collect::>().join("\n"); let sp = docs .map(|(_, sp)| sp) .reduce(|acc, sp| acc.join(sp).expect("The spans are in the same file")) .expect("There is at least one doc comment"); + // Insert a new command description attribute, with all descriptions and doc + // comments let attr = Attribute::parse_outer .parse2(quote_spanned! { sp => #[command(description = #description)] })?; attributes.push(attr.into_iter().next().unwrap()); @@ -149,21 +148,44 @@ fn is_command_attribute(a: &Attribute) -> bool { } } -/// Returns the span of the first attribute with a description meta item. -fn get_descripation_span(attrs: &[Attribute]) -> Option { - for attr in attrs { +fn is_command_description(attr: &Attribute) -> bool { + for token in attr.tokens.clone() { + if let TokenTree::Group(group) = token { + for token in group.stream() { + if let TokenTree::Ident(ident) = token { + if ident == "description" { + return true; + } + } + } + } + } + false +} + +fn parse_command_description(attr: &Attribute) -> Option { + if is_command_attribute(attr) { for token in attr.tokens.clone() { if let TokenTree::Group(group) = token { for token in group.stream() { if let TokenTree::Ident(ident) = token { if ident == "description" { - return Some(group.span()); + for token in group.stream() { + if let TokenTree::Literal(lit) = token { + let description = lit.to_string(); + return Some( + lit.to_string().trim()[1..description.len() - 1] + .replace(r"\n", "\n"), + ); + } + } } } } } } } + None }