From 26eba3eb14ea215ade607d86c9f4eb453f6e2f57 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 2 Oct 2022 16:24:28 +0400 Subject: [PATCH 1/4] Rename `rename` => `rename_rule` --- src/command_attr.rs | 10 ++++++---- tests/command.rs | 42 +++++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/command_attr.rs b/src/command_attr.rs index 4550f679..fa15d4f0 100644 --- a/src/command_attr.rs +++ b/src/command_attr.rs @@ -27,7 +27,7 @@ pub(crate) struct CommandAttr { pub(crate) enum CommandAttrKind { Prefix(String), Description(String), - Rename(RenameRule), + RenameRule(RenameRule), ParseWith(ParserType), Separator(String), } @@ -67,7 +67,7 @@ impl CommandAttrs { match attr.kind { Prefix(p) => insert(&mut this.prefix, p, attr.sp), Description(d) => insert(&mut this.description, d, attr.sp), - Rename(r) => insert(&mut this.rename_rule, r, attr.sp), + RenameRule(r) => insert(&mut this.rename_rule, r, attr.sp), ParseWith(p) => insert(&mut this.parser, p, attr.sp), Separator(s) => insert(&mut this.separator, s, attr.sp), }?; @@ -87,8 +87,10 @@ impl CommandAttr { let kind = match &*key.to_string() { "prefix" => Prefix(value.expect_string()?), "description" => Description(value.expect_string()?), - "rename" => Rename( - value.expect_string().and_then(|r| RenameRule::parse(&r))?, + "rename_rule" => RenameRule( + value + .expect_string() + .and_then(|r| self::RenameRule::parse(&r))?, ), "parse_with" => { ParseWith(value.expect_string().map(|p| ParserType::parse(&p))?) diff --git a/tests/command.rs b/tests/command.rs index 135610cf..6300c063 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -9,7 +9,7 @@ use teloxide::utils::command::BotCommands as _; #[test] fn parse_command_with_args() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { Start(String), Help, @@ -24,7 +24,7 @@ fn parse_command_with_args() { #[test] fn parse_command_with_non_string_arg() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { Start(i32), Help, @@ -39,7 +39,7 @@ fn parse_command_with_non_string_arg() { #[test] fn attribute_prefix() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { #[command(prefix = "!")] Start(String), @@ -55,7 +55,7 @@ fn attribute_prefix() { #[test] fn many_attributes() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { #[command(prefix = "!", description = "desc")] Start, @@ -75,7 +75,11 @@ fn many_attributes() { #[test] fn global_attributes() { #[derive(BotCommands, Debug, PartialEq)] - #[command(prefix = "!", rename = "lowercase", description = "Bot commands")] + #[command( + prefix = "!", + rename_rule = "lowercase", + description = "Bot commands" + )] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -99,7 +103,7 @@ fn global_attributes() { #[test] fn parse_command_with_bot_name() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -115,7 +119,7 @@ fn parse_command_with_bot_name() { #[test] fn parse_with_split() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] #[command(parse_with = "split")] enum DefaultCommands { Start(u8, String), @@ -131,7 +135,7 @@ fn parse_with_split() { #[test] fn parse_with_split2() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] #[command(parse_with = "split", separator = "|")] enum DefaultCommands { Start(u8, String), @@ -174,7 +178,7 @@ fn parse_custom_parser() { use parser::custom_parse_function; #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { #[command(parse_with = "custom_parse_function")] Start(u8, String), @@ -199,7 +203,7 @@ fn parse_custom_parser() { #[test] fn parse_named_fields() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] #[command(parse_with = "split")] enum DefaultCommands { Start { num: u8, data: String }, @@ -215,7 +219,7 @@ fn parse_named_fields() { #[test] fn descriptions_off() { #[derive(BotCommands, Debug, PartialEq)] - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] enum DefaultCommands { #[command(description = "off")] Start, @@ -229,21 +233,21 @@ fn descriptions_off() { fn rename_rules() { #[derive(BotCommands, Debug, PartialEq)] enum DefaultCommands { - #[command(rename = "lowercase")] + #[command(rename_rule = "lowercase")] AaaAaa, - #[command(rename = "UPPERCASE")] + #[command(rename_rule = "UPPERCASE")] BbbBbb, - #[command(rename = "PascalCase")] + #[command(rename_rule = "PascalCase")] CccCcc, - #[command(rename = "camelCase")] + #[command(rename_rule = "camelCase")] DddDdd, - #[command(rename = "snake_case")] + #[command(rename_rule = "snake_case")] EeeEee, - #[command(rename = "SCREAMING_SNAKE_CASE")] + #[command(rename_rule = "SCREAMING_SNAKE_CASE")] FffFff, - #[command(rename = "kebab-case")] + #[command(rename_rule = "kebab-case")] GggGgg, - #[command(rename = "SCREAMING-KEBAB-CASE")] + #[command(rename_rule = "SCREAMING-KEBAB-CASE")] HhhHhh, //#[command(rename = "Bar")] //Foo, From ff08854ca9475791e080d8e10824f0ced27ccc20 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 2 Oct 2022 18:03:10 +0400 Subject: [PATCH 2/4] Refactor attributes AGAIN --- src/bot_commands.rs | 40 ++++++++++++++---------------- src/command.rs | 59 ++++++++++++++++++++++----------------------- src/command_attr.rs | 31 +++++++++++++++--------- src/command_enum.rs | 19 +++++++++------ src/fields_parse.rs | 2 +- 5 files changed, 80 insertions(+), 71 deletions(-) diff --git a/src/bot_commands.rs b/src/bot_commands.rs index 46d77672..89ee82ce 100644 --- a/src/bot_commands.rs +++ b/src/bot_commands.rs @@ -1,6 +1,6 @@ use crate::{ - command::Command, command_attr::CommandAttrs, command_enum::CommandEnum, - compile_error, fields_parse::impl_parse_args, unzip::Unzip, Result, + command::Command, command_enum::CommandEnum, compile_error, + fields_parse::impl_parse_args, unzip::Unzip, Result, }; use proc_macro2::TokenStream; @@ -9,22 +9,23 @@ use syn::DeriveInput; pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result { let data_enum = get_enum_data(&input)?; - let enum_attrs = CommandAttrs::from_attributes(&input.attrs)?; - let command_enum = CommandEnum::try_from(enum_attrs)?; + let command_enum = CommandEnum::from_attributes(&input.attrs)?; let Unzip(var_init, var_info) = data_enum .variants .iter() .map(|variant| { - let attrs = CommandAttrs::from_attributes(&variant.attrs)?; - let command = Command::try_from(attrs, &variant.ident.to_string())?; + let command = Command::new( + &variant.ident.to_string(), + &variant.attrs, + &command_enum, + )?; let variant_name = &variant.ident; let self_variant = quote! { Self::#variant_name }; - let parser = - command.parser.as_ref().unwrap_or(&command_enum.parser_type); - let parse = impl_parse_args(&variant.fields, self_variant, parser); + let parse = + impl_parse_args(&variant.fields, self_variant, &command.parser); Ok((parse, command)) }) @@ -32,8 +33,8 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result { let type_name = &input.ident; let fn_descriptions = impl_descriptions(&var_info, &command_enum); - let fn_parse = impl_parse(&var_info, &command_enum, &var_init); - let fn_commands = impl_commands(&var_info, &command_enum); + let fn_parse = impl_parse(&var_info, &var_init); + let fn_commands = impl_commands(&var_info); let trait_impl = quote! { impl teloxide::utils::command::BotCommands for #type_name { @@ -46,15 +47,12 @@ pub(crate) fn bot_commands_impl(input: DeriveInput) -> Result { Ok(trait_impl) } -fn impl_commands( - infos: &[Command], - global: &CommandEnum, -) -> proc_macro2::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_matched_value(global); + let c = command.get_prefixed_command(); let d = command.description.as_deref().unwrap_or_default(); quote! { BotCommand::new(#c,#d) } }); @@ -74,10 +72,9 @@ fn impl_descriptions( let command_descriptions = infos .iter() .filter(|command| command.description_is_enabled()) - .map(|c| { - let (prefix, command) = c.get_matched_value2(global); - let description = c.description.clone().unwrap_or_default(); - quote! { CommandDescription { prefix: #prefix, command: #command, description: #description } } + .map(|Command { prefix, name, description, ..}| { + let description = description.clone().unwrap_or_default(); + quote! { CommandDescription { prefix: #prefix, command: #name, description: #description } } }); let global_description = match global.description.as_deref() { @@ -100,10 +97,9 @@ fn impl_descriptions( fn impl_parse( infos: &[Command], - global: &CommandEnum, variants_initialization: &[proc_macro2::TokenStream], ) -> proc_macro2::TokenStream { - let matching_values = infos.iter().map(|c| c.get_matched_value(global)); + let matching_values = infos.iter().map(|c| c.get_prefixed_command()); quote! { fn parse(s: &str, bot_name: N) -> Result diff --git a/src/command.rs b/src/command.rs index 769c83cc..1aa8ca21 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,55 +1,54 @@ use crate::{ command_attr::CommandAttrs, command_enum::CommandEnum, - fields_parse::ParserType, rename_rules::RenameRule, Result, + fields_parse::ParserType, Result, }; pub(crate) struct Command { - pub prefix: Option, + /// Prefix of this command, for example "/". + pub prefix: String, + /// Description for the command. pub description: Option, - pub parser: Option, + /// Name of the command, with all renames already applied. pub name: String, + /// Parser for arguments of this command. + pub parser: ParserType, } impl Command { - pub fn try_from(attrs: CommandAttrs, name: &str) -> Result { + pub fn new( + name: &str, + attributes: &[syn::Attribute], + global_options: &CommandEnum, + ) -> Result { + let attrs = CommandAttrs::from_attributes(attributes)?; let CommandAttrs { prefix, description, rename_rule, parser, + // FIXME: error on/do not ignore separator separator: _, } = attrs; - let name = rename_rule.unwrap_or(RenameRule::Identity).apply(name); + let name = rename_rule + .map(|(rr, _)| rr) + .unwrap_or(global_options.rename_rule) + .apply(name); + + 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()); Ok(Self { prefix, description, parser, name }) } - pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String { - let prefix = if let Some(prefix) = &self.prefix { - prefix - } else if let Some(prefix) = &global_parameters.prefix { - prefix - } else { - "/" - }; - - String::from(prefix) + &global_parameters.rename_rule.apply(&self.name) - } - - pub fn get_matched_value2( - &self, - global_parameters: &CommandEnum, - ) -> (String, String) { - let prefix = if let Some(prefix) = &self.prefix { - prefix - } else if let Some(prefix) = &global_parameters.prefix { - prefix - } else { - "/" - }; - - (String::from(prefix), global_parameters.rename_rule.apply(&self.name)) + pub fn get_prefixed_command(&self) -> String { + let Self { prefix, name, .. } = self; + format!("{prefix}{name}") } pub(crate) fn description_is_enabled(&self) -> bool { diff --git a/src/command_attr.rs b/src/command_attr.rs index fa15d4f0..c8937891 100644 --- a/src/command_attr.rs +++ b/src/command_attr.rs @@ -9,22 +9,31 @@ use crate::{ use proc_macro2::Span; use syn::Attribute; -/// Attributes for `BotCommands` derive macro. +/// All attributes that can be used for `derive(BotCommands)` pub(crate) struct CommandAttrs { - pub prefix: Option, - pub description: Option, - pub rename_rule: Option, - pub parser: Option, - pub separator: Option, + pub prefix: Option<(String, Span)>, + pub description: Option<(String, Span)>, + pub rename_rule: Option<(RenameRule, Span)>, + pub parser: Option<(ParserType, Span)>, + pub separator: Option<(String, Span)>, } -/// An attribute for `BotCommands` derive macro. -pub(crate) struct CommandAttr { +/// A single k/v attribute for `BotCommands` derive macro. +/// +/// For example: +/// ```text +/// #[command(prefix = "!", rename_rule = "snake_case")] +/// /^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^---- CommandAttr { kind: RenameRule(SnakeCase) } +/// | +/// CommandAttr { kind: Prefix("!") } +/// ``` +struct CommandAttr { kind: CommandAttrKind, sp: Span, } -pub(crate) enum CommandAttrKind { +/// Kind of [`CommandAttr`]. +enum CommandAttrKind { Prefix(String), Description(String), RenameRule(RenameRule), @@ -49,13 +58,13 @@ impl CommandAttrs { }, |mut this, attr| { fn insert( - opt: &mut Option, + opt: &mut Option<(T, Span)>, x: T, sp: Span, ) -> Result<()> { match opt { slot @ None => { - *slot = Some(x); + *slot = Some((x, sp)); Ok(()) } Some(_) => { diff --git a/src/command_enum.rs b/src/command_enum.rs index ad3959a0..6720c88f 100644 --- a/src/command_enum.rs +++ b/src/command_enum.rs @@ -5,14 +5,15 @@ use crate::{ #[derive(Debug)] pub(crate) struct CommandEnum { - pub prefix: Option, + pub prefix: String, pub description: Option, pub rename_rule: RenameRule, pub parser_type: ParserType, } impl CommandEnum { - pub fn try_from(attrs: CommandAttrs) -> Result { + pub fn from_attributes(attributes: &[syn::Attribute]) -> Result { + let attrs = CommandAttrs::from_attributes(attributes)?; let CommandAttrs { prefix, description, @@ -20,18 +21,22 @@ impl CommandEnum { parser, separator, } = attrs; - let mut parser = parser.unwrap_or(ParserType::Default); + + let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default); // FIXME: Error on unused separator - if let (ParserType::Split { separator }, Some(s)) = + if let (ParserType::Split { separator }, Some((s, _))) = (&mut parser, &separator) { *separator = Some(s.clone()) } + Ok(Self { - prefix, - description, - rename_rule: rename_rule.unwrap_or(RenameRule::Identity), + prefix: prefix.map(|(p, _)| p).unwrap_or_else(|| "/".to_owned()), + description: description.map(|(d, _)| d), + rename_rule: rename_rule + .map(|(rr, _)| rr) + .unwrap_or(RenameRule::Identity), parser_type: parser, }) } diff --git a/src/fields_parse.rs b/src/fields_parse.rs index a9a2d0b2..6dc2baeb 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -1,7 +1,7 @@ use quote::quote; use syn::{Fields, FieldsNamed, FieldsUnnamed, Type}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum ParserType { Default, Split { separator: Option }, From f3bb54d670e8bb9d9a9d82eb6fb254a231c47fa7 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 2 Oct 2022 18:13:54 +0400 Subject: [PATCH 3/4] Add `#[command(rename = "blah")]` --- src/command.rs | 18 +++++++++++++----- src/command_attr.rs | 5 +++++ src/command_enum.rs | 12 ++++++++++-- tests/command.rs | 16 +++++++--------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/command.rs b/src/command.rs index 1aa8ca21..72b05f3e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,6 +1,6 @@ use crate::{ command_attr::CommandAttrs, command_enum::CommandEnum, - fields_parse::ParserType, Result, + error::compile_error_at, fields_parse::ParserType, Result, }; pub(crate) struct Command { @@ -25,15 +25,23 @@ impl Command { prefix, description, rename_rule, + rename, parser, // FIXME: error on/do not ignore separator separator: _, } = attrs; - let name = rename_rule - .map(|(rr, _)| rr) - .unwrap_or(global_options.rename_rule) - .apply(name); + let name = match (rename, rename_rule) { + (Some((rename, _)), None) => rename, + (Some(_), Some((_, sp))) => { + return Err(compile_error_at( + "`rename_rule` can't be applied to `rename`-d variant", + sp, + )) + } + (None, Some((rule, _))) => rule.apply(name), + (None, None) => global_options.rename_rule.apply(name), + }; let prefix = prefix .map(|(p, _)| p) diff --git a/src/command_attr.rs b/src/command_attr.rs index c8937891..3aeb8e63 100644 --- a/src/command_attr.rs +++ b/src/command_attr.rs @@ -14,6 +14,7 @@ pub(crate) struct CommandAttrs { pub prefix: Option<(String, Span)>, pub description: Option<(String, Span)>, pub rename_rule: Option<(RenameRule, Span)>, + pub rename: Option<(String, Span)>, pub parser: Option<(ParserType, Span)>, pub separator: Option<(String, Span)>, } @@ -37,6 +38,7 @@ enum CommandAttrKind { Prefix(String), Description(String), RenameRule(RenameRule), + Rename(String), ParseWith(ParserType), Separator(String), } @@ -53,6 +55,7 @@ impl CommandAttrs { prefix: None, description: None, rename_rule: None, + rename: None, parser: None, separator: None, }, @@ -77,6 +80,7 @@ impl CommandAttrs { Prefix(p) => insert(&mut this.prefix, p, attr.sp), Description(d) => insert(&mut this.description, d, attr.sp), RenameRule(r) => insert(&mut this.rename_rule, r, attr.sp), + 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), }?; @@ -101,6 +105,7 @@ impl CommandAttr { .expect_string() .and_then(|r| self::RenameRule::parse(&r))?, ), + "rename" => Rename(value.expect_string()?), "parse_with" => { ParseWith(value.expect_string().map(|p| ParserType::parse(&p))?) } diff --git a/src/command_enum.rs b/src/command_enum.rs index 6720c88f..2a882e17 100644 --- a/src/command_enum.rs +++ b/src/command_enum.rs @@ -1,6 +1,6 @@ use crate::{ - command_attr::CommandAttrs, fields_parse::ParserType, - rename_rules::RenameRule, Result, + command_attr::CommandAttrs, error::compile_error_at, + fields_parse::ParserType, rename_rules::RenameRule, Result, }; #[derive(Debug)] @@ -18,10 +18,18 @@ impl CommandEnum { prefix, description, rename_rule, + rename, parser, separator, } = attrs; + if let Some((_rename, sp)) = rename { + return Err(compile_error_at( + "`rename` attribute can only be applied to enums *variants*", + sp, + )); + } + let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default); // FIXME: Error on unused separator diff --git a/tests/command.rs b/tests/command.rs index 6300c063..f7297c8d 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -249,8 +249,8 @@ fn rename_rules() { GggGgg, #[command(rename_rule = "SCREAMING-KEBAB-CASE")] HhhHhh, - //#[command(rename = "Bar")] - //Foo, + #[command(rename = "Bar")] + Foo, } assert_eq!( @@ -285,16 +285,14 @@ fn rename_rules() { DefaultCommands::HhhHhh, DefaultCommands::parse("/HHH-HHH", "").unwrap() ); - //assert_eq!(DefaultCommands::Foo, DefaultCommands::parse("/Bar", - // "").unwrap()); + assert_eq!( + DefaultCommands::Foo, + DefaultCommands::parse("/Bar", "").unwrap() + ); - // assert_eq!( - // "/aaaaaa\n/BBBBBB\n/CccCcc\n/dddDdd\n/eee_eee\n/FFF_FFF\n/ggg-ggg\n/ - // HHH-HHH\n/Bar", DefaultCommands::descriptions().to_string() - // ); assert_eq!( "/aaaaaa\n/BBBBBB\n/CccCcc\n/dddDdd\n/eee_eee\n/FFF_FFF\n/ggg-ggg\n/\ - HHH-HHH", + HHH-HHH\n/Bar", DefaultCommands::descriptions().to_string() ); } From 7beb705b4ae7a88867b7d8a760c4f5ab8df6aafe Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 3 Oct 2022 20:54:05 +0400 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0916ad5..9c997856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- `#[command(rename = "a_name_that_is_not_a_case_name")]` doesn't work anymore +- `#[command(rename = "...")]` now always renames to `"..."`, to rename multiple commands using the same pattern, use `#[command(rename_rule = "snake_case")]` and the like. ## 0.6.3 - 2022-07-19