Repository: Peternator7/strum
Pull request #100 (2020-07-27 06:26:07)

Pulled some of the core logic out of plugins


Patch:
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<syn::Meta>;
+}
+
+fn get_metadata_inner<'a>(
+    ident: &str,
+    it: impl IntoIterator<Item = &'a syn::Attribute>,
+) -> Vec<syn::Meta> {
+    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<syn::Meta> {
+        get_metadata_inner(ident, &self.attrs)
+    }
+}
+
+impl HasMetadata for syn::DeriveInput {
+    fn get_metadata(&self, ident: &str) -> Vec<syn::Meta> {
+        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<String>;
-
-    fn find_unique_property(&self, attr: &str, prop: &str) -> Option<String> {
-        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<T> MetaIteratorHelpers for [T]
-where
-    T: std::borrow::Borrow<Meta>,
-{
-    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::<Vec<_>>()
-            .into_iter()
-    }
-
-    fn find_properties(&self, attr: &str, prop: &str) -> Vec<String> {
-        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<Meta> {
-    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<CaseStyle>,
+    pub discriminant_derives: Vec<syn::Path>,
+    pub discriminant_name: Option<syn::Path>,
+    pub discriminant_others: Vec<syn::Meta>,
+}
+
+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<String>,
+    pub detailed_message: Option<String>,
+    pub string_props: HashMap<String, String>,
+    serialize: Vec<String>,
+    to_string: Option<String>,
+    ident: Option<syn::Ident>,
+}
+
+impl StrumVariantProperties {
+    pub fn get_preferred_name(&self, case_style: Option<CaseStyle>) -> 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<CaseStyle>) -> Vec<String> {
+        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::<Vec<&syn::Meta>>();
-
-    let derives = discriminant_attrs
-        .find_attribute("derive")
-        .map(|meta| meta.path())
-        .collect::<Vec<_>>();
+    let type_properties = ast.get_type_properties();
+
+    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::<Vec<_>>();
 
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::<Vec<_>>();
 
     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<TokenStream> {
     let name = &ast.ident;
@@ -12,36 +11,21 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
         _ => 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! {},


Test patch:
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),
 }