From ca17ae75810363884aca55d96414b7f2a7365ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Glotfelty=20=F0=9F=9A=80?= Date: Sat, 17 Aug 2019 09:40:39 -0700 Subject: [PATCH] Did additional refactoring and added helpful trait methods to improve readability --- strum_macros/src/helpers/case_style.rs | 46 +++++++++++- .../src/helpers/meta_iterator_helpers.rs | 71 +++++++------------ strum_macros/src/helpers/mod.rs | 64 +---------------- strum_macros/src/macros/enum_discriminants.rs | 30 ++++---- strum_macros/src/macros/enum_messages.rs | 6 +- strum_macros/src/macros/enum_variant_names.rs | 6 +- strum_macros/src/macros/mod.rs | 12 ++-- .../src/macros/{ => strings}/as_ref_str.rs | 10 +-- .../src/macros/{ => strings}/display.rs | 10 +-- .../src/macros/{ => strings}/from_string.rs | 17 +++-- strum_macros/src/macros/strings/mod.rs | 4 ++ .../src/macros/{ => strings}/to_string.rs | 10 +-- 12 files changed, 131 insertions(+), 155 deletions(-) rename strum_macros/src/macros/{ => strings}/as_ref_str.rs (91%) rename strum_macros/src/macros/{ => strings}/display.rs (83%) rename strum_macros/src/macros/{ => strings}/from_string.rs (81%) create mode 100644 strum_macros/src/macros/strings/mod.rs rename strum_macros/src/macros/{ => strings}/to_string.rs (82%) diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs index afdb2fa..7a7f79d 100644 --- a/strum_macros/src/helpers/case_style.rs +++ b/strum_macros/src/helpers/case_style.rs @@ -1,4 +1,6 @@ -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CaseStyle { CamelCase, KebabCase, @@ -43,3 +45,45 @@ impl<'s> From<&'s str> for CaseStyle { } } } + +pub trait CaseStyleHelpers { + fn convert_case(&self, case_style: Option) -> String; +} + +impl CaseStyleHelpers for syn::Ident { + fn convert_case(&self, case_style: Option) -> String { + let ident_string = self.to_string(); + if let Some(case_style) = case_style { + match case_style { + CaseStyle::PascalCase => ident_string.to_camel_case(), + CaseStyle::KebabCase => ident_string.to_kebab_case(), + CaseStyle::MixedCase => ident_string.to_mixed_case(), + CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(), + CaseStyle::SnakeCase => ident_string.to_snake_case(), + CaseStyle::TitleCase => ident_string.to_title_case(), + CaseStyle::UpperCase => ident_string.to_uppercase(), + CaseStyle::LowerCase => ident_string.to_lowercase(), + CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(), + CaseStyle::CamelCase => { + let camel_case = ident_string.to_camel_case(); + let mut pascal = String::with_capacity(camel_case.len()); + let mut it = camel_case.chars(); + if let Some(ch) = it.next() { + pascal.extend(ch.to_lowercase()); + } + pascal.extend(it); + pascal + } + } + } else { + ident_string + } + } +} + +#[test] +fn test_convert_case() { + let id = syn::Ident::new("test_me", proc_macro2::Span::call_site()); + assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase))); + assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase))); +} \ No newline at end of file diff --git a/strum_macros/src/helpers/meta_iterator_helpers.rs b/strum_macros/src/helpers/meta_iterator_helpers.rs index a17be1f..f16f1b4 100644 --- a/strum_macros/src/helpers/meta_iterator_helpers.rs +++ b/strum_macros/src/helpers/meta_iterator_helpers.rs @@ -1,18 +1,15 @@ -use syn::{Meta, MetaList}; - +use syn::Meta; use super::MetaHelpers; use super::MetaListHelpers; pub trait MetaIteratorHelpers { - type Item; - fn unique_meta_list(self, attr: &str) -> Option; - fn extract_attrs(self, attr: &str, prop: &str) -> Vec; + fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta>; + fn find_properties(&self, attr: &str, prop: &str) -> Vec; - fn unique_attr(self, attr: &str, prop: &str) -> Option - where Self: Sized + fn find_unique_property(&self, attr: &str, prop: &str) -> Option { - let mut curr = self.extract_attrs(attr, prop); + let mut curr = self.find_properties(attr, prop); if curr.len() > 1 { panic!("More than one property: {} found on variant", prop); } @@ -20,10 +17,9 @@ pub trait MetaIteratorHelpers { curr.pop() } - fn is_disabled(self) -> bool - where Self: Sized + fn is_disabled(&self) -> bool { - let v = self.extract_attrs("strum", "disabled"); + let v = self.find_properties("strum", "disabled"); match v.len() { 0 => false, 1 => v[0] == "true", @@ -32,44 +28,29 @@ pub trait MetaIteratorHelpers { } } -impl <'a, I> MetaIteratorHelpers for I +//impl MetaIteratorHelpers for [Meta] +impl MetaIteratorHelpers for [T] where - I: std::iter::IntoIterator + T: std::borrow::Borrow { - type Item=&'a MetaList; - - /// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`. - /// - /// # Panics - /// - /// Panics if more than one `Meta` exists with the name. - fn unique_meta_list(self, attr: &str) -> Option<&'a MetaList> - where - Self: Sized - { - // let mut curr = get_meta_list(metas.into_iter(), attr).collect::>(); - let mut curr = self.into_iter() - .filter_map(|meta| meta.try_metalist()) - .filter(|list| list.path.is_ident(attr)) - .collect::>(); - - if curr.len() > 1 { - panic!("More than one `{}` attribute found on type", attr); - } - - curr.pop() - } - - fn extract_attrs(self, attr: &str, prop: &str) -> Vec - where - Self: Sized - { - use syn::{Lit, MetaNameValue}; - self.into_iter() - .filter_map(|meta| meta.try_metalist()) + fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta> { + self.iter() + .filter_map(|meta| meta.borrow().try_metalist()) .filter(|list| list.path.is_ident(attr)) .flat_map(|list| list.expand_inner()) - // Get all the inner elements as long as they start with ser. + .collect::>() + .into_iter() + } + + fn find_properties(&self, attr: &str, prop: &str) -> Vec + { + use syn::{Lit, MetaNameValue}; + self.iter() + // Only look at MetaList style attributes `[strum(...)]` + .filter_map(|meta| meta.borrow().try_metalist()) + .filter(|list| list.path.is_ident(attr)) + .flat_map(|list| list.expand_inner()) + // Match all the properties with a given ident `[strum(serialize = "value")]` .filter_map(|meta| match *meta { Meta::NameValue(MetaNameValue { ref path, diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 4915610..a7e5978 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -1,6 +1,5 @@ -use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase}; -use syn::{Attribute, Ident, Meta, MetaList}; -use crate::helpers::case_style::CaseStyle; + +use syn::{Attribute, Meta}; pub mod case_style; mod metalist_helpers; @@ -10,6 +9,7 @@ mod meta_iterator_helpers; pub use self::metalist_helpers::MetaListHelpers; pub use self::meta_helpers::MetaHelpers; pub use self::meta_iterator_helpers::MetaIteratorHelpers; +pub use self::case_style::CaseStyleHelpers; pub fn extract_meta(attrs: &[Attribute]) -> Vec { attrs @@ -17,61 +17,3 @@ pub fn extract_meta(attrs: &[Attribute]) -> Vec { .filter_map(|attribute| attribute.parse_meta().ok()) .collect() } - -/// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`. -/// -/// # Panics -/// -/// Panics if more than one `Meta` exists with the name. -pub fn unique_meta_list<'meta, MetaIt>(metas: MetaIt, attr: &'meta str) -> Option<&'meta MetaList> -where - MetaIt: Iterator, -{ - // let mut curr = get_meta_list(metas.into_iter(), attr).collect::>(); - let mut curr = metas - .filter_map(|meta| meta.try_metalist()) - .filter(|list| list.path.is_ident(attr)) - .collect::>(); - - if curr.len() > 1 { - panic!("More than one `{}` attribute found on type", attr); - } - - curr.pop() -} - -pub fn convert_case(ident: &Ident, case_style: Option) -> String { - let ident_string = ident.to_string(); - if let Some(case_style) = case_style { - match case_style { - CaseStyle::PascalCase => ident_string.to_camel_case(), - CaseStyle::KebabCase => ident_string.to_kebab_case(), - CaseStyle::MixedCase => ident_string.to_mixed_case(), - CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(), - CaseStyle::SnakeCase => ident_string.to_snake_case(), - CaseStyle::TitleCase => ident_string.to_title_case(), - CaseStyle::UpperCase => ident_string.to_uppercase(), - CaseStyle::LowerCase => ident_string.to_lowercase(), - CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(), - CaseStyle::CamelCase => { - let camel_case = ident_string.to_camel_case(); - let mut pascal = String::with_capacity(camel_case.len()); - let mut it = camel_case.chars(); - if let Some(ch) = it.next() { - pascal.extend(ch.to_lowercase()); - } - pascal.extend(it); - pascal - } - } - } else { - ident_string - } -} - -#[test] -fn test_convert_case() { - let id = Ident::new("test_me", proc_macro2::Span::call_site()); - assert_eq!("testMe", convert_case(&id, Some(CaseStyle::CamelCase))); - assert_eq!("TestMe", convert_case(&id, Some(CaseStyle::PascalCase))); -} diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs index 5661907..fcae5a2 100644 --- a/strum_macros/src/macros/enum_discriminants.rs +++ b/strum_macros/src/macros/enum_discriminants.rs @@ -1,8 +1,8 @@ -use crate::helpers::{MetaHelpers, MetaListHelpers}; +use crate::helpers::{MetaHelpers, MetaListHelpers, MetaIteratorHelpers}; use proc_macro2::{Span, TokenStream}; use syn; -use helpers::{extract_meta, unique_meta_list}; +use helpers::{extract_meta}; pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -15,19 +15,12 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { // Derives for the generated enum let type_meta = extract_meta(&ast.attrs); - let discriminant_attrs = type_meta.iter() - // We are only looking at 'metalist' style attributes [list(item1,item2,...)] - .filter_map(|meta| meta.try_metalist()) - // Find the list named `strum_discriminants` - .filter(|list| list.path.is_ident("strum_discriminants")) - // If there are multiple attributes, we combine them all together. - .flat_map(|list| list.expand_inner()) + let discriminant_attrs = type_meta + .find_attribute("strum_discriminants") .collect::>(); - let derives = discriminant_attrs.iter() - .filter_map(|meta| meta.try_metalist()) - .filter(|list| list.path.is_ident("derive")) - .flat_map(|list| list.expand_inner()) + let derives = discriminant_attrs + .find_attribute("derive") .map(|meta| meta.path()) .collect::>(); @@ -41,7 +34,16 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { Span::call_site() )); - let discriminants_name = unique_meta_list(discriminant_attrs.iter().map(|&m| m), "name") + let discriminants_name = discriminant_attrs.iter() + .filter_map(|meta| meta.try_metalist()) + .filter(|list| list.path.is_ident("name")) + // We want exactly zero or one items. Start with the assumption we have zero, i.e. None + // Then set our output to the first value we see. If fold is called again and we already + // have a value, panic. + .fold(None, |acc, val| match acc { + Some(_) => panic!("Expecting a single attribute 'name' in EnumDiscriminants."), + None => Some(val), + }) .map(|meta| meta.expand_inner()) .and_then(|metas| metas.into_iter().map(|meta| meta.path()).next()) .unwrap_or(&default_name); diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs index d021053..24ec451 100644 --- a/strum_macros/src/macros/enum_messages.rs +++ b/strum_macros/src/macros/enum_messages.rs @@ -17,8 +17,8 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { for variant in variants { let meta = extract_meta(&variant.attrs); - let messages = meta.unique_attr("strum", "message"); - let detailed_messages = meta.unique_attr("strum", "detailed_message"); + let messages = meta.find_unique_property("strum", "message"); + let detailed_messages = meta.find_unique_property("strum", "detailed_message"); let ident = &variant.ident; use syn::Fields::*; @@ -30,7 +30,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { // You can't disable getting the serializations. { - let mut serialization_variants = meta.extract_attrs("strum", "serialize"); + let mut serialization_variants = meta.find_properties("strum", "serialize"); if serialization_variants.len() == 0 { serialization_variants.push(ident.to_string()); } diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs index f71e5d6..ee2a84b 100644 --- a/strum_macros/src/macros/enum_variant_names.rs +++ b/strum_macros/src/macros/enum_variant_names.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use syn; use crate::helpers::case_style::CaseStyle; -use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers}; +use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers}; pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -14,12 +14,12 @@ pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream { // Derives for the generated enum let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta.unique_attr("strum", "serialize_all") + let case_style = type_meta.find_unique_property("strum", "serialize_all") .map(|style| CaseStyle::from(style.as_ref())); let names = variants .iter() - .map(|v| convert_case(&v.ident, case_style)) + .map(|v| v.ident.convert_case(case_style)) .collect::>(); quote! { diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 4b439cb..9048ed6 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -1,10 +1,14 @@ -pub mod as_ref_str; -pub mod display; + pub mod enum_count; pub mod enum_discriminants; pub mod enum_iter; pub mod enum_messages; pub mod enum_properties; pub mod enum_variant_names; -pub mod from_string; -pub mod to_string; + +mod strings; + +pub use self::strings::as_ref_str; +pub use self::strings::display; +pub use self::strings::from_string; +pub use self::strings::to_string; diff --git a/strum_macros/src/macros/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs similarity index 91% rename from strum_macros/src/macros/as_ref_str.rs rename to strum_macros/src/macros/strings/as_ref_str.rs index 8bfb83b..2637f37 100644 --- a/strum_macros/src/macros/as_ref_str.rs +++ b/strum_macros/src/macros/strings/as_ref_str.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use syn; use crate::helpers::case_style::CaseStyle; -use helpers::{convert_case, extract_meta, MetaIteratorHelpers}; +use helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers}; fn get_arms(ast: &syn::DeriveInput) -> Vec { let name = &ast.ident; @@ -13,7 +13,7 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec { }; let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta.unique_attr("strum", "serialize_all") + let case_style = type_meta.find_unique_property("strum", "serialize_all") .map(|style| CaseStyle::from(style.as_ref())); for variant in variants { @@ -28,16 +28,16 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec { // Look at all the serialize attributes. // Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent // (i.e. always `enum.as_ref().to_string() == enum.to_string()`). - let output = if let Some(n) = meta.unique_attr("strum", "to_string") { + let output = if let Some(n) = meta.find_unique_property("strum", "to_string") { n } else { - let mut attrs = meta.extract_attrs("strum", "serialize"); + let mut attrs = meta.find_properties("strum", "serialize"); // We always take the longest one. This is arbitary, but is *mostly* deterministic attrs.sort_by_key(|s| s.len()); if let Some(n) = attrs.pop() { n } else { - convert_case(ident, case_style) + ident.convert_case(case_style) } }; diff --git a/strum_macros/src/macros/display.rs b/strum_macros/src/macros/strings/display.rs similarity index 83% rename from strum_macros/src/macros/display.rs rename to strum_macros/src/macros/strings/display.rs index e782647..5ec4346 100644 --- a/strum_macros/src/macros/display.rs +++ b/strum_macros/src/macros/strings/display.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use syn; use crate::helpers::case_style::CaseStyle; -use helpers::{convert_case, extract_meta, MetaIteratorHelpers}; +use helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers}; pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -13,7 +13,7 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { }; let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta.unique_attr("strum", "serialize_all") + let case_style = type_meta.find_unique_property("strum", "serialize_all") .map(|style| CaseStyle::from(style.as_ref())); let mut arms = Vec::new(); @@ -27,16 +27,16 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { } // Look at all the serialize attributes. - let output = if let Some(n) = meta.unique_attr("strum", "to_string") { + let output = if let Some(n) = meta.find_unique_property("strum", "to_string") { n } else { - let mut attrs = meta.extract_attrs("strum", "serialize"); + let mut attrs = meta.find_properties("strum", "serialize"); // We always take the longest one. This is arbitary, but is *mostly* deterministic attrs.sort_by_key(|s| s.len()); if let Some(n) = attrs.pop() { n } else { - convert_case(ident, case_style) + ident.convert_case(case_style) } }; diff --git a/strum_macros/src/macros/from_string.rs b/strum_macros/src/macros/strings/from_string.rs similarity index 81% rename from strum_macros/src/macros/from_string.rs rename to strum_macros/src/macros/strings/from_string.rs index 0715711..fc707aa 100644 --- a/strum_macros/src/macros/from_string.rs +++ b/strum_macros/src/macros/strings/from_string.rs @@ -2,8 +2,7 @@ use proc_macro2::TokenStream; use syn; use crate::helpers::case_style::CaseStyle; -// use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr, MetaIteratorHelpers}; -use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers}; +use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers}; pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -14,7 +13,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { }; let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta.unique_attr("strum", "serialize_all") + let case_style = type_meta.find_unique_property("strum", "serialize_all") .map(|style| CaseStyle::from(style.as_ref())); let mut has_default = false; @@ -27,18 +26,18 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { let meta = extract_meta(&variant.attrs); // Look at all the serialize attributes. - // let mut attrs = extract_attrs(&meta, "strum", "serialize"); - // attrs.extend(extract_attrs(&meta, "strum", "to_string")); + // let mut attrs = find_properties(&meta, "strum", "serialize"); + // attrs.extend(find_properties(&meta, "strum", "to_string")); - let mut attrs = meta.extract_attrs("strum", "serialize"); - attrs.extend(meta.extract_attrs("strum", "to_string")); + let mut attrs = meta.find_properties("strum", "serialize"); + attrs.extend(meta.find_properties("strum", "to_string")); // if is_disabled(&meta) { if meta.is_disabled() { continue; } - if meta.unique_attr("strum", "default").map_or(false, |s| s == "true") { + if meta.find_unique_property("strum", "default").map_or(false, |s| s == "true") { if has_default { panic!("Can't have multiple default variants"); } @@ -61,7 +60,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { // If we don't have any custom variants, add the default serialized name. if attrs.len() == 0 { - attrs.push(convert_case(ident, case_style)); + attrs.push(ident.convert_case(case_style)); } let params = match variant.fields { diff --git a/strum_macros/src/macros/strings/mod.rs b/strum_macros/src/macros/strings/mod.rs new file mode 100644 index 0000000..e416f4b --- /dev/null +++ b/strum_macros/src/macros/strings/mod.rs @@ -0,0 +1,4 @@ +pub mod as_ref_str; +pub mod display; +pub mod from_string; +pub mod to_string; diff --git a/strum_macros/src/macros/to_string.rs b/strum_macros/src/macros/strings/to_string.rs similarity index 82% rename from strum_macros/src/macros/to_string.rs rename to strum_macros/src/macros/strings/to_string.rs index f0cfbd4..4cbbd4f 100644 --- a/strum_macros/src/macros/to_string.rs +++ b/strum_macros/src/macros/strings/to_string.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use syn; use crate::helpers::case_style::CaseStyle; -use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers}; +use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers}; pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -13,7 +13,7 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { }; let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta.unique_attr( "strum", "serialize_all") + let case_style = type_meta.find_unique_property( "strum", "serialize_all") .map(|style| CaseStyle::from(style.as_ref())); let mut arms = Vec::new(); @@ -27,16 +27,16 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { } // Look at all the serialize attributes. - let output = if let Some(n) = meta.unique_attr("strum", "to_string") { + let output = if let Some(n) = meta.find_unique_property("strum", "to_string") { n } else { - let mut attrs = meta.extract_attrs("strum", "serialize"); + let mut attrs = meta.find_properties("strum", "serialize"); // We always take the longest one. This is arbitary, but is *mostly* deterministic attrs.sort_by_key(|s| s.len()); if let Some(n) = attrs.pop() { n } else { - convert_case(ident, case_style) + ident.convert_case(case_style) } };