mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Merge pull request #932 from kpp/command_separator
Implemend command_separator attr to split command and args
This commit is contained in:
commit
d591d24507
7 changed files with 113 additions and 4 deletions
|
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Now you can use `#[command(command_separator="sep")]` (default is a whitespace character) to set the separator between command and its arguments ([issue #897](https://github.com/teloxide/teloxide/issues/897))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834))
|
- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834))
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result<TokenStream> {
|
||||||
|
|
||||||
let type_name = &input.ident;
|
let type_name = &input.ident;
|
||||||
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
|
let fn_descriptions = impl_descriptions(&var_info, &command_enum);
|
||||||
let fn_parse = impl_parse(&var_info, &var_init);
|
let fn_parse = impl_parse(&var_info, &var_init, &command_enum.command_separator);
|
||||||
let fn_commands = impl_commands(&var_info);
|
let fn_commands = impl_commands(&var_info);
|
||||||
|
|
||||||
let trait_impl = quote! {
|
let trait_impl = quote! {
|
||||||
|
@ -99,6 +99,7 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::To
|
||||||
fn impl_parse(
|
fn impl_parse(
|
||||||
infos: &[Command],
|
infos: &[Command],
|
||||||
variants_initialization: &[proc_macro2::TokenStream],
|
variants_initialization: &[proc_macro2::TokenStream],
|
||||||
|
command_separator: &str,
|
||||||
) -> proc_macro2::TokenStream {
|
) -> proc_macro2::TokenStream {
|
||||||
let matching_values = infos.iter().map(|c| c.get_prefixed_command());
|
let matching_values = infos.iter().map(|c| c.get_prefixed_command());
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ fn impl_parse(
|
||||||
|
|
||||||
// 2 is used to only split once (=> in two parts),
|
// 2 is used to only split once (=> in two parts),
|
||||||
// we only need to split the command and the rest of arguments.
|
// we only need to split the command and the rest of arguments.
|
||||||
let mut words = s.splitn(2, ' ');
|
let mut words = s.splitn(2, #command_separator);
|
||||||
|
|
||||||
// Unwrap: split iterators always have at least one item
|
// Unwrap: split iterators always have at least one item
|
||||||
let mut full_command = words.next().unwrap().split('@');
|
let mut full_command = words.next().unwrap().split('@');
|
||||||
|
|
|
@ -34,6 +34,8 @@ impl Command {
|
||||||
parser,
|
parser,
|
||||||
// FIXME: error on/do not ignore separator
|
// FIXME: error on/do not ignore separator
|
||||||
separator: _,
|
separator: _,
|
||||||
|
// FIXME: error on/do not ignore command separator
|
||||||
|
command_separator: _,
|
||||||
hide,
|
hide,
|
||||||
} = attrs;
|
} = attrs;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub(crate) struct CommandAttrs {
|
||||||
pub rename: Option<(String, Span)>,
|
pub rename: Option<(String, Span)>,
|
||||||
pub parser: Option<(ParserType, Span)>,
|
pub parser: Option<(ParserType, Span)>,
|
||||||
pub separator: Option<(String, Span)>,
|
pub separator: Option<(String, Span)>,
|
||||||
|
pub command_separator: Option<(String, Span)>,
|
||||||
pub hide: Option<((), Span)>,
|
pub hide: Option<((), Span)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ enum CommandAttrKind {
|
||||||
Rename(String),
|
Rename(String),
|
||||||
ParseWith(ParserType),
|
ParseWith(ParserType),
|
||||||
Separator(String),
|
Separator(String),
|
||||||
|
CommandSeparator(String),
|
||||||
Hide,
|
Hide,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +68,7 @@ impl CommandAttrs {
|
||||||
rename: None,
|
rename: None,
|
||||||
parser: None,
|
parser: None,
|
||||||
separator: None,
|
separator: None,
|
||||||
|
command_separator: None,
|
||||||
hide: None,
|
hide: None,
|
||||||
},
|
},
|
||||||
|mut this, attr| {
|
|mut this, attr| {
|
||||||
|
@ -110,6 +113,7 @@ impl CommandAttrs {
|
||||||
Rename(r) => insert(&mut this.rename, r, attr.sp),
|
Rename(r) => insert(&mut this.rename, r, attr.sp),
|
||||||
ParseWith(p) => insert(&mut this.parser, p, attr.sp),
|
ParseWith(p) => insert(&mut this.parser, p, attr.sp),
|
||||||
Separator(s) => insert(&mut this.separator, s, attr.sp),
|
Separator(s) => insert(&mut this.separator, s, attr.sp),
|
||||||
|
CommandSeparator(s) => insert(&mut this.command_separator, s, attr.sp),
|
||||||
Hide => insert(&mut this.hide, (), attr.sp),
|
Hide => insert(&mut this.hide, (), attr.sp),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
@ -165,6 +169,7 @@ impl CommandAttr {
|
||||||
"rename" => Rename(value.expect_string()?),
|
"rename" => Rename(value.expect_string()?),
|
||||||
"parse_with" => ParseWith(ParserType::parse(value)?),
|
"parse_with" => ParseWith(ParserType::parse(value)?),
|
||||||
"separator" => Separator(value.expect_string()?),
|
"separator" => Separator(value.expect_string()?),
|
||||||
|
"command_separator" => CommandSeparator(value.expect_string()?),
|
||||||
"hide" => value.expect_none("hide").map(|_| Hide)?,
|
"hide" => value.expect_none("hide").map(|_| Hide)?,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(compile_error_at(
|
return Err(compile_error_at(
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub(crate) struct CommandEnum {
|
||||||
pub prefix: String,
|
pub prefix: String,
|
||||||
/// The bool is true if the description contains a doc comment
|
/// The bool is true if the description contains a doc comment
|
||||||
pub description: Option<(String, bool)>,
|
pub description: Option<(String, bool)>,
|
||||||
|
pub command_separator: String,
|
||||||
pub rename_rule: RenameRule,
|
pub rename_rule: RenameRule,
|
||||||
pub parser_type: ParserType,
|
pub parser_type: ParserType,
|
||||||
}
|
}
|
||||||
|
@ -14,8 +15,16 @@ pub(crate) struct CommandEnum {
|
||||||
impl CommandEnum {
|
impl CommandEnum {
|
||||||
pub fn from_attributes(attributes: &[syn::Attribute]) -> Result<Self> {
|
pub fn from_attributes(attributes: &[syn::Attribute]) -> Result<Self> {
|
||||||
let attrs = CommandAttrs::from_attributes(attributes)?;
|
let attrs = CommandAttrs::from_attributes(attributes)?;
|
||||||
let CommandAttrs { prefix, description, rename_rule, rename, parser, separator, hide } =
|
let CommandAttrs {
|
||||||
attrs;
|
prefix,
|
||||||
|
description,
|
||||||
|
rename_rule,
|
||||||
|
rename,
|
||||||
|
parser,
|
||||||
|
separator,
|
||||||
|
command_separator,
|
||||||
|
hide,
|
||||||
|
} = attrs;
|
||||||
|
|
||||||
if let Some((_rename, sp)) = rename {
|
if let Some((_rename, sp)) = rename {
|
||||||
return Err(compile_error_at(
|
return Err(compile_error_at(
|
||||||
|
@ -39,6 +48,9 @@ impl CommandEnum {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
prefix: prefix.map(|(p, _)| p).unwrap_or_else(|| "/".to_owned()),
|
prefix: prefix.map(|(p, _)| p).unwrap_or_else(|| "/".to_owned()),
|
||||||
description: description.map(|(d, is_doc, _)| (d, is_doc)),
|
description: description.map(|(d, is_doc, _)| (d, is_doc)),
|
||||||
|
command_separator: command_separator
|
||||||
|
.map(|(s, _)| s)
|
||||||
|
.unwrap_or_else(|| String::from(" ")),
|
||||||
rename_rule: rename_rule.map(|(rr, _)| rr).unwrap_or(RenameRule::Identity),
|
rename_rule: rename_rule.map(|(rr, _)| rr).unwrap_or(RenameRule::Identity),
|
||||||
parser_type: parser,
|
parser_type: parser,
|
||||||
})
|
})
|
||||||
|
|
|
@ -155,6 +155,30 @@ pub use teloxide_macros::BotCommands;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// 6. `#[command(command_separator = "sep")]`
|
||||||
|
/// Specify separator between command and args. Default is a space character.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// # #[cfg(feature = "macros")] {
|
||||||
|
/// use teloxide::utils::command::BotCommands;
|
||||||
|
///
|
||||||
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
|
/// #[command(
|
||||||
|
/// rename_rule = "lowercase",
|
||||||
|
/// parse_with = "split",
|
||||||
|
/// separator = "_",
|
||||||
|
/// command_separator = "_"
|
||||||
|
/// )]
|
||||||
|
/// enum Command {
|
||||||
|
/// Nums(u8, u16, i32),
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let command = Command::parse("/nums_1_32_5", "").unwrap();
|
||||||
|
/// assert_eq!(command, Command::Nums(1, 32, 5));
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// # Variant attributes
|
/// # Variant attributes
|
||||||
/// All variant attributes override the corresponding `enum` attributes.
|
/// All variant attributes override the corresponding `enum` attributes.
|
||||||
///
|
///
|
||||||
|
|
|
@ -166,6 +166,67 @@ fn parse_with_split4() {
|
||||||
assert_eq!(DefaultCommands::Start(), DefaultCommands::parse("/start", "").unwrap(),);
|
assert_eq!(DefaultCommands::Start(), DefaultCommands::parse("/start", "").unwrap(),);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_command_separator1() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split", separator = "|", command_separator = "_")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Start(u8, String),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
DefaultCommands::Start(10, "hello".to_string()),
|
||||||
|
DefaultCommands::parse("/start_10|hello", "").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_command_separator2() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split", separator = "_", command_separator = "_")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Start(u8, String),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
DefaultCommands::Start(10, "hello".to_string()),
|
||||||
|
DefaultCommands::parse("/start_10_hello", "").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_command_separator3() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split", command_separator = ":")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_command_separator4() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split", command_separator = ":")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Start(u8),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(DefaultCommands::Start(10), DefaultCommands::parse("/start:10", "").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_custom_parser() {
|
fn parse_custom_parser() {
|
||||||
|
|
Loading…
Reference in a new issue