From 291aeb727013f882950fd66456e8e1e16235f84e Mon Sep 17 00:00:00 2001 From: "Alex M. M" Date: Thu, 26 Sep 2019 19:20:33 +0200 Subject: [PATCH] Interpret doc-comments as the description attribute (#724) Closes #723 --- command_attr/src/lib.rs | 35 +++++++++++++++++++++++++++++----- command_attr/src/structures.rs | 22 +++++++++++++++------ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/command_attr/src/lib.rs b/command_attr/src/lib.rs index deae314a355..f7b665fd2f7 100644 --- a/command_attr/src/lib.rs +++ b/command_attr/src/lib.rs @@ -78,6 +78,11 @@ macro_rules! match_options { /// | `#[owner_privilege]`
`#[owner_privilege(b)]` | If owners can bypass certain options. | `b` is a boolean. If no boolean is provided, the value is assumed to be `true`. | /// | `#[sub_commands(commands)]` | The sub or children commands of this command. They are executed in the form: `this-command sub-command`. | `commands` is a comma separated list of identifiers referencing functions marked by the `#[command]` macro. | /// +/// Documentation comments (`///`) applied onto the function are interpreted as sugar for the +/// `#[description]` option. When more than one application of the option is performed, +/// the text is delimited by newlines. This mimics the behaviour of regular doc-comments, +/// which are sugar for the `#[doc = "..."]` attribute. +/// /// # Notes /// The name of the command is parsed from the applied function, /// or may be specified inside the `#[command]` attribute, a lá `#[command("foobar")]`. @@ -122,12 +127,22 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream { .examples .push(propagate_err!(attributes::parse(values))); } + "description" => { + let arg: String = propagate_err!(attributes::parse(values)); + + if let Some(desc) = &mut options.description.0 { + use std::fmt::Write; + + let _ = write!(desc, "\n{}", arg.trim_matches(' ')); + } else { + options.description = AsOption(Some(arg)); + } + } _ => { match_options!(name, values, options, span => [ checks; bucket; aliases; - description; delimiters; usage; min_args; @@ -562,6 +577,17 @@ pub fn group(attr: TokenStream, input: TokenStream) -> TokenStream { "prefix" => { options.prefixes = vec![propagate_err!(attributes::parse(values))]; } + "description" => { + let arg: String = propagate_err!(attributes::parse(values)); + + if let Some(desc) = &mut options.description.0 { + use std::fmt::Write; + + let _ = write!(desc, "\n{}", arg.trim_matches(' ')); + } else { + options.description = AsOption(Some(arg)); + } + } _ => match_options!(name, values, options, span => [ prefixes; only_in; @@ -572,7 +598,6 @@ pub fn group(attr: TokenStream, input: TokenStream) -> TokenStream { required_permissions; checks; default_command; - description; commands; sub_groups ]), @@ -600,10 +625,10 @@ pub fn group(attr: TokenStream, input: TokenStream) -> TokenStream { let n = group.name.with_suffix(GROUP); let default_command = default_command.map(|ident| { - let i = ident.with_suffix(COMMAND); + let i = ident.with_suffix(COMMAND); - quote!(&#i) - }); + quote!(&#i) + }); let commands = commands .into_iter() diff --git a/command_attr/src/structures.rs b/command_attr/src/structures.rs index 5ca43fc6f5e..33ae85be4f8 100644 --- a/command_attr/src/structures.rs +++ b/command_attr/src/structures.rs @@ -7,7 +7,8 @@ use syn::{ braced, parse::{Error, Parse, ParseStream, Result}, spanned::Spanned, - Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility, + Attribute, Block, FnArg, Ident, Pat, Path, PathSegment, ReturnType, Stmt, Token, Type, + Visibility, }; #[derive(Debug, PartialEq)] @@ -91,7 +92,7 @@ fn parse_argument(arg: FnArg) -> Result { pub struct CommandFun { /// `#[...]`-style attributes. pub attributes: Vec, - /// Populated by either `#[cfg(...)]` or `#[doc = "..."]` (the desugared form of doc-comments) type of attributes. + /// Populated by `#[cfg(...)]` type attributes. pub cooked: Vec, pub visibility: Visibility, pub name: Ident, @@ -104,9 +105,18 @@ impl Parse for CommandFun { fn parse(input: ParseStream<'_>) -> Result { let attributes = input.call(Attribute::parse_outer)?; - let (cooked, attributes): (Vec<_>, Vec<_>) = attributes - .into_iter() - .partition(|a| a.path.is_ident("cfg") || a.path.is_ident("doc")); + let (cooked, mut attributes): (Vec<_>, Vec<_>) = + attributes.into_iter().partition(|a| a.path.is_ident("cfg")); + + for attr in &mut attributes { + // Rename documentation comment attributes (`#[doc = "..."]`) to `#[description = "..."]`. + if attr.path.is_ident("doc") { + attr.path = Path::from(PathSegment::from(Ident::new( + "description", + Span::call_site(), + ))); + } + } let visibility = input.parse::()?; @@ -149,8 +159,8 @@ impl Parse for CommandFun { impl ToTokens for CommandFun { fn to_tokens(&self, stream: &mut TokenStream2) { let Self { - cooked, attributes: _, + cooked, visibility, name, args,