Cleanup renaming

(this technically breaks `#[rename = "actual_name"]` but we wanted to
split `rename` VS `rename_rule` anyway, so this is ok)
This commit is contained in:
Maybe Waffle 2022-08-22 17:36:36 +04:00
parent 88449531a6
commit ddacee966e
4 changed files with 131 additions and 101 deletions

View file

@ -2,7 +2,7 @@ use crate::{
attr::{Attr, BotCommandAttribute}, attr::{Attr, BotCommandAttribute},
command_enum::CommandEnum, command_enum::CommandEnum,
fields_parse::ParserType, fields_parse::ParserType,
rename_rules::rename_by_rule, rename_rules::RenameRule,
Result, Result,
}; };
@ -11,24 +11,22 @@ pub(crate) struct Command {
pub description: Option<String>, pub description: Option<String>,
pub parser: Option<ParserType>, pub parser: Option<ParserType>,
pub name: String, pub name: String,
pub renamed: bool,
} }
impl Command { impl Command {
pub fn try_from(attrs: &[Attr], name: &str) -> Result<Self> { pub fn try_from(attrs: &[Attr], name: &str) -> Result<Self> {
let attrs = parse_attrs(attrs)?; let attrs = parse_attrs(attrs)?;
let mut new_name = name.to_string(); let CommandAttrs {
let mut renamed = false; prefix,
description,
rename_rule,
parser,
separator: _,
} = attrs;
let prefix = attrs.prefix; let name = rename_rule.apply(name);
let description = attrs.description;
let rename = attrs.rename; Ok(Self { prefix, description, parser, name })
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 })
} }
pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String { pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String {
@ -39,11 +37,8 @@ impl Command {
} else { } else {
"/" "/"
}; };
if let Some(rule) = &global_parameters.rename_rule {
String::from(prefix) + &rename_by_rule(&self.name, rule.as_str()) String::from(prefix) + &global_parameters.rename_rule.apply(&self.name)
} else {
String::from(prefix) + &self.name
}
} }
pub fn get_matched_value2( pub fn get_matched_value2(
@ -57,18 +52,15 @@ impl Command {
} else { } else {
"/" "/"
}; };
if let Some(rule) = &global_parameters.rename_rule {
(String::from(prefix), rename_by_rule(&self.name, rule.as_str())) (String::from(prefix), global_parameters.rename_rule.apply(&self.name))
} else {
(String::from(prefix), self.name.clone())
}
} }
} }
pub(crate) struct CommandAttrs { pub(crate) struct CommandAttrs {
pub prefix: Option<String>, pub prefix: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub rename: Option<String>, pub rename_rule: RenameRule,
pub parser: Option<ParserType>, pub parser: Option<ParserType>,
pub separator: Option<String>, pub separator: Option<String>,
} }
@ -76,7 +68,7 @@ pub(crate) struct CommandAttrs {
pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs> { pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs> {
let mut prefix = None; let mut prefix = None;
let mut description = None; let mut description = None;
let mut rename_rule = None; let mut rename_rule = RenameRule::Identity;
let mut parser = None; let mut parser = None;
let mut separator = None; let mut separator = None;
@ -86,7 +78,9 @@ pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs> {
BotCommandAttribute::Description => { BotCommandAttribute::Description => {
description = Some(attr.value()) description = Some(attr.value())
} }
BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), BotCommandAttribute::RenameRule => {
rename_rule = RenameRule::parse(&attr.value())?
}
BotCommandAttribute::CustomParser => { BotCommandAttribute::CustomParser => {
parser = Some(ParserType::parse(&attr.value())) parser = Some(ParserType::parse(&attr.value()))
} }
@ -94,11 +88,5 @@ pub(crate) fn parse_attrs(attrs: &[Attr]) -> Result<CommandAttrs> {
} }
} }
Ok(CommandAttrs { Ok(CommandAttrs { prefix, description, rename_rule, parser, separator })
prefix,
description,
rename: rename_rule,
parser,
separator,
})
} }

