diff --git a/crates/teloxide-macros/CHANGELOG.md b/crates/teloxide-macros/CHANGELOG.md index 10b1109e..f5ced9e1 100644 --- a/crates/teloxide-macros/CHANGELOG.md +++ b/crates/teloxide-macros/CHANGELOG.md @@ -10,6 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834)) +### Added + +- Now you can use `#[command(hide)]` to hide a command from the help message ([PR #862](https://github.com/teloxide/teloxide/pull/862)) + +### Deprecated + +- `off` in `#[command(description = "off")]` is deprecated in favour of `#[command(hide)]` + ## 0.7.1 - 2023-01-17 ### Fixed diff --git a/crates/teloxide-macros/src/bot_commands.rs b/crates/teloxide-macros/src/bot_commands.rs index 581a3611..960184f6 100644 --- a/crates/teloxide-macros/src/bot_commands.rs +++ b/crates/teloxide-macros/src/bot_commands.rs @@ -4,7 +4,7 @@ use crate::{ }; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::DeriveInput; pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result { @@ -45,7 +45,7 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result { fn impl_commands(infos: &[Command]) -> proc_macro2::TokenStream { let commands = infos.iter().filter(|command| command.description_is_enabled()).map(|command| { let c = command.get_prefixed_command(); - let d = command.description.as_deref().unwrap_or_default(); + let d = command.description().unwrap_or_default(); quote! { BotCommand::new(#c,#d) } }); @@ -61,11 +61,21 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::To let command_descriptions = infos .iter() .filter(|command| command.description_is_enabled()) - .map(|Command { prefix, name, description, ..}| { - let description = description.clone().unwrap_or_default(); + .map(|command @ Command { prefix, name, ..}| { + let description = command.description().unwrap_or_default(); quote! { CommandDescription { prefix: #prefix, command: #name, description: #description } } }); + let warnings = infos.iter().filter_map(|command| command.deprecated_description_off_span()).map(|span| { + quote_spanned! { span => + const _: () = { + #[deprecated(note="\n`description = \"off\"` is deprecated, use `hide` instead")] + struct Deprecated; + _ = Deprecated; + }; + } + }); + let global_description = match global.description.as_deref() { Some(gd) => quote! { .global_description(#gd) }, None => quote! {}, @@ -76,6 +86,8 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::To use teloxide::utils::command::{CommandDescriptions, CommandDescription}; use std::borrow::Cow; + #(#warnings)* + CommandDescriptions::new(&[ #(#command_descriptions),* ]) diff --git a/crates/teloxide-macros/src/command.rs b/crates/teloxide-macros/src/command.rs index fb421e8f..d26c85f7 100644 --- a/crates/teloxide-macros/src/command.rs +++ b/crates/teloxide-macros/src/command.rs @@ -1,3 +1,5 @@ +use proc_macro2::Span; + use crate::{ command_attr::CommandAttrs, command_enum::CommandEnum, error::compile_error_at, fields_parse::ParserType, Result, @@ -7,11 +9,13 @@ pub(crate) struct Command { /// Prefix of this command, for example "/". pub prefix: String, /// Description for the command. - pub description: Option, + pub description: Option<(String, Span)>, /// Name of the command, with all renames already applied. pub name: String, /// Parser for arguments of this command. pub parser: ParserType, + /// Whether the command is hidden from the help message. + pub hidden: bool, } impl Command { @@ -29,6 +33,7 @@ impl Command { parser, // FIXME: error on/do not ignore separator separator: _, + hide, } = attrs; let name = match (rename, rename_rule) { @@ -44,10 +49,10 @@ impl Command { }; let prefix = prefix.map(|(p, _)| p).unwrap_or_else(|| global_options.prefix.clone()); - let description = description.map(|(d, _)| d); let parser = parser.map(|(p, _)| p).unwrap_or_else(|| global_options.parser_type.clone()); + let hidden = hide.is_some(); - Ok(Self { prefix, description, parser, name }) + Ok(Self { prefix, description, parser, name, hidden }) } pub fn get_prefixed_command(&self) -> String { @@ -55,7 +60,16 @@ impl Command { format!("{prefix}{name}") } + pub fn description(&self) -> Option<&str> { + self.description.as_ref().map(|(d, _span)| &**d) + } + pub(crate) fn description_is_enabled(&self) -> bool { - self.description != Some("off".to_owned()) + // FIXME: remove the first, `== "off"`, check eventually + self.description() != Some("off") && !self.hidden + } + + pub(crate) fn deprecated_description_off_span(&self) -> Option { + self.description.as_ref().filter(|(d, _)| d == "off").map(|&(_, span)| span) } } diff --git a/crates/teloxide-macros/src/command_attr.rs b/crates/teloxide-macros/src/command_attr.rs index 96c9c2b7..5c916a7b 100644 --- a/crates/teloxide-macros/src/command_attr.rs +++ b/crates/teloxide-macros/src/command_attr.rs @@ -17,6 +17,7 @@ pub(crate) struct CommandAttrs { pub rename: Option<(String, Span)>, pub parser: Option<(ParserType, Span)>, pub separator: Option<(String, Span)>, + pub hide: Option<((), Span)>, } /// A single k/v attribute for `BotCommands` derive macro. @@ -41,6 +42,7 @@ enum CommandAttrKind { Rename(String), ParseWith(ParserType), Separator(String), + Hide, } impl CommandAttrs { @@ -58,6 +60,7 @@ impl CommandAttrs { rename: None, parser: None, separator: None, + hide: None, }, |mut this, attr| { fn insert(opt: &mut Option<(T, Span)>, x: T, sp: Span) -> Result<()> { @@ -77,6 +80,7 @@ impl CommandAttrs { Rename(r) => insert(&mut this.rename, r, attr.sp), ParseWith(p) => insert(&mut this.parser, p, attr.sp), Separator(s) => insert(&mut this.separator, s, attr.sp), + Hide => insert(&mut this.hide, (), attr.sp), }?; Ok(this) @@ -100,10 +104,11 @@ impl CommandAttr { "rename" => Rename(value.expect_string()?), "parse_with" => ParseWith(ParserType::parse(value)?), "separator" => Separator(value.expect_string()?), + "hide" => Hide, _ => { return Err(compile_error_at( "unexpected attribute name (expected one of `prefix`, `description`, \ - `rename`, `parse_with` and `separator`", + `rename`, `parse_with`, `separator` and `hide`", key.span(), )) } diff --git a/crates/teloxide-macros/src/command_enum.rs b/crates/teloxide-macros/src/command_enum.rs index 83f20dec..3648d3ae 100644 --- a/crates/teloxide-macros/src/command_enum.rs +++ b/crates/teloxide-macros/src/command_enum.rs @@ -13,13 +13,19 @@ pub(crate) struct CommandEnum { impl CommandEnum { pub fn from_attributes(attributes: &[syn::Attribute]) -> Result { let attrs = CommandAttrs::from_attributes(attributes)?; - let CommandAttrs { prefix, description, rename_rule, rename, parser, separator } = attrs; + let CommandAttrs { prefix, description, rename_rule, rename, parser, separator, hide } = + attrs; if let Some((_rename, sp)) = rename { return Err(compile_error_at( "`rename` attribute can only be applied to enums *variants*", sp, )); + } else if let Some((_hide, sp)) = hide { + return Err(compile_error_at( + "`hide` attribute can only be applied to enums *variants*", + sp, + )); } let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default); diff --git a/crates/teloxide/src/utils/command.rs b/crates/teloxide/src/utils/command.rs index 763f1f7a..78a4df7c 100644 --- a/crates/teloxide/src/utils/command.rs +++ b/crates/teloxide/src/utils/command.rs @@ -168,14 +168,16 @@ pub use teloxide_macros::BotCommands; /// `rename_rule`). /// /// 3. `#[command(description = "description")]` -/// Give your command a description. Write `"off"` for `"description"` to hide a -/// command. +/// Give your command a description. It will be shown in the help message. /// /// 4. `#[command(parse_with = "parser")]` /// Parse arguments of one command with a given parser. `parser` must be a /// function of the signature `fn(String) -> Result`, where /// `Tuple` corresponds to the variant's arguments. /// +/// 5. `#[command(hide)]` +/// Hide a command from the help message. It will still be parsed. +/// /// ## Example /// ``` /// # #[cfg(feature = "macros")] { diff --git a/crates/teloxide/tests/command.rs b/crates/teloxide/tests/command.rs index 245b4c33..c80e71bc 100644 --- a/crates/teloxide/tests/command.rs +++ b/crates/teloxide/tests/command.rs @@ -232,8 +232,10 @@ fn descriptions_off() { #[derive(BotCommands, Debug, PartialEq)] #[command(rename_rule = "lowercase")] enum DefaultCommands { - #[command(description = "off")] + #[command(hide)] Start, + #[command(hide)] + Username, Help, }