From c5fc096ecee73c6c711623fdff7500091cf9bd2e Mon Sep 17 00:00:00 2001 From: Peter Glotfelty Date: Wed, 29 Jul 2020 09:36:48 -0700 Subject: [PATCH] Pulled some of the core logic out of plugins (#100) * Major refactor of code. * More minor refactorings * renamed variables * add repository link --- CHANGELOG.md | 24 ++++ strum_macros/Cargo.toml | 1 + strum_macros/src/helpers/has_metadata.rs | 43 ++++++ strum_macros/src/helpers/meta_helpers.rs | 59 +++++++-- .../src/helpers/meta_iterator_helpers.rs | 66 ---------- strum_macros/src/helpers/metalist_helpers.rs | 18 --- strum_macros/src/helpers/mod.rs | 21 +-- strum_macros/src/helpers/type_props.rs | 86 ++++++++++++ strum_macros/src/helpers/variant_props.rs | 123 ++++++++++++++++++ strum_macros/src/macros/enum_discriminants.rs | 37 +----- strum_macros/src/macros/enum_iter.rs | 4 +- strum_macros/src/macros/enum_messages.rs | 21 +-- strum_macros/src/macros/enum_properties.rs | 40 +----- strum_macros/src/macros/enum_variant_names.rs | 9 +- strum_macros/src/macros/strings/as_ref_str.rs | 26 +--- strum_macros/src/macros/strings/display.rs | 32 ++--- .../src/macros/strings/from_string.rs | 29 +---- strum_macros/src/macros/strings/to_string.rs | 26 +--- strum_tests/src/lib.rs | 2 +- strum_tests/src/main.rs | 2 +- strum_tests/tests/as_ref_no_strum.rs | 2 +- strum_tests/tests/as_ref_str.rs | 2 +- strum_tests/tests/display.rs | 17 ++- strum_tests/tests/enum_message.rs | 2 +- strum_tests/tests/from_str.rs | 2 +- strum_tests/tests/to_string.rs | 2 +- 26 files changed, 398 insertions(+), 298 deletions(-) create mode 100644 strum_macros/src/helpers/has_metadata.rs delete mode 100644 strum_macros/src/helpers/meta_iterator_helpers.rs delete mode 100644 strum_macros/src/helpers/metalist_helpers.rs create mode 100644 strum_macros/src/helpers/type_props.rs create mode 100644 strum_macros/src/helpers/variant_props.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c85e2fc..7cfb6db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 0.19.0 + +* **Breaking Change**. `default` and `disabled` should now be written as markers instead of key value pairs. + Here is the old way of adding these attributes to a variant. + ```rust + // OLD WAY + enum Test { + #[strum(disabled = "true", default = "true")] + Variant(String) + } + ``` + + Here is the new way. There is less ambiguity in the new syntax. + + ```rust + enum Test { + #[strum(disabled, default)] + Variant(String) + } + ``` +* **Breaking Change**. Most of the strum plugins will now error more aggresively on invalid options being + used. Historically, the plugins have ignore invalid options, but most of these should error now. Silent + errors are a rust anti-pattern. + ## 0.18.0 * Only copy across `"doc", "cfg", "allow", "deny"` attributes from main enum variants to discriminant variants. [#73](https://github.com/Peternator7/strum/issues/73) diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index 3e88195..a1805d9 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -10,6 +10,7 @@ categories = ["development-tools::procedural-macro-helpers", "parsing"] documentation = "https://docs.rs/strum" homepage = "https://github.com/Peternator7/strum" +repository = "https://github.com/Peternator7/strum" readme = "../README.md" [lib] diff --git a/strum_macros/src/helpers/has_metadata.rs b/strum_macros/src/helpers/has_metadata.rs new file mode 100644 index 0000000..f00cd04 --- /dev/null +++ b/strum_macros/src/helpers/has_metadata.rs @@ -0,0 +1,43 @@ +///Represents a type that can have strum metadata associated with it. +pub trait HasMetadata { + /// Get all the metadata associated with a specific "tag". + /// All of strum's metadata is nested inside a path such as + /// #[strum(...)] so this let's us quickly filter down to only our metadata. + fn get_metadata(&self, ident: &str) -> Vec; +} + +fn get_metadata_inner<'a>( + ident: &str, + it: impl IntoIterator, +) -> Vec { + it.into_iter() + .map(|attr| attr.parse_meta().unwrap()) + .filter_map(|meta| match meta { + syn::Meta::List(syn::MetaList { path, nested, .. }) => { + if path.is_ident(ident) { + Some(nested) + } else { + None + } + } + _ => None, + }) + .flat_map(|id| id) + .map(|nested| match nested { + syn::NestedMeta::Meta(meta) => meta, + _ => panic!("unexpected literal parsing strum attributes"), + }) + .collect() +} + +impl HasMetadata for syn::Variant { + fn get_metadata(&self, ident: &str) -> Vec { + get_metadata_inner(ident, &self.attrs) + } +} + +impl HasMetadata for syn::DeriveInput { + fn get_metadata(&self, ident: &str) -> Vec { + get_metadata_inner(ident, &self.attrs) + } +} diff --git a/strum_macros/src/helpers/meta_helpers.rs b/strum_macros/src/helpers/meta_helpers.rs index 1274e2f..6b55081 100644 --- a/strum_macros/src/helpers/meta_helpers.rs +++ b/strum_macros/src/helpers/meta_helpers.rs @@ -1,30 +1,63 @@ -use syn::{Meta, MetaList}; +use syn::{Meta, MetaList, NestedMeta}; pub trait MetaHelpers { - fn try_metalist(&self) -> Option<&MetaList>; - fn try_path(&self) -> Option<&syn::Path>; - fn try_namevalue(&self) -> Option<&syn::MetaNameValue>; + fn expect_metalist(&self, msg: &str) -> &MetaList; + fn expect_path(&self, msg: &str) -> &syn::Path; + fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue; } impl MetaHelpers for syn::Meta { - fn try_metalist(&self) -> Option<&MetaList> { + fn expect_metalist(&self, msg: &str) -> &MetaList { match self { - Meta::List(list) => Some(list), - _ => None, + Meta::List(list) => list, + _ => panic!("{}", msg), } } - fn try_path(&self) -> Option<&syn::Path> { + fn expect_path(&self, msg: &str) -> &syn::Path { match self { - Meta::Path(path) => Some(path), - _ => None, + Meta::Path(path) => path, + _ => panic!("{}", msg), } } - fn try_namevalue(&self) -> Option<&syn::MetaNameValue> { + fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue { match self { - Meta::NameValue(pair) => Some(pair), - _ => None, + Meta::NameValue(pair) => pair, + _ => panic!("{}", msg), + } + } +} + +pub trait NestedMetaHelpers { + fn expect_meta(&self, msg: &str) -> &syn::Meta; + fn expect_lit(&self, msg: &str) -> &syn::Lit; +} + +impl NestedMetaHelpers for NestedMeta { + fn expect_meta(&self, msg: &str) -> &Meta { + match self { + syn::NestedMeta::Meta(m) => m, + _ => panic!("{}", msg), + } + } + fn expect_lit(&self, msg: &str) -> &syn::Lit { + match self { + syn::NestedMeta::Lit(l) => l, + _ => panic!("{}", msg), + } + } +} + +pub trait LitHelpers { + fn expect_string(&self, msg: &str) -> String; +} + +impl LitHelpers for syn::Lit { + fn expect_string(&self, msg: &str) -> String { + match self { + syn::Lit::Str(s) => s.value(), + _ => panic!("{}", msg), } } } diff --git a/strum_macros/src/helpers/meta_iterator_helpers.rs b/strum_macros/src/helpers/meta_iterator_helpers.rs deleted file mode 100644 index 918f117..0000000 --- a/strum_macros/src/helpers/meta_iterator_helpers.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::MetaHelpers; -use super::MetaListHelpers; -use syn::Meta; - -pub trait MetaIteratorHelpers { - fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta>; - fn find_properties(&self, attr: &str, prop: &str) -> Vec; - - fn find_unique_property(&self, attr: &str, prop: &str) -> Option { - let mut curr = self.find_properties(attr, prop); - if curr.len() > 1 { - panic!("More than one property: {} found on variant", prop); - } - - curr.pop() - } - - fn is_disabled(&self) -> bool { - let v = self.find_properties("strum", "disabled"); - match v.len() { - 0 => false, - 1 => v[0] == "true", - _ => panic!("Can't have multiple values for 'disabled'"), - } - } -} - -//impl MetaIteratorHelpers for [Meta] -impl MetaIteratorHelpers for [T] -where - T: std::borrow::Borrow, -{ - 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()) - .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, - lit: Lit::Str(ref s), - .. - }) => { - if path.is_ident(prop) { - Some(s.value()) - } else { - None - } - } - _ => None, - }) - .collect() - } -} diff --git a/strum_macros/src/helpers/metalist_helpers.rs b/strum_macros/src/helpers/metalist_helpers.rs deleted file mode 100644 index 4b6709b..0000000 --- a/strum_macros/src/helpers/metalist_helpers.rs +++ /dev/null @@ -1,18 +0,0 @@ -use syn::Meta; - -pub trait MetaListHelpers { - fn expand_inner(&self) -> Vec<&Meta>; -} - -impl MetaListHelpers for syn::MetaList { - fn expand_inner(&self) -> Vec<&Meta> { - use syn::NestedMeta; - self.nested - .iter() - .filter_map(|nested| match *nested { - NestedMeta::Meta(ref meta) => Some(meta), - _ => None, - }) - .collect() - } -} diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 1d68efd..f30dbdc 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -1,18 +1,11 @@ -use syn::{Attribute, Meta}; +pub use self::case_style::CaseStyleHelpers; +pub use self::meta_helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers}; +pub use self::type_props::HasTypeProperties; +pub use self::variant_props::HasStrumVariantProperties; pub mod case_style; +pub mod type_props; +pub mod variant_props; +mod has_metadata; mod meta_helpers; -mod meta_iterator_helpers; -mod metalist_helpers; -pub use self::case_style::CaseStyleHelpers; -pub use self::meta_helpers::MetaHelpers; -pub use self::meta_iterator_helpers::MetaIteratorHelpers; -pub use self::metalist_helpers::MetaListHelpers; - -pub fn extract_meta(attrs: &[Attribute]) -> Vec { - attrs - .iter() - .filter_map(|attribute| attribute.parse_meta().ok()) - .collect() -} diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs new file mode 100644 index 0000000..98f1039 --- /dev/null +++ b/strum_macros/src/helpers/type_props.rs @@ -0,0 +1,86 @@ +use std::convert::From; +use std::default::Default; + +use crate::helpers::case_style::CaseStyle; +use crate::helpers::has_metadata::HasMetadata; +use crate::helpers::{MetaHelpers, NestedMetaHelpers}; + +pub trait HasTypeProperties { + fn get_type_properties(&self) -> StrumTypeProperties; +} + +#[derive(Debug, Clone, Eq, PartialEq, Default)] +pub struct StrumTypeProperties { + pub case_style: Option, + pub discriminant_derives: Vec, + pub discriminant_name: Option, + pub discriminant_others: Vec, +} + +impl HasTypeProperties for syn::DeriveInput { + fn get_type_properties(&self) -> StrumTypeProperties { + let mut output = StrumTypeProperties::default(); + + let strum_meta = self.get_metadata("strum"); + let discriminants_meta = self.get_metadata("strum_discriminants"); + + for meta in strum_meta { + let meta = match meta { + syn::Meta::NameValue(mv) => mv, + _ => panic!("strum on types only supports key-values"), + }; + + if meta.path.is_ident("serialize_all") { + let style = match meta.lit { + syn::Lit::Str(s) => s.value(), + _ => panic!("expected string value for 'serialize_all'"), + }; + + if output.case_style.is_some() { + panic!("found multiple values of serialize_all"); + } + + output.case_style = Some(CaseStyle::from(&*style)); + } else { + panic!("unrecognized attribue found on strum(..)"); + } + } + + for meta in discriminants_meta { + match meta { + syn::Meta::List(ref ls) => { + if ls.path.is_ident("derive") { + let paths = ls + .nested + .iter() + .map(|meta| meta.expect_meta("unexpected literal").path().clone()); + + output.discriminant_derives.extend(paths); + } else if ls.path.is_ident("name") { + if ls.nested.len() != 1 { + panic!("name expects exactly 1 value"); + } + + let value = ls.nested.first().expect("unexpected error"); + let name = value + .expect_meta("unexpected literal") + .expect_path("name must be an identifier"); + + if output.discriminant_name.is_some() { + panic!("multiple occurrences of 'name'"); + } + + output.discriminant_name = Some(name.clone()); + } else { + output.discriminant_others.push(meta); + } + } + _ => { + output.discriminant_others.push(meta); + } + } + } + + output + } +} diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs new file mode 100644 index 0000000..45ec888 --- /dev/null +++ b/strum_macros/src/helpers/variant_props.rs @@ -0,0 +1,123 @@ +use std::collections::HashMap; +use std::default::Default; + +use crate::helpers::case_style::{CaseStyle, CaseStyleHelpers}; +use crate::helpers::has_metadata::HasMetadata; +use crate::helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers}; + +pub trait HasStrumVariantProperties { + fn get_variant_properties(&self) -> StrumVariantProperties; +} + +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct StrumVariantProperties { + pub is_disabled: bool, + pub default: bool, + pub message: Option, + pub detailed_message: Option, + pub string_props: HashMap, + serialize: Vec, + to_string: Option, + ident: Option, +} + +impl StrumVariantProperties { + pub fn get_preferred_name(&self, case_style: Option) -> String { + if let Some(ref to_string) = self.to_string { + to_string.clone() + } else { + let mut serialized = self.serialize.clone(); + serialized.sort_by_key(|s| s.len()); + if let Some(n) = serialized.pop() { + n + } else { + self.ident.as_ref().expect("identifier").convert_case(case_style) + } + } + } + + pub fn get_serializations(&self, case_style: Option) -> Vec { + let mut attrs = self.serialize.clone(); + if let Some(ref to_string) = self.to_string { + attrs.push(to_string.clone()); + } + + if attrs.is_empty() { + attrs.push(self.ident.as_ref().expect("identifier").convert_case(case_style)); + } + + attrs + } +} + +impl HasStrumVariantProperties for syn::Variant { + fn get_variant_properties(&self) -> StrumVariantProperties { + let mut output = StrumVariantProperties::default(); + output.ident = Some(self.ident.clone()); + + for meta in self.get_metadata("strum") { + match meta { + syn::Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => { + if path.is_ident("message") { + if output.message.is_some() { + panic!("message is set twice on the same variant"); + } + + output.message = Some(lit.expect_string("expected string")); + } else if path.is_ident("detailed_message") { + if output.detailed_message.is_some() { + panic!("detailed message set twice on the same variant"); + } + + output.detailed_message = Some(lit.expect_string("expected string")); + } else if path.is_ident("serialize") { + output.serialize.push(lit.expect_string("expected string")); + } else if path.is_ident("to_string") { + if output.to_string.is_some() { + panic!("to_string is set twice on the same variant"); + } + + output.to_string = Some(lit.expect_string("expected string")); + } else if path.is_ident("disabled") { + panic!("this method is deprecated. Prefer #[strum(disabled)] instead of #[strum(disabled=\"true\")]"); + } else if path.is_ident("default") { + panic!("this method is deprecated. Prefer #[strum(default)] instead of #[strum(default=\"true\")]"); + } else { + panic!("unrecognized value in strum(..) attribute"); + } + } + syn::Meta::Path(p) => { + if p.is_ident("disabled") { + output.is_disabled = true; + } else if p.is_ident("default") { + output.default = true; + } else { + panic!("unrecognized value in strum(..) attribute"); + } + } + syn::Meta::List(syn::MetaList { path, nested, .. }) => { + if path.is_ident("props") { + for p in nested { + let p = p + .expect_meta("unexpected literal found in props") + .expect_namevalue("props must be key-value pairs"); + + let key = p + .path + .get_ident() + .expect("key must be an identifier") + .to_string(); + + let value = p.lit.expect_string("expected string"); + output.string_props.insert(key, value); + } + } else { + panic!("unrecognized value in strum(..) attribute"); + } + } + } + } + + output + } +} diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs index 5191cb3..c270329 100644 --- a/strum_macros/src/macros/enum_discriminants.rs +++ b/strum_macros/src/macros/enum_discriminants.rs @@ -1,8 +1,7 @@ -use crate::helpers::{MetaHelpers, MetaIteratorHelpers, MetaListHelpers}; use proc_macro2::{Span, TokenStream}; use syn; -use helpers::extract_meta; +use crate::helpers::HasTypeProperties; /// Attributes to copy from the main enum's variants to the discriminant enum's variants. /// @@ -20,15 +19,9 @@ 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 - .find_attribute("strum_discriminants") - .collect::>(); + let type_properties = ast.get_type_properties(); - let derives = discriminant_attrs - .find_attribute("derive") - .map(|meta| meta.path()) - .collect::>(); + let derives = type_properties.discriminant_derives; let derives = quote! { #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)] @@ -40,28 +33,12 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { Span::call_site(), )); - 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); + let discriminants_name = type_properties.discriminant_name.unwrap_or(default_name); // Pass through all other attributes - let pass_though_attributes = discriminant_attrs - .iter() - .filter(|meta| { - let path = meta.path(); - !path.is_ident("derive") && !path.is_ident("name") - }) + let pass_though_attributes = type_properties + .discriminant_others + .into_iter() .map(|meta| quote! { #[ #meta ] }) .collect::>(); diff --git a/strum_macros/src/macros/enum_iter.rs b/strum_macros/src/macros/enum_iter.rs index 05978de..1c8b5df 100644 --- a/strum_macros/src/macros/enum_iter.rs +++ b/strum_macros/src/macros/enum_iter.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use syn; -use helpers::{extract_meta, MetaIteratorHelpers}; +use crate::helpers::HasStrumVariantProperties; pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -31,7 +31,7 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream { let mut arms = Vec::new(); let enabled = variants .iter() - .filter(|variant| !extract_meta(&variant.attrs).is_disabled()); + .filter(|variant| !variant.get_variant_properties().is_disabled); for (idx, variant) in enabled.enumerate() { use syn::Fields::*; diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs index d46b66d..bca25e8 100644 --- a/strum_macros/src/macros/enum_messages.rs +++ b/strum_macros/src/macros/enum_messages.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::case_style::CaseStyle; -use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -12,19 +11,16 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { _ => panic!("EnumMessage only works on Enums"), }; - let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); + let type_properties = ast.get_type_properties(); let mut arms = Vec::new(); let mut detailed_arms = Vec::new(); let mut serializations = Vec::new(); for variant in variants { - let meta = extract_meta(&variant.attrs); - let messages = meta.find_unique_property("strum", "message"); - let detailed_messages = meta.find_unique_property("strum", "detailed_message"); + let variant_properties = variant.get_variant_properties(); + let messages = variant_properties.message.as_ref(); + let detailed_messages = variant_properties.detailed_message.as_ref(); let ident = &variant.ident; use syn::Fields::*; @@ -36,10 +32,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { // You can't disable getting the serializations. { - let mut serialization_variants = meta.find_properties("strum", "serialize"); - if serialization_variants.len() == 0 { - serialization_variants.push(ident.convert_case(case_style)); - } + let serialization_variants = variant_properties.get_serializations(type_properties.case_style); let count = serialization_variants.len(); serializations.push(quote! { @@ -51,7 +44,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { } // But you can disable the messages. - if meta.is_disabled() { + if variant_properties.is_disabled { continue; } diff --git a/strum_macros/src/macros/enum_properties.rs b/strum_macros/src/macros/enum_properties.rs index 6b80ffb..469aafa 100644 --- a/strum_macros/src/macros/enum_properties.rs +++ b/strum_macros/src/macros/enum_properties.rs @@ -1,28 +1,7 @@ use proc_macro2::TokenStream; use syn; -use syn::Meta; -use crate::helpers::{extract_meta, MetaHelpers, MetaIteratorHelpers, MetaListHelpers}; - -fn extract_properties(meta: &[Meta]) -> Vec<(&syn::Path, &syn::Lit)> { - meta.iter() - // Filter down to the strum(..) attribute - .filter_map(|meta| meta.try_metalist()) - .filter(|list| list.path.is_ident("strum")) - .flat_map(|list| list.expand_inner()) - // Filter down to the `strum(props(..))` attribute - .filter_map(|meta| meta.try_metalist()) - .filter(|inner_list| inner_list.path.is_ident("props")) - .flat_map(|inner_list| inner_list.expand_inner()) - // Expand all the pairs `strum(props(key = value, ..))` - .filter_map(|prop| match *prop { - syn::Meta::NameValue(syn::MetaNameValue { - ref path, ref lit, .. - }) => Some((path, lit)), - _ => None, - }) - .collect() -} +use crate::helpers::HasStrumVariantProperties; pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -35,12 +14,12 @@ pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream { let mut arms = Vec::new(); for variant in variants { let ident = &variant.ident; - let meta = extract_meta(&variant.attrs); + let variant_properties = variant.get_variant_properties(); let mut string_arms = Vec::new(); let mut bool_arms = Vec::new(); let mut num_arms = Vec::new(); // But you can disable the messages. - if meta.is_disabled() { + if variant_properties.is_disabled { continue; } @@ -51,17 +30,8 @@ pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream { Named(..) => quote! { {..} }, }; - for (key, value) in extract_properties(&meta) { - use syn::Lit::*; - let key = key.segments.last().unwrap().ident.to_string(); - match value { - Str(ref s, ..) => { - string_arms.push(quote! { #key => ::std::option::Option::Some( #s )}) - } - Bool(b) => bool_arms.push(quote! { #key => ::std::option::Option::Some( #b )}), - Int(i, ..) => num_arms.push(quote! { #key => ::std::option::Option::Some( #i )}), - _ => {} - } + for (key, value) in variant_properties.string_props { + string_arms.push(quote! { #key => ::std::option::Option::Some( #value )}) } string_arms.push(quote! { _ => ::std::option::Option::None }); diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs index ae6dcd7..588c418 100644 --- a/strum_macros/src/macros/enum_variant_names.rs +++ b/strum_macros/src/macros/enum_variant_names.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::{case_style::CaseStyle, extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{CaseStyleHelpers, HasTypeProperties}; pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -14,14 +14,11 @@ 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 - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); + let type_properties = ast.get_type_properties(); let names = variants .iter() - .map(|v| v.ident.convert_case(case_style)) + .map(|v| v.ident.convert_case(type_properties.case_style)) .collect::>(); quote! { diff --git a/strum_macros/src/macros/strings/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs index 524754f..3966db1 100644 --- a/strum_macros/src/macros/strings/as_ref_str.rs +++ b/strum_macros/src/macros/strings/as_ref_str.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::case_style::CaseStyle; -use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{HasTypeProperties, HasStrumVariantProperties}; fn get_arms(ast: &syn::DeriveInput) -> Vec { let name = &ast.ident; @@ -12,36 +11,21 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec { _ => panic!("This macro only works on Enums"), }; - let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); + let type_properties = ast.get_type_properties(); for variant in variants { use syn::Fields::*; let ident = &variant.ident; - let meta = extract_meta(&variant.attrs); + let variant_properties = variant.get_variant_properties(); - if meta.is_disabled() { + if variant_properties.is_disabled { continue; } // 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.find_unique_property("strum", "to_string") { - n - } else { - 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 { - ident.convert_case(case_style) - } - }; - + let output = variant_properties.get_preferred_name(type_properties.case_style); let params = match variant.fields { Unit => quote! {}, Unnamed(..) => quote! { (..) }, diff --git a/strum_macros/src/macros/strings/display.rs b/strum_macros/src/macros/strings/display.rs index 2260b60..20fe361 100644 --- a/strum_macros/src/macros/strings/display.rs +++ b/strum_macros/src/macros/strings/display.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::case_style::CaseStyle; -use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{HasTypeProperties, HasStrumVariantProperties}; pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -12,39 +11,24 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { _ => panic!("Display only works on Enums"), }; - let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); + let type_properties = ast.get_type_properties(); let mut arms = Vec::new(); for variant in variants { - use syn::Fields::*; let ident = &variant.ident; - let meta = extract_meta(&variant.attrs); + let variant_properties = variant.get_variant_properties(); - if meta.is_disabled() { + if variant_properties.is_disabled { continue; } // Look at all the serialize attributes. - let output = if let Some(n) = meta.find_unique_property("strum", "to_string") { - n - } else { - 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 { - ident.convert_case(case_style) - } - }; + let output = variant_properties.get_preferred_name(type_properties.case_style); let params = match variant.fields { - Unit => quote! {}, - Unnamed(..) => quote! { (..) }, - Named(..) => quote! { {..} }, + syn::Fields::Unit => quote! {}, + syn::Fields::Unnamed(..) => quote! { (..) }, + syn::Fields::Named(..) => quote! { {..} }, }; arms.push(quote! { #name::#ident #params => f.pad(#output) }); diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs index 0346587..0d2fe0a 100644 --- a/strum_macros/src/macros/strings/from_string.rs +++ b/strum_macros/src/macros/strings/from_string.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::case_style::CaseStyle; -use crate::helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{HasTypeProperties, HasStrumVariantProperties}; pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -12,10 +11,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { _ => panic!("FromString only works on Enums"), }; - let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); + let type_properties = ast.get_type_properties(); let mut has_default = false; let mut default = @@ -24,24 +20,13 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { for variant in variants { use syn::Fields::*; let ident = &variant.ident; - let meta = extract_meta(&variant.attrs); + let variant_properties = variant.get_variant_properties(); - // Look at all the serialize attributes. - // let mut attrs = find_properties(&meta, "strum", "serialize"); - // attrs.extend(find_properties(&meta, "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() { + if variant_properties.is_disabled { continue; } - if meta - .find_unique_property("strum", "default") - .map_or(false, |s| s == "true") - { + if variant_properties.default { if has_default { panic!("Can't have multiple default variants"); } @@ -63,9 +48,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(ident.convert_case(case_style)); - } + let attrs = variant_properties.get_serializations(type_properties.case_style); let params = match variant.fields { Unit => quote! {}, diff --git a/strum_macros/src/macros/strings/to_string.rs b/strum_macros/src/macros/strings/to_string.rs index dfa4e6a..234742f 100644 --- a/strum_macros/src/macros/strings/to_string.rs +++ b/strum_macros/src/macros/strings/to_string.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; use syn; -use crate::helpers::case_style::CaseStyle; -use crate::helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers}; +use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -12,34 +11,19 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { _ => panic!("ToString only works on Enums"), }; - let type_meta = extract_meta(&ast.attrs); - let case_style = type_meta - .find_unique_property("strum", "serialize_all") - .map(|style| CaseStyle::from(style.as_ref())); - + let type_properties = ast.get_type_properties(); let mut arms = Vec::new(); for variant in variants { use syn::Fields::*; let ident = &variant.ident; - let meta = extract_meta(&variant.attrs); + let variant_properties = variant.get_variant_properties(); - if meta.is_disabled() { + if variant_properties.is_disabled { continue; } // Look at all the serialize attributes. - let output = if let Some(n) = meta.find_unique_property("strum", "to_string") { - n - } else { - 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 { - ident.convert_case(case_style) - } - }; + let output = variant_properties.get_preferred_name(type_properties.case_style); let params = match variant.fields { Unit => quote! {}, diff --git a/strum_tests/src/lib.rs b/strum_tests/src/lib.rs index 0d64650..b1091c4 100644 --- a/strum_tests/src/lib.rs +++ b/strum_tests/src/lib.rs @@ -14,6 +14,6 @@ pub enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(disabled = "true")] + #[strum(disabled)] Green(String), } diff --git a/strum_tests/src/main.rs b/strum_tests/src/main.rs index d2a1123..5c26718 100644 --- a/strum_tests/src/main.rs +++ b/strum_tests/src/main.rs @@ -12,7 +12,7 @@ enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(disabled = "true")] + #[strum(disabled)] Green(String), } diff --git a/strum_tests/tests/as_ref_no_strum.rs b/strum_tests/tests/as_ref_no_strum.rs index 446a082..2606a33 100644 --- a/strum_tests/tests/as_ref_no_strum.rs +++ b/strum_tests/tests/as_ref_no_strum.rs @@ -9,7 +9,7 @@ enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default = "true")] + #[strum(default)] Green(String), } diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs index e0fa77e..33b2f25 100644 --- a/strum_tests/tests/as_ref_str.rs +++ b/strum_tests/tests/as_ref_str.rs @@ -13,7 +13,7 @@ enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default = "true")] + #[strum(default)] Green(String), } diff --git a/strum_tests/tests/display.rs b/strum_tests/tests/display.rs index c36d6d2..59d458a 100644 --- a/strum_tests/tests/display.rs +++ b/strum_tests/tests/display.rs @@ -10,7 +10,7 @@ enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default = "true")] + #[strum(default)] Green(String), } @@ -21,9 +21,18 @@ fn to_blue_string() { #[test] fn test_formatters() { - assert_eq!(String::from(" blue"), format!("{:>6}", Color::Blue { hue: 0 })); - assert_eq!(String::from("blue "), format!("{:<6}", Color::Blue { hue: 0 })); - assert_eq!(String::from(" blue "), format!("{:^6}", Color::Blue { hue: 0 })); + assert_eq!( + String::from(" blue"), + format!("{:>6}", Color::Blue { hue: 0 }) + ); + assert_eq!( + String::from("blue "), + format!("{:<6}", Color::Blue { hue: 0 }) + ); + assert_eq!( + String::from(" blue "), + format!("{:^6}", Color::Blue { hue: 0 }) + ); assert_eq!(String::from("bl"), format!("{:.2}", Color::Blue { hue: 0 })); } diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs index ad617b8..5d506a4 100644 --- a/strum_tests/tests/enum_message.rs +++ b/strum_tests/tests/enum_message.rs @@ -15,7 +15,7 @@ enum Pets { #[strum(detailed_message = "My fish is named Charles McFish")] Fish, Bird, - #[strum(disabled = "true")] + #[strum(disabled)] Hamster, } diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs index 3203f19..88a8c3b 100644 --- a/strum_tests/tests/from_str.rs +++ b/strum_tests/tests/from_str.rs @@ -12,7 +12,7 @@ enum Color { }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default = "true")] + #[strum(default)] Green(String), #[strum(to_string = "purp")] Purple, diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs index a345b6f..1ec5cfb 100644 --- a/strum_tests/tests/to_string.rs +++ b/strum_tests/tests/to_string.rs @@ -13,7 +13,7 @@ enum Color { Blue { hue: usize }, #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default = "true")] + #[strum(default)] Green(String), }