View file

@ -1,13 +1,13 @@
use crate::{ use crate::{
attr::Attr, command::parse_attrs, error::compile_error, attr::Attr, command::parse_attrs, fields_parse::ParserType,
fields_parse::ParserType, Result, rename_rules::RenameRule, Result,
}; };
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct CommandEnum { pub(crate) struct CommandEnum {
pub prefix: Option<String>, pub prefix: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub rename_rule: Option<String>, pub rename_rule: RenameRule,
pub parser_type: ParserType, pub parser_type: ParserType,
} }
@ -17,7 +17,7 @@ impl CommandEnum {
let prefix = attrs.prefix; let prefix = attrs.prefix;
let description = attrs.description; let description = attrs.description;
let rename = attrs.rename; let rename = attrs.rename_rule;
let separator = attrs.separator; let separator = attrs.separator;
let mut parser = attrs.parser.unwrap_or(ParserType::Default); let mut parser = attrs.parser.unwrap_or(ParserType::Default);
if let (ParserType::Split { separator }, Some(s)) = if let (ParserType::Split { separator }, Some(s)) =
@ -25,19 +25,6 @@ impl CommandEnum {
{ {
*separator = Some(s.clone()) *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 { Ok(Self {
prefix, prefix,
description, description,

View file

@ -3,6 +3,7 @@ use quote::{quote, ToTokens};
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>; pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub(crate) struct Error(TokenStream); pub(crate) struct Error(TokenStream);
pub(crate) fn compile_error<T>(data: T) -> Error pub(crate) fn compile_error<T>(data: T) -> Error

View file

@ -5,23 +5,75 @@ use heck::{
ToShoutySnakeCase, ToSnakeCase, ToShoutySnakeCase, ToSnakeCase,
}; };
/// Apply a renaming rule to an enum variant, use crate::error::{compile_error, Result};
/// returning the version expected in the source.
/// #[derive(Copy, Clone, Debug)]
/// The possible `rule` can be: `lowercase`, `UPPERCASE`, `PascalCase`, pub(crate) enum RenameRule {
/// `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, /// -> `lowercase`
/// `SCREAMING-KEBAB-CASE`. See tests for the details how it will work. LowerCase,
pub(crate) fn rename_by_rule(input: &str, rule: &str) -> String { /// -> `UPPERCASE`
match rule { UpperCase,
"lowercase" => input.to_lowercase(), /// -> `PascalCase`
"UPPERCASE" => input.to_uppercase(), PascalCase,
"PascalCase" => input.to_pascal_case(), /// -> `camelCase`
"camelCase" => input.to_lower_camel_case(), CamelCase,
"snake_case" => input.to_snake_case(), /// -> `snake_case`
"SCREAMING_SNAKE_CASE" => input.to_shouty_snake_case(), SnakeCase,
"kebab-case" => input.to_kebab_case(), /// -> `SCREAMING_SNAKE_CASE`
"SCREAMING-KEBAB-CASE" => input.to_shouty_kebab_case(), ScreamingSnakeCase,
_ => rule.to_string(), /// -> `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<Self> {
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::*; use super::*;
macro_rules! test_eq { macro_rules! test_eq {
($lval:expr, $rval:expr) => { ($lval:expr => $rval:expr) => {
assert_eq!(rename_by_rule($lval, TYPE), $rval); let rule = RenameRule::parse(TYPE).unwrap();
assert_eq!(rule.apply($lval), $rval);
}; };
} }
@ -39,79 +93,79 @@ mod tests {
fn test_lowercase() { fn test_lowercase() {
const TYPE: &str = "lowercase"; const TYPE: &str = "lowercase";
test_eq!("HelloWorld", "helloworld"); test_eq!("HelloWorld" => "helloworld");
test_eq!("Hello_World", "hello_world"); test_eq!("Hello_World" => "hello_world");
test_eq!("Hello-World", "hello-world"); test_eq!("Hello-World" => "hello-world");
test_eq!("helloWorld", "helloworld"); test_eq!("helloWorld" => "helloworld");
} }
#[test] #[test]
fn test_uppercase() { fn test_uppercase() {
const TYPE: &str = "UPPERCASE"; const TYPE: &str = "UPPERCASE";
test_eq!("HelloWorld", "HELLOWORLD"); test_eq!("HelloWorld" => "HELLOWORLD");
test_eq!("Hello_World", "HELLO_WORLD"); test_eq!("Hello_World" => "HELLO_WORLD");
test_eq!("Hello-World", "HELLO-WORLD"); test_eq!("Hello-World" => "HELLO-WORLD");
test_eq!("helloWorld", "HELLOWORLD"); test_eq!("helloWorld" => "HELLOWORLD");
} }
#[test] #[test]
fn test_pascalcase() { fn test_pascalcase() {
const TYPE: &str = "PascalCase"; const TYPE: &str = "PascalCase";
test_eq!("HelloWorld", "HelloWorld"); test_eq!("HelloWorld" => "HelloWorld");
test_eq!("Hello_World", "HelloWorld"); test_eq!("Hello_World" => "HelloWorld");
test_eq!("Hello-World", "HelloWorld"); test_eq!("Hello-World" => "HelloWorld");
test_eq!("helloWorld", "HelloWorld"); test_eq!("helloWorld" => "HelloWorld");
} }
#[test] #[test]
fn test_camelcase() { fn test_camelcase() {
const TYPE: &str = "camelCase"; const TYPE: &str = "camelCase";
test_eq!("HelloWorld", "helloWorld"); test_eq!("HelloWorld" => "helloWorld");
test_eq!("Hello_World", "helloWorld"); test_eq!("Hello_World" => "helloWorld");
test_eq!("Hello-World", "helloWorld"); test_eq!("Hello-World" => "helloWorld");
test_eq!("helloWorld", "helloWorld"); test_eq!("helloWorld" => "helloWorld");
} }
#[test] #[test]
fn test_snakecase() { fn test_snakecase() {
const TYPE: &str = "snake_case"; const TYPE: &str = "snake_case";
test_eq!("HelloWorld", "hello_world"); test_eq!("HelloWorld" => "hello_world");
test_eq!("Hello_World", "hello_world"); test_eq!("Hello_World" => "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] #[test]
fn test_screaming_snakecase() { fn test_screaming_snakecase() {
const TYPE: &str = "SCREAMING_SNAKE_CASE"; const TYPE: &str = "SCREAMING_SNAKE_CASE";
test_eq!("HelloWorld", "HELLO_WORLD"); test_eq!("HelloWorld" => "HELLO_WORLD");
test_eq!("Hello_World", "HELLO_WORLD"); test_eq!("Hello_World" => "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] #[test]
fn test_kebabcase() { fn test_kebabcase() {
const TYPE: &str = "kebab-case"; const TYPE: &str = "kebab-case";
test_eq!("HelloWorld", "hello-world"); test_eq!("HelloWorld" => "hello-world");
test_eq!("Hello_World", "hello-world"); test_eq!("Hello_World" => "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] #[test]
fn test_screaming_kebabcase() { fn test_screaming_kebabcase() {
const TYPE: &str = "SCREAMING-KEBAB-CASE"; const TYPE: &str = "SCREAMING-KEBAB-CASE";
test_eq!("HelloWorld", "HELLO-WORLD"); test_eq!("HelloWorld" => "HELLO-WORLD");
test_eq!("Hello_World", "HELLO-WORLD"); test_eq!("Hello_World" => "HELLO-WORLD");
test_eq!("Hello-World", "HELLO-WORLD"); test_eq!("Hello-World" => "HELLO-WORLD");
test_eq!("helloWorld", "HELLO-WORLD"); test_eq!("helloWorld" => "HELLO-WORLD");
} }
} }