Merge pull request #862 from TheAwiteb/hide-attr

`#[command(hide)]` to hide a command from the help message
This commit is contained in:
Sima Kinsart 2023-06-15 10:30:47 +00:00 committed by GitHub
commit 34c079cb78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 62 additions and 13 deletions

View file

@ -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

View file

@ -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<TokenStream> {
@ -45,7 +45,7 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
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),*
])

View file

@ -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<String>,
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<Span> {
self.description.as_ref().filter(|(d, _)| d == "off").map(|&(_, span)| span)
}
}

View file

@ -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<T>(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(),
))
}

View file

@ -13,13 +13,19 @@ pub(crate) struct CommandEnum {
impl CommandEnum {
pub fn from_attributes(attributes: &[syn::Attribute]) -> Result<Self> {
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);

View file

@ -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<Tuple, ParseError>`, 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")] {

View file

@ -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,
}