From ddacee966e6c86160d37e7d9c97c4c9bc545d606 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 22 Aug 2022 17:36:36 +0400 Subject: [PATCH] Cleanup renaming (this technically breaks `#[rename = "actual_name"]` but we wanted to split `rename` VS `rename_rule` anyway, so this is ok) --- src/command.rs | 54 ++++++--------- src/command_enum.rs | 21 ++---- src/error.rs | 1 + src/rename_rules.rs | 156 +++++++++++++++++++++++++++++--------------- 4 files changed, 131 insertions(+), 101 deletions(-) diff --git a/src/command.rs b/src/command.rs index 023e765e..090c9c46 100644 --- a/src/command.rs +++ b/src/command.rs @@ -2,7 +2,7 @@ use crate::{ attr::{Attr, BotCommandAttribute}, command_enum::CommandEnum, fields_parse::ParserType, - rename_rules::rename_by_rule, + rename_rules::RenameRule, Result, }; @@ -11,24 +11,22 @@ pub(crate) struct Command { pub description: Option, pub parser: Option, pub name: String, - pub renamed: bool, } impl Command { pub fn try_from(attrs: &[Attr], name: &str) -> Result { let attrs = parse_attrs(attrs)?; - let mut new_name = name.to_string(); - let mut renamed = false; + let CommandAttrs { + prefix, + description, + rename_rule, + parser, + separator: _, + } = attrs; - let prefix = attrs.prefix; - let description = attrs.description; - let rename = attrs.rename; - let parser = attrs.parser; - if let Some(rename_rule) = rename { - new_name = rename_by_rule(name, &rename_rule); - renamed = true; - } - Ok(Self { prefix, description, parser, name: new_name, renamed }) + let name = rename_rule.apply(name); + + Ok(Self { prefix, description, parser, name }) } pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String { @@ -39,11 +37,8 @@ impl Command { } else { "/" }; - if let Some(rule) = &global_parameters.rename_rule { - String::from(prefix) + &rename_by_rule(&self.name, rule.as_str()) - } else { - String::from(prefix) + &self.name - } + + String::from(prefix) + &global_parameters.rename_rule.apply(&self.name) } pub fn get_matched_value2( @@ -57,18 +52,15 @@ impl Command { } else { "/" }; - if let Some(rule) = &global_parameters.rename_rule { - (String::from(prefix), rename_by_rule(&self.name, rule.as_str())) - } else { - (String::from(prefix), self.name.clone()) - } + + (String::from(prefix), global_parameters.rename_rule.apply(&self.name)) } } pub(crate) struct CommandAttrs { pub prefix: Option, pub description: Option, - pub rename: Option, + pub rename_rule: RenameRule, pub parser: Option, pub separator: Option, } @@ -76,7 +68,7 @@ pub(crate) struct CommandAttrs { pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result { let mut prefix = None; let mut description = None; - let mut rename_rule = None; + let mut rename_rule = RenameRule::Identity; let mut parser = None; let mut separator = None; @@ -86,7 +78,9 @@ pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result { BotCommandAttribute::Description => { description = Some(attr.value()) } - BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), + BotCommandAttribute::RenameRule => { + rename_rule = RenameRule::parse(&attr.value())? + } BotCommandAttribute::CustomParser => { parser = Some(ParserType::parse(&attr.value())) } @@ -94,11 +88,5 @@ pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result { } } - Ok(CommandAttrs { - prefix, - description, - rename: rename_rule, - parser, - separator, - }) + Ok(CommandAttrs { prefix, description, rename_rule, parser, separator }) } diff --git a/src/command_enum.rs b/src/command_enum.rs index 46b12a1e..271a4ab5 100644 --- a/src/command_enum.rs +++ b/src/command_enum.rs @@ -1,13 +1,13 @@ use crate::{ - attr::Attr, command::parse_attrs, error::compile_error, - fields_parse::ParserType, Result, + attr::Attr, command::parse_attrs, fields_parse::ParserType, + rename_rules::RenameRule, Result, }; #[derive(Debug)] pub(crate) struct CommandEnum { pub prefix: Option, pub description: Option, - pub rename_rule: Option, + pub rename_rule: RenameRule, pub parser_type: ParserType, } @@ -17,7 +17,7 @@ impl CommandEnum { let prefix = attrs.prefix; let description = attrs.description; - let rename = attrs.rename; + let rename = attrs.rename_rule; let separator = attrs.separator; let mut parser = attrs.parser.unwrap_or(ParserType::Default); if let (ParserType::Split { separator }, Some(s)) = @@ -25,19 +25,6 @@ impl CommandEnum { { *separator = Some(s.clone()) } - if let Some(rename_rule) = &rename { - match rename_rule.as_str() { - "lowercase" - | "UPPERCASE" - | "PascalCase" - | "camelCase" - | "snake_case" - | "SCREAMING_SNAKE_CASE" - | "kebab-case" - | "SCREAMING-KEBAB-CASE" => {} - _ => return Err(compile_error("disallowed value")), - } - } Ok(Self { prefix, description, diff --git a/src/error.rs b/src/error.rs index 950a85d9..60796612 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use quote::{quote, ToTokens}; pub(crate) type Result = std::result::Result; +#[derive(Debug)] pub(crate) struct Error(TokenStream); pub(crate) fn compile_error(data: T) -> Error diff --git a/src/rename_rules.rs b/src/rename_rules.rs index 085dd3c9..7069a011 100644 --- a/src/rename_rules.rs +++ b/src/rename_rules.rs @@ -5,23 +5,75 @@ use heck::{ ToShoutySnakeCase, ToSnakeCase, }; -/// Apply a renaming rule to an enum variant, -/// returning the version expected in the source. -/// -/// The possible `rule` can be: `lowercase`, `UPPERCASE`, `PascalCase`, -/// `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, -/// `SCREAMING-KEBAB-CASE`. See tests for the details how it will work. -pub(crate) fn rename_by_rule(input: &str, rule: &str) -> String { - match rule { - "lowercase" => input.to_lowercase(), - "UPPERCASE" => input.to_uppercase(), - "PascalCase" => input.to_pascal_case(), - "camelCase" => input.to_lower_camel_case(), - "snake_case" => input.to_snake_case(), - "SCREAMING_SNAKE_CASE" => input.to_shouty_snake_case(), - "kebab-case" => input.to_kebab_case(), - "SCREAMING-KEBAB-CASE" => input.to_shouty_kebab_case(), - _ => rule.to_string(), +use crate::error::{compile_error, Result}; + +#[derive(Copy, Clone, Debug)] +pub(crate) enum RenameRule { + /// -> `lowercase` + LowerCase, + /// -> `UPPERCASE` + UpperCase, + /// -> `PascalCase` + PascalCase, + /// -> `camelCase` + CamelCase, + /// -> `snake_case` + SnakeCase, + /// -> `SCREAMING_SNAKE_CASE` + ScreamingSnakeCase, + /// -> `kebab-case` + KebabCase, + /// -> `SCREAMING-KEBAB-CASE` + ScreamingKebabCase, + /// Leaves input as-is + Identity, +} + +impl RenameRule { + /// Apply a renaming rule to a string, returning the version expected in the + /// source. + /// + /// See tests for the details how it will work. + pub fn apply(self, input: &str) -> String { + use RenameRule::*; + + match self { + LowerCase => input.to_lowercase(), + UpperCase => input.to_uppercase(), + PascalCase => input.to_pascal_case(), + CamelCase => input.to_lower_camel_case(), + SnakeCase => input.to_snake_case(), + ScreamingSnakeCase => input.to_shouty_snake_case(), + KebabCase => input.to_kebab_case(), + ScreamingKebabCase => input.to_shouty_kebab_case(), + Identity => input.to_owned(), + } + } + + pub fn parse(rule: &str) -> Result { + use RenameRule::*; + + let rule = match rule { + "lowercase" => LowerCase, + "UPPERCASE" => UpperCase, + "PascalCase" => PascalCase, + "camelCase" => CamelCase, + "snake_case" => SnakeCase, + "SCREAMING_SNAKE_CASE" => ScreamingSnakeCase, + "kebab-case" => KebabCase, + "SCREAMING-KEBAB-CASE" => ScreamingKebabCase, + "identity" => Identity, + invalid => { + return Err(compile_error(format!( + "invalid rename rule `{invalid}` (supported rules: \ + `lowercase`, `UPPERCASE`, `PascalCase`, `camelCase`, \ + `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, \ + `SCREAMING-KEBAB-CASE` and `identity`)" + ))) + } + }; + + Ok(rule) } } @@ -30,8 +82,10 @@ mod tests { use super::*; macro_rules! test_eq { - ($lval:expr, $rval:expr) => { - assert_eq!(rename_by_rule($lval, TYPE), $rval); + ($lval:expr => $rval:expr) => { + let rule = RenameRule::parse(TYPE).unwrap(); + + assert_eq!(rule.apply($lval), $rval); }; } @@ -39,79 +93,79 @@ mod tests { fn test_lowercase() { const TYPE: &str = "lowercase"; - test_eq!("HelloWorld", "helloworld"); - test_eq!("Hello_World", "hello_world"); - test_eq!("Hello-World", "hello-world"); - test_eq!("helloWorld", "helloworld"); + test_eq!("HelloWorld" => "helloworld"); + test_eq!("Hello_World" => "hello_world"); + test_eq!("Hello-World" => "hello-world"); + test_eq!("helloWorld" => "helloworld"); } #[test] fn test_uppercase() { const TYPE: &str = "UPPERCASE"; - test_eq!("HelloWorld", "HELLOWORLD"); - test_eq!("Hello_World", "HELLO_WORLD"); - test_eq!("Hello-World", "HELLO-WORLD"); - test_eq!("helloWorld", "HELLOWORLD"); + test_eq!("HelloWorld" => "HELLOWORLD"); + test_eq!("Hello_World" => "HELLO_WORLD"); + test_eq!("Hello-World" => "HELLO-WORLD"); + test_eq!("helloWorld" => "HELLOWORLD"); } #[test] fn test_pascalcase() { const TYPE: &str = "PascalCase"; - test_eq!("HelloWorld", "HelloWorld"); - test_eq!("Hello_World", "HelloWorld"); - test_eq!("Hello-World", "HelloWorld"); - test_eq!("helloWorld", "HelloWorld"); + test_eq!("HelloWorld" => "HelloWorld"); + test_eq!("Hello_World" => "HelloWorld"); + test_eq!("Hello-World" => "HelloWorld"); + test_eq!("helloWorld" => "HelloWorld"); } #[test] fn test_camelcase() { const TYPE: &str = "camelCase"; - test_eq!("HelloWorld", "helloWorld"); - test_eq!("Hello_World", "helloWorld"); - test_eq!("Hello-World", "helloWorld"); - test_eq!("helloWorld", "helloWorld"); + test_eq!("HelloWorld" => "helloWorld"); + test_eq!("Hello_World" => "helloWorld"); + test_eq!("Hello-World" => "helloWorld"); + test_eq!("helloWorld" => "helloWorld"); } #[test] fn test_snakecase() { const TYPE: &str = "snake_case"; - test_eq!("HelloWorld", "hello_world"); - test_eq!("Hello_World", "hello_world"); - test_eq!("Hello-World", "hello_world"); - test_eq!("helloWorld", "hello_world"); + test_eq!("HelloWorld" => "hello_world"); + test_eq!("Hello_World" => "hello_world"); + test_eq!("Hello-World" => "hello_world"); + test_eq!("helloWorld" => "hello_world"); } #[test] fn test_screaming_snakecase() { const TYPE: &str = "SCREAMING_SNAKE_CASE"; - test_eq!("HelloWorld", "HELLO_WORLD"); - test_eq!("Hello_World", "HELLO_WORLD"); - test_eq!("Hello-World", "HELLO_WORLD"); - test_eq!("helloWorld", "HELLO_WORLD"); + test_eq!("HelloWorld" => "HELLO_WORLD"); + test_eq!("Hello_World" => "HELLO_WORLD"); + test_eq!("Hello-World" => "HELLO_WORLD"); + test_eq!("helloWorld" => "HELLO_WORLD"); } #[test] fn test_kebabcase() { const TYPE: &str = "kebab-case"; - test_eq!("HelloWorld", "hello-world"); - test_eq!("Hello_World", "hello-world"); - test_eq!("Hello-World", "hello-world"); - test_eq!("helloWorld", "hello-world"); + test_eq!("HelloWorld" => "hello-world"); + test_eq!("Hello_World" => "hello-world"); + test_eq!("Hello-World" => "hello-world"); + test_eq!("helloWorld" => "hello-world"); } #[test] fn test_screaming_kebabcase() { const TYPE: &str = "SCREAMING-KEBAB-CASE"; - test_eq!("HelloWorld", "HELLO-WORLD"); - test_eq!("Hello_World", "HELLO-WORLD"); - test_eq!("Hello-World", "HELLO-WORLD"); - test_eq!("helloWorld", "HELLO-WORLD"); + test_eq!("HelloWorld" => "HELLO-WORLD"); + test_eq!("Hello_World" => "HELLO-WORLD"); + test_eq!("Hello-World" => "HELLO-WORLD"); + test_eq!("helloWorld" => "HELLO-WORLD"); } }