From bb2a37e9b6ed8706bafe0f71bb45ceb916b76a77 Mon Sep 17 00:00:00 2001 From: Peternator7 Date: Sat, 15 Apr 2017 18:08:49 -0700 Subject: [PATCH 1/3] Refactored deriviations into different files and added a ToString implementation --- strum_macros/src/enum_iter.rs | 100 ++++++ strum_macros/src/enum_messages.rs | 100 ++++++ strum_macros/src/enum_properties.rs | 108 ++++++ strum_macros/src/from_string.rs | 95 ++++++ strum_macros/src/helpers.rs | 45 +++ strum_macros/src/lib.rs | 488 +++------------------------- strum_macros/src/to_string.rs | 60 ++++ strum_tests/src/main.rs | 20 ++ strum_tests/tests/enum_iter.rs | 4 +- strum_tests/tests/to_string.rs | 40 +++ 10 files changed, 615 insertions(+), 445 deletions(-) create mode 100644 strum_macros/src/enum_iter.rs create mode 100644 strum_macros/src/enum_messages.rs create mode 100644 strum_macros/src/enum_properties.rs create mode 100644 strum_macros/src/from_string.rs create mode 100644 strum_macros/src/helpers.rs create mode 100644 strum_macros/src/to_string.rs create mode 100644 strum_tests/src/main.rs create mode 100644 strum_tests/tests/to_string.rs diff --git a/strum_macros/src/enum_iter.rs b/strum_macros/src/enum_iter.rs new file mode 100644 index 0000000..53de92c --- /dev/null +++ b/strum_macros/src/enum_iter.rs @@ -0,0 +1,100 @@ +use quote; +use syn; + +use helpers::is_disabled; + +pub fn enum_iter_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let gen = &ast.generics; + let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); + let vis = &ast.vis; + + if gen.lifetimes.len() > 0 { + panic!("Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \ + be unbounded."); + } + + let phantom_data = if gen.ty_params.len() > 0 { + let g = gen.ty_params + .iter() + .map(|param| ¶m.ident) + .collect::>(); + quote!{ < ( #(#g),* ) > } + } else { + quote! { < () > } + }; + + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("EnumIter only works on Enums"), + }; + + let mut arms = Vec::new(); + let enabled = variants + .iter() + .filter(|variant| !is_disabled(&variant.attrs)); + + for (idx, variant) in enabled.enumerate() { + use syn::VariantData::*; + let ident = &variant.ident; + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(ref fields) => { + let default = fields + .iter() + .map(|_| "::std::default::Default::default()") + .collect::>() + .join(", "); + + quote::Ident::from(&*format!("({})", default)) + } + Struct(ref fields) => { + let default = fields + .iter() + .map(|field| { + format!("{}: {}", + field.ident.as_ref().unwrap(), + "::std::default::Default::default()") + }) + .collect::>() + .join(", "); + + quote::Ident::from(&*format!("{{{}}}", default)) + } + }; + + arms.push(quote!{#idx => ::std::option::Option::Some(#name::#ident #params)}); + } + + arms.push(quote! { _ => ::std::option::Option::None }); + let iter_name = quote::Ident::from(&*format!("{}Iter", name)); + quote!{ + #vis struct #iter_name #ty_generics { + idx: usize, + marker: ::std::marker::PhantomData #phantom_data, + } + + impl #impl_generics strum::IntoEnumIterator for #name #ty_generics #where_clause { + type Iterator = #iter_name #ty_generics; + fn iter() -> #iter_name #ty_generics { + #iter_name { + idx:0, + marker: std::marker::PhantomData, + } + } + } + + impl #impl_generics Iterator for #iter_name #ty_generics #where_clause { + type Item = #name #ty_generics; + + fn next(&mut self) -> Option<#name #ty_generics> { + let output = match self.idx { + #(#arms),* + }; + + self.idx += 1; + output + } + } + } +} \ No newline at end of file diff --git a/strum_macros/src/enum_messages.rs b/strum_macros/src/enum_messages.rs new file mode 100644 index 0000000..f195fbe --- /dev/null +++ b/strum_macros/src/enum_messages.rs @@ -0,0 +1,100 @@ +use quote; +use syn; + +use helpers::{unique_attr, extract_attrs, is_disabled}; + +pub fn enum_message_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("EnumMessage only works on Enums"), + }; + + let mut arms = Vec::new(); + let mut detailed_arms = Vec::new(); + let mut serializations = Vec::new(); + + for variant in variants { + let messages = unique_attr(&variant.attrs, "strum", "message"); + let detailed_messages = unique_attr(&variant.attrs, "strum", "detailed_message"); + let ident = &variant.ident; + + use syn::VariantData::*; + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(..) => quote::Ident::from("(..)"), + Struct(..) => quote::Ident::from("{..}"), + }; + + // You can't disable getting the serializations. + { + let mut serialization_variants = extract_attrs(&variant.attrs, "strum", "serialize"); + if serialization_variants.len() == 0 { + serialization_variants.push(ident.as_ref()); + } + + let count = serialization_variants.len(); + serializations.push(quote!{ + &#name::#ident #params => { + static ARR: [&'static str; #count] = [#(#serialization_variants),*]; + &ARR + } + }); + } + + // But you can disable the messages. + if is_disabled(&variant.attrs) { + continue; + } + + if let Some(msg) = messages { + let params = params.clone(); + + // Push the simple message. + let tokens = quote!{ &#name::#ident #params => ::std::option::Option::Some(#msg) }; + arms.push(tokens.clone()); + + if detailed_messages.is_none() { + detailed_arms.push(tokens); + } + } + + if let Some(msg) = detailed_messages { + let params = params.clone(); + // Push the simple message. + detailed_arms + .push(quote!{ &#name::#ident #params => ::std::option::Option::Some(#msg) }); + } + } + + if arms.len() < variants.len() { + arms.push(quote!{ _ => ::std::option::Option::None }); + } + + if detailed_arms.len() < variants.len() { + detailed_arms.push(quote!{ _ => ::std::option::Option::None }); + } + + quote!{ + impl #impl_generics ::strum::EnumMessage for #name #ty_generics #where_clause { + fn get_message(&self) -> ::std::option::Option<&str> { + match self { + #(#arms),* + } + } + + fn get_detailed_message(&self) -> ::std::option::Option<&str> { + match self { + #(#detailed_arms),* + } + } + + fn get_serializations(&self) -> &[&str] { + match self { + #(#serializations),* + } + } + } + } +} diff --git a/strum_macros/src/enum_properties.rs b/strum_macros/src/enum_properties.rs new file mode 100644 index 0000000..abf63bf --- /dev/null +++ b/strum_macros/src/enum_properties.rs @@ -0,0 +1,108 @@ +use quote; +use syn; + +use helpers::is_disabled; + +fn extract_properties(ast: &syn::Variant) -> Vec<(&syn::Ident, &syn::Lit)> { + use syn::*; + ast.attrs + .iter() + .filter_map(|attr| { + // Look for all the strum attributes + if let &Attribute { value: MetaItem::List(ref ident, ref nested), .. } = attr { + if ident == "strum" { + return Option::Some(nested); + } + } + + Option::None + }) + .flat_map(|prop| prop) + .filter_map(|prop| { + // Look for all the recursive property attributes + if let &NestedMetaItem::MetaItem(MetaItem::List(ref ident, ref nested)) = prop { + if ident == "props" { + return Option::Some(nested); + } + } + + Option::None + }) + .flat_map(|prop| prop) + .filter_map(|prop| { + // Only look at key value pairs + if let &NestedMetaItem::MetaItem(MetaItem::NameValue(ref ident, ref value)) = prop { + return Option::Some((ident, value)); + } + + Option::None + }) + .collect() +} + +pub fn enum_properties_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("EnumProp only works on Enums"), + }; + + let mut arms = Vec::new(); + for variant in variants { + let ident = &variant.ident; + 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 is_disabled(&variant.attrs) { + continue; + } + + use syn::VariantData::*; + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(..) => quote::Ident::from("(..)"), + Struct(..) => quote::Ident::from("{..}"), + }; + + for (key, value) in extract_properties(&variant) { + use syn::Lit::*; + let key = key.as_ref(); + 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 )}), + _ => {} + } + } + + string_arms.push(quote!{ _ => ::std::option::Option::None }); + bool_arms.push(quote!{ _ => ::std::option::Option::None }); + num_arms.push(quote!{ _ => ::std::option::Option::None }); + + arms.push(quote!{ + &#name::#ident #params => { + match prop { + #(#string_arms),* + } + } + }); + } + + if arms.len() < variants.len() { + arms.push(quote!{ _ => ::std::option::Option::None }); + } + + quote!{ + impl #impl_generics ::strum::EnumProperty for #name #ty_generics #where_clause { + fn get_str(&self, prop: &str) -> ::std::option::Option<&'static str> { + match self { + #(#arms),* + } + } + } + } +} diff --git a/strum_macros/src/from_string.rs b/strum_macros/src/from_string.rs new file mode 100644 index 0000000..668ba60 --- /dev/null +++ b/strum_macros/src/from_string.rs @@ -0,0 +1,95 @@ + +use quote; +use syn; + +use helpers::{unique_attr, extract_attrs, is_disabled}; + +pub fn from_string_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("FromString only works on Enums"), + }; + + let mut has_default = false; + let mut default = + quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) }; + let mut arms = Vec::new(); + for variant in variants { + use syn::VariantData::*; + let ident = &variant.ident; + + // Look at all the serialize attributes. + let mut attrs = extract_attrs(&variant.attrs, "strum", "serialize"); + attrs.extend(extract_attrs(&variant.attrs, "strum", "to_string")); + if is_disabled(&variant.attrs) { + continue; + } + + if let Some("true") = unique_attr(&variant.attrs, "strum", "default") { + if has_default { + panic!("Can't have multiple default variants"); + } + + if let Tuple(ref fields) = variant.data { + if fields.len() != 1 { + panic!("Default only works on unit structs with a single String parameter"); + } + + default = quote!{ + default => ::std::result::Result::Ok(#name::#ident (default.into())) + }; + } else { + panic!("Default only works on unit structs with a single String parameter"); + } + + has_default = true; + continue; + } + + // If we don't have any custom variants, add the default name. + if attrs.len() == 0 { + attrs.push(ident.as_ref()); + } + + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(ref fields) => { + let default = fields + .iter() + .map(|_| "Default::default()") + .collect::>() + .join(", "); + + quote::Ident::from(&*format!("({})", default)) + } + Struct(ref fields) => { + let default = fields + .iter() + .map(|field| { + format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()") + }) + .collect::>() + .join(", "); + + quote::Ident::from(&*format!("{{{}}}", default)) + } + }; + + arms.push(quote!{ #(#attrs)|* => ::std::result::Result::Ok(#name::#ident #params) }); + } + + arms.push(default); + + quote!{ + impl #impl_generics ::std::str::FromStr for #name #ty_generics #where_clause { + type Err = ::strum::ParseError; + fn from_str(s: &str) -> ::std::result::Result< #name #ty_generics , Self::Err> { + match s { + #(#arms),* + } + } + } + } +} \ No newline at end of file diff --git a/strum_macros/src/helpers.rs b/strum_macros/src/helpers.rs new file mode 100644 index 0000000..c32e839 --- /dev/null +++ b/strum_macros/src/helpers.rs @@ -0,0 +1,45 @@ + +use syn; +use syn::Attribute; + +pub fn extract_attrs<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Vec<&'a str> { + attrs.iter() + // Get all the attributes with our tag on them. + .filter_map(|attribute| { + use syn::MetaItem::*; + if let List(ref i, ref nested) = attribute.value { + if i == attr { Some(nested) } else { None } + } else { + None + } + }) + .flat_map(|nested| nested) + // Get all the inner elements as long as they start with ser. + .filter_map(|attribute| { + use syn::NestedMetaItem::*; + use syn::MetaItem::*; + if let &MetaItem(NameValue(ref i, syn::Lit::Str(ref s, ..))) = attribute { + if i == prop { Some(&**s) } else { None } + } else { + None + } + }).collect() +} + +pub fn unique_attr<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Option<&'a str> { + let mut curr = extract_attrs(attrs, attr, prop); + if curr.len() > 1 { + panic!("More than one property: {} found on variant", prop); + } + + curr.pop() +} + +pub fn is_disabled(attrs: &[Attribute]) -> bool { + let v = extract_attrs(attrs, "strum", "disabled"); + match v.len() { + 0 => false, + 1 => v[0] == "true", + _ => panic!("Can't have multiple values for 'disabled'"), + } +} \ No newline at end of file diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 3c2f175..c25861b 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -12,50 +12,16 @@ extern crate syn; extern crate quote; extern crate proc_macro; +mod helpers; +mod to_string; +mod from_string; +mod enum_iter; +mod enum_messages; +mod enum_properties; + use proc_macro::TokenStream; -use syn::Attribute; use std::env; -#[proc_macro_derive(EnumString,attributes(strum))] -pub fn from_string(input: TokenStream) -> TokenStream { - let s = input.to_string(); - let ast = syn::parse_derive_input(&s).unwrap(); - - let toks = from_string_inner(&ast); - debug_print_generated(&ast, &toks); - toks.parse().unwrap() -} - -#[proc_macro_derive(EnumIter,attributes(strum))] -pub fn enum_iter(input: TokenStream) -> TokenStream { - let s = input.to_string(); - let ast = syn::parse_derive_input(&s).unwrap(); - - let toks = enum_iter_inner(&ast); - debug_print_generated(&ast, &toks); - toks.parse().unwrap() -} - -#[proc_macro_derive(EnumMessage,attributes(strum))] -pub fn enum_messages(input: TokenStream) -> TokenStream { - let s = input.to_string(); - let ast = syn::parse_derive_input(&s).unwrap(); - - let toks = enum_message_inner(&ast); - debug_print_generated(&ast, &toks); - toks.parse().unwrap() -} - -#[proc_macro_derive(EnumProperty,attributes(strum))] -pub fn enum_properties(input: TokenStream) -> TokenStream { - let s = input.to_string(); - let ast = syn::parse_derive_input(&s).unwrap(); - - let toks = enum_properties_inner(&ast); - debug_print_generated(&ast, &toks); - toks.parse().unwrap() -} - fn debug_print_generated(ast: &syn::DeriveInput, toks: "e::Tokens) { let ident = ast.ident.as_ref(); let debug = env::var("STRUM_DEBUG"); @@ -70,418 +36,52 @@ fn debug_print_generated(ast: &syn::DeriveInput, toks: "e::Tokens) { } } -fn extract_attrs<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Vec<&'a str> { - attrs.iter() - // Get all the attributes with our tag on them. - .filter_map(|attribute| { - use syn::MetaItem::*; - if let List(ref i, ref nested) = attribute.value { - if i == attr { Some(nested) } else { None } - } else { - None - } - }) - .flat_map(|nested| nested) - // Get all the inner elements as long as they start with ser. - .filter_map(|attribute| { - use syn::NestedMetaItem::*; - use syn::MetaItem::*; - if let &MetaItem(NameValue(ref i, syn::Lit::Str(ref s, ..))) = attribute { - if i == prop { Some(&**s) } else { None } - } else { - None - } - }).collect() +#[proc_macro_derive(EnumString,attributes(strum))] +pub fn from_string(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + + let toks = from_string::from_string_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() } -fn unique_attr<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Option<&'a str> { - let mut curr = extract_attrs(attrs, attr, prop); - if curr.len() > 1 { - panic!("More than one property: {} found on variant", prop); - } +#[proc_macro_derive(ToString,attributes(strum))] +pub fn to_string(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); - curr.pop() + let toks = to_string::to_string_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() } -fn is_disabled(attrs: &[Attribute]) -> bool { - let v = extract_attrs(attrs, "strum", "disabled"); - match v.len() { - 0 => false, - 1 => v[0] == "true", - _ => panic!("Can't have multiple values for 'disabled'"), - } +#[proc_macro_derive(EnumIter,attributes(strum))] +pub fn enum_iter(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + + let toks = enum_iter::enum_iter_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() } -fn from_string_inner(ast: &syn::DeriveInput) -> quote::Tokens { - let name = &ast.ident; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.body { - syn::Body::Enum(ref v) => v, - _ => panic!("FromString only works on Enums"), - }; +#[proc_macro_derive(EnumMessage,attributes(strum))] +pub fn enum_messages(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); - let mut has_default = false; - let mut default = - quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) }; - let mut arms = Vec::new(); - for variant in variants { - use syn::VariantData::*; - let ident = &variant.ident; - - // Look at all the serialize attributes. - let mut attrs = extract_attrs(&variant.attrs, "strum", "serialize"); - if is_disabled(&variant.attrs) { - continue; - } - - if let Some("true") = unique_attr(&variant.attrs, "strum", "default") { - if has_default { - panic!("Can't have multiple default variants"); - } - - if let Tuple(ref fields) = variant.data { - if fields.len() != 1 { - panic!("Default only works on unit structs with a single String parameter"); - } - - default = quote!{ - default => ::std::result::Result::Ok(#name::#ident (default.into())) - }; - } else { - panic!("Default only works on unit structs with a single String parameter"); - } - - has_default = true; - continue; - } - - // If we don't have any custom variants, add the default name. - if attrs.len() == 0 { - attrs.push(ident.as_ref()); - } - - let params = match variant.data { - Unit => quote::Ident::from(""), - Tuple(ref fields) => { - let default = fields.iter() - .map(|_| "Default::default()") - .collect::>() - .join(", "); - - quote::Ident::from(&*format!("({})", default)) - } - Struct(ref fields) => { - let default = fields.iter() - .map(|field| { - format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()") - }) - .collect::>() - .join(", "); - - quote::Ident::from(&*format!("{{{}}}", default)) - } - }; - - arms.push(quote!{ #(#attrs)|* => ::std::result::Result::Ok(#name::#ident #params) }); - } - - arms.push(default); - - quote!{ - impl #impl_generics ::std::str::FromStr for #name #ty_generics #where_clause { - type Err = ::strum::ParseError; - fn from_str(s: &str) -> ::std::result::Result< #name #ty_generics , Self::Err> { - match s { - #(#arms),* - } - } - } - } + let toks = enum_messages::enum_message_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() } -fn enum_iter_inner(ast: &syn::DeriveInput) -> quote::Tokens { - let name = &ast.ident; - let gen = &ast.generics; - let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); - let vis = &ast.vis; +#[proc_macro_derive(EnumProperty,attributes(strum))] +pub fn enum_properties(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); - if gen.lifetimes.len() > 0 { - panic!("Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \ - be unbounded."); - } - - let phantom_data = if gen.ty_params.len() > 0 { - let g = gen.ty_params.iter().map(|param| ¶m.ident).collect::>(); - quote!{ < ( #(#g),* ) > } - } else { - quote! { < () > } - }; - - let variants = match ast.body { - syn::Body::Enum(ref v) => v, - _ => panic!("EnumIter only works on Enums"), - }; - - let mut arms = Vec::new(); - let enabled = variants.iter().filter(|variant| !is_disabled(&variant.attrs)); - - for (idx, variant) in enabled.enumerate() { - use syn::VariantData::*; - let ident = &variant.ident; - let params = match variant.data { - Unit => quote::Ident::from(""), - Tuple(ref fields) => { - let default = fields.iter() - .map(|_| "Default::default()") - .collect::>() - .join(", "); - - quote::Ident::from(&*format!("({})", default)) - } - Struct(ref fields) => { - let default = fields.iter() - .map(|field| { - format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()") - }) - .collect::>() - .join(", "); - - quote::Ident::from(&*format!("{{{}}}", default)) - } - }; - - arms.push(quote!{#idx => ::std::option::Option::Some(#name::#ident #params)}); - } - - arms.push(quote! { _ => ::std::option::Option::None }); - let iter_name = quote::Ident::from(&*format!("{}Iter", name)); - quote!{ - #vis struct #iter_name #ty_generics { - idx: usize, - marker: ::std::marker::PhantomData #phantom_data, - } - - impl #impl_generics strum::IntoEnumIterator for #name #ty_generics #where_clause { - type Iterator = #iter_name #ty_generics; - fn iter() -> #iter_name #ty_generics { - #iter_name { - idx:0, - marker: std::marker::PhantomData, - } - } - } - - impl #impl_generics Iterator for #iter_name #ty_generics #where_clause { - type Item = #name #ty_generics; - - fn next(&mut self) -> Option<#name #ty_generics> { - use std::default::Default; - let output = match self.idx { - #(#arms),* - }; - - self.idx += 1; - output - } - } - } -} - -fn enum_message_inner(ast: &syn::DeriveInput) -> quote::Tokens { - let name = &ast.ident; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.body { - syn::Body::Enum(ref v) => v, - _ => panic!("EnumMessage only works on Enums"), - }; - - let mut arms = Vec::new(); - let mut detailed_arms = Vec::new(); - let mut serializations = Vec::new(); - - for variant in variants { - let messages = unique_attr(&variant.attrs, "strum", "message"); - let detailed_messages = unique_attr(&variant.attrs, "strum", "detailed_message"); - let ident = &variant.ident; - - use syn::VariantData::*; - let params = match variant.data { - Unit => quote::Ident::from(""), - Tuple(..) => quote::Ident::from("(..)"), - Struct(..) => quote::Ident::from("{..}"), - }; - - // You can't disable getting the serializations. - { - let mut serialization_variants = extract_attrs(&variant.attrs, "strum", "serialize"); - if serialization_variants.len() == 0 { - serialization_variants.push(ident.as_ref()); - } - - let count = serialization_variants.len(); - serializations.push(quote!{ - &#name::#ident #params => { - static ARR: [&'static str; #count] = [#(#serialization_variants),*]; - &ARR - } - }); - } - - // But you can disable the messages. - if is_disabled(&variant.attrs) { - continue; - } - - if let Some(msg) = messages { - let params = params.clone(); - - // Push the simple message. - let tokens = quote!{ &#name::#ident #params => ::std::option::Option::Some(#msg) }; - arms.push(tokens.clone()); - - if detailed_messages.is_none() { - detailed_arms.push(tokens); - } - } - - if let Some(msg) = detailed_messages { - let params = params.clone(); - // Push the simple message. - detailed_arms.push(quote!{ &#name::#ident #params => ::std::option::Option::Some(#msg) }); - } - } - - if arms.len() < variants.len() { - arms.push(quote!{ _ => ::std::option::Option::None }); - } - - if detailed_arms.len() < variants.len() { - detailed_arms.push(quote!{ _ => ::std::option::Option::None }); - } - - quote!{ - impl #impl_generics ::strum::EnumMessage for #name #ty_generics #where_clause { - fn get_message(&self) -> ::std::option::Option<&str> { - match self { - #(#arms),* - } - } - - fn get_detailed_message(&self) -> ::std::option::Option<&str> { - match self { - #(#detailed_arms),* - } - } - - fn get_serializations(&self) -> &[&str] { - match self { - #(#serializations),* - } - } - } - } -} - -fn extract_properties(ast: &syn::Variant) -> Vec<(&syn::Ident, &syn::Lit)> { - use syn::*; - ast.attrs - .iter() - .filter_map(|attr| { - // Look for all the strum attributes - if let &Attribute { value: MetaItem::List(ref ident, ref nested), .. } = attr { - if ident == "strum" { - return Option::Some(nested); - } - } - - Option::None - }) - .flat_map(|prop| prop) - .filter_map(|prop| { - // Look for all the recursive property attributes - if let &NestedMetaItem::MetaItem(MetaItem::List(ref ident, ref nested)) = prop { - if ident == "props" { - return Option::Some(nested); - } - } - - Option::None - }) - .flat_map(|prop| prop) - .filter_map(|prop| { - // Only look at key value pairs - if let &NestedMetaItem::MetaItem(MetaItem::NameValue(ref ident, ref value)) = prop { - return Option::Some((ident, value)); - } - - Option::None - }) - .collect() -} - -fn enum_properties_inner(ast: &syn::DeriveInput) -> quote::Tokens { - let name = &ast.ident; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.body { - syn::Body::Enum(ref v) => v, - _ => panic!("EnumProp only works on Enums"), - }; - - let mut arms = Vec::new(); - for variant in variants { - let ident = &variant.ident; - 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 is_disabled(&variant.attrs) { - continue; - } - - use syn::VariantData::*; - let params = match variant.data { - Unit => quote::Ident::from(""), - Tuple(..) => quote::Ident::from("(..)"), - Struct(..) => quote::Ident::from("{..}"), - }; - - for (key, value) in extract_properties(&variant) { - use syn::Lit::*; - let key = key.as_ref(); - 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 )}), - _ => {} - } - } - - string_arms.push(quote!{ _ => ::std::option::Option::None }); - bool_arms.push(quote!{ _ => ::std::option::Option::None }); - num_arms.push(quote!{ _ => ::std::option::Option::None }); - - arms.push(quote!{ - &#name::#ident #params => { - match prop { - #(#string_arms),* - } - } - }); - } - - if arms.len() < variants.len() { - arms.push(quote!{ _ => ::std::option::Option::None }); - } - - quote!{ - impl #impl_generics ::strum::EnumProperty for #name #ty_generics #where_clause { - fn get_str(&self, prop: &str) -> ::std::option::Option<&'static str> { - match self { - #(#arms),* - } - } - } - } + let toks = enum_properties::enum_properties_inner(&ast); + debug_print_generated(&ast, &toks); + toks.parse().unwrap() } diff --git a/strum_macros/src/to_string.rs b/strum_macros/src/to_string.rs new file mode 100644 index 0000000..256cb90 --- /dev/null +++ b/strum_macros/src/to_string.rs @@ -0,0 +1,60 @@ + +use quote; +use syn; + +use helpers::{unique_attr, extract_attrs, is_disabled}; + +pub fn to_string_inner(ast: &syn::DeriveInput) -> quote::Tokens { + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let variants = match ast.body { + syn::Body::Enum(ref v) => v, + _ => panic!("ToString only works on Enums"), + }; + + let mut arms = Vec::new(); + for variant in variants { + use syn::VariantData::*; + let ident = &variant.ident; + + if is_disabled(&variant.attrs) { + continue; + } + + // Look at all the serialize attributes. + let output = if let Some(n) = unique_attr(&variant.attrs, "strum", "to_string") { + n + } else { + let mut attrs = extract_attrs(&variant.attrs, "strum", "serialize"); + // We always take the longest one. This is arbitary, but is *mostly* deterministic + attrs.sort_by_key(|s| -(s.len() as isize)); + if let Some(n) = attrs.first() { + n + } else { + ident.as_ref() + } + }; + + let params = match variant.data { + Unit => quote::Ident::from(""), + Tuple(..) => quote::Ident::from("(..)"), + Struct(..) => quote::Ident::from("{..}"), + }; + + arms.push(quote!{ #name::#ident #params => ::std::string::String::from(#output) }); + } + + if arms.len() < variants.len() { + arms.push(quote!{ _ => panic!("to_string() called on disabled variant.")}) + } + + quote!{ + impl #impl_generics ::std::string::ToString for #name #ty_generics #where_clause { + fn to_string(&self) -> ::std::string::String { + match *self { + #(#arms),* + } + } + } + } +} \ No newline at end of file diff --git a/strum_tests/src/main.rs b/strum_tests/src/main.rs new file mode 100644 index 0000000..aefed81 --- /dev/null +++ b/strum_tests/src/main.rs @@ -0,0 +1,20 @@ +extern crate strum; +#[macro_use] +extern crate strum_macros; + +#[allow(dead_code)] +#[derive(Debug,Eq,PartialEq,EnumString,ToString)] +enum Color { + #[strum(to_string="RedRed")] + Red, + #[strum(serialize="b", to_string="blue")] + Blue { hue: usize }, + #[strum(serialize="y",serialize="yellow")] + Yellow, + #[strum(disabled="true")] + Green(String), +} + +fn main() { + println!("Tests crate"); +} \ No newline at end of file diff --git a/strum_tests/tests/enum_iter.rs b/strum_tests/tests/enum_iter.rs index 3f503be..3c152d9 100644 --- a/strum_tests/tests/enum_iter.rs +++ b/strum_tests/tests/enum_iter.rs @@ -39,7 +39,9 @@ enum Complicated { #[test] fn complicated_test() { let results = Complicated::iter().collect::>(); - let expected = vec![Complicated::A(0), Complicated::B { v: String::new() }, Complicated::C]; + let expected = vec![Complicated::A(0), + Complicated::B { v: String::new() }, + Complicated::C]; assert_eq!(expected, results); } diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs new file mode 100644 index 0000000..2364971 --- /dev/null +++ b/strum_tests/tests/to_string.rs @@ -0,0 +1,40 @@ +extern crate strum; +#[macro_use] +extern crate strum_macros; + +use std::str::FromStr; +use std::string::ToString; + +#[derive(Debug,Eq,PartialEq,EnumString,ToString)] +enum Color { + #[strum(to_string="RedRed")] + Red, + #[strum(serialize="b", to_string="blue")] + Blue { hue: usize }, + #[strum(serialize="y", serialize="yellow")] + Yellow, + #[strum(default="true")] + Green(String), +} + +#[test] +fn color_simple() { + assert_eq!(Color::Red, Color::from_str("RedRed").unwrap()); +} + +#[test] +fn to_blue_string() { + assert_eq!(String::from("blue"), + (Color::Blue { hue: 0 }).to_string().as_ref()); +} + +#[test] +fn to_yellow_string() { + assert_eq!(String::from("yellow"), (Color::Yellow).to_string().as_ref()); +} + +#[test] +fn to_red_string() { + assert_eq!(Color::Red, + Color::from_str((Color::Red).to_string().as_ref()).unwrap()); +} From fbea713633d56ff18cac97f81f57d0abd9e66905 Mon Sep 17 00:00:00 2001 From: Peternator7 Date: Sat, 15 Apr 2017 18:54:49 -0700 Subject: [PATCH 2/3] Updated Version. Changed Readme --- README.md | 42 +++++++++++++++++++++++++++++++++++++---- strum/Cargo.toml | 4 ++-- strum/src/lib.rs | 41 ++++++++++++++++++++++++++++++++++++---- strum_macros/Cargo.toml | 2 +- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2773a1b..202d133 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ only dump the code generated on a type named `YourType`. Strum has implemented the following macros: 1. `EnumString`: auto-derives `std::str::FromStr` on the enum. Each variant of the enum will match on it's - own name. This can be overridden using `serialize="DifferentName"` on the attribute as shown below. + own name. This can be overridden using `serialize="DifferentName"` or `to_string="DifferentName"` + on the attribute as shown below. Multiple deserializations can be added to the same variant. If the variant contains additional data, they will be set to their default values upon deserialization. @@ -95,7 +96,40 @@ Strum has implemented the following macros: is potentially an expensive operation. If you do need that behavior, consider the more powerful Serde library for your serialization. -2. `EnumIter`: iterate over the variants of an Enum. Any additional data on your variants will be +2. `ToString`: prints out the given enum variant as a string. This enables you to perform round trip + style conversions from enum into string and back again for unit style variants. `ToString` chooses + which serialization to used based on the following criteria: + + 1. If there is a `to_string` property, this value will be used. There can only be one per variant. + 2. Of the various `serialize` properties, the value with the longest length is chosen. If that + behavior isn't desired, you should use `to_string`. + 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes. + + ```rust + // You need to bring the type into scope to use it!!! + use std::string::ToString; + + #[derive(ToString,Debug)] + enum Color { + #[strum(serialize="redred")] + Red, + Green { range:usize }, + Blue(usize), + Yellow, + } + + // It's simple to iterate over the variants of an enum. + fn debug_colors() { + let red = Color::Red; + assert_eq!(String::from("redred"), red.to_string()); + } + + fn main() { + debug_colors(); + } + ``` + +3. `EnumIter`: iterate over the variants of an Enum. Any additional data on your variants will be set to `Default::default()`. The macro implements `strum::IntoEnumIter` on your enum and creates a new type called `YourEnumIter` that is the iterator object. You cannot derive `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely @@ -125,7 +159,7 @@ Strum has implemented the following macros: } ``` -3. `EnumMessage`: encode strings into the enum itself. This macro implements +4. `EnumMessage`: encode strings into the enum itself. This macro implements the `strum::EnumMessage` trait. `EnumMessage` looks for `#[strum(message="...")]` attributes on your variants. You can also provided a `detailed_message="..."` attribute to create a @@ -187,7 +221,7 @@ Strum has implemented the following macros: ``` -4. `EnumProperty`: Enables the encoding of arbitary constants into enum variants. This method +5. `EnumProperty`: Enables the encoding of arbitary constants into enum variants. This method currently only supports adding additional string values. Other types of literals are still experimental in the rustc compiler. The generated code works by nesting match statements. The first match statement matches on the type of the enum, and the inner match statement diff --git a/strum/Cargo.toml b/strum/Cargo.toml index 7c4335c..8547bf5 100644 --- a/strum/Cargo.toml +++ b/strum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "strum" -version = "0.5.1" +version = "0.6.0" authors = ["Peter Glotfelty "] license = "MIT" @@ -13,7 +13,7 @@ homepage = "https://github.com/Peternator7/strum" readme = "../README.md" [dev-dependencies] -strum_macros = "0.5.0" +strum_macros = "0.6.0" # strum_macros = { path = "../strum_macros" } diff --git a/strum/src/lib.rs b/strum/src/lib.rs index c57a926..691416f 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -33,7 +33,7 @@ //! Strum has implemented the following macros: //! //! 1. `EnumString`: auto-derives `std::str::FromStr` on the enum. Each variant of the enum will match on it's -//! own name. This can be overridden using `serialize="DifferentName"` on the attribute as shown below. +//! own name. This can be overridden using `serialize="DifferentName"` or `to_string="DifferentName"`on the attribute as shown below. //! Multiple deserializations can be added to the same variant. If the variant contains additional data, //! they will be set to their default values upon deserialization. //! @@ -84,7 +84,40 @@ //! is potentially an expensive operation. If you do need that behavior, consider the more powerful //! Serde library for your serialization. //! -//! 2. `EnumIter`: iterate over the variants of an Enum. Any additional data on your variants will be +//! 2. `ToString`: prints out the given enum variant as a string. This enables you to perform round trip +//! style conversions from enum into string and back again for unit style variants. `ToString` chooses +//! which serialization to used based on the following criteria: +//! +//! 1. If there is a `to_string` property, this value will be used. There can only be one per variant. +//! 2. Of the various `serialize` properties, the value with the longest length is chosen. If that +//! behavior isn't desired, you should use `to_string`. +//! 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes. +//! +//! ```rust +//! # extern crate strum; +//! # #[macro_use] extern crate strum_macros; +//! // You need to bring the type into scope to use it!!! +//! use std::string::ToString; +//! +//! #[derive(ToString, Debug)] +//! enum Color { +//! #[strum(serialize="redred")] +//! Red, +//! Green { range:usize }, +//! Blue(usize), +//! Yellow, +//! } +//! +//! // It's simple to iterate over the variants of an enum. +//! fn debug_colors() { +//! let red = Color::Red; +//! assert_eq!(String::from("redred"), red.to_string()); +//! } +//! +//! fn main () { debug_colors(); } +//! ``` +//! +//! 3. `EnumIter`: iterate over the variants of an Enum. Any additional data on your variants will be //! set to `Default::default()`. The macro implements `strum::IntoEnumIter` on your enum and //! creates a new type called `YourEnumIter` that is the iterator object. You cannot derive //! `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely @@ -117,7 +150,7 @@ //! } //! ``` //! -//! 3. `EnumMessage`: encode strings into the enum itself. This macro implements +//! 4. `EnumMessage`: encode strings into the enum itself. This macro implements //! the `strum::EnumMessage` trait. `EnumMessage` looks for //! `#[strum(message="...")]` attributes on your variants. //! You can also provided a `detailed_message="..."` attribute to create a @@ -181,7 +214,7 @@ //! # fn main() {} //! ``` //! -//! 4. `EnumProperty`: Enables the encoding of arbitary constants into enum variants. This method +//! 5. `EnumProperty`: Enables the encoding of arbitary constants into enum variants. This method //! currently only supports adding additional string values. Other types of literals are still //! experimental in the rustc compiler. The generated code works by nesting match statements. //! The first match statement matches on the type of the enum, and the inner match statement diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index 81b0247..a46b41b 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "strum_macros" -version = "0.5.0" +version = "0.6.0" authors = ["Peter Glotfelty "] license = "MIT" From a82aaa1e0d421c00a8a317acdee676e10ab5fc8c Mon Sep 17 00:00:00 2001 From: Peternator7 Date: Sat, 15 Apr 2017 19:01:58 -0700 Subject: [PATCH 3/3] Fixed a link in the readme --- README.md | 5 ++++- strum_tests/tests/from_str.rs | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 202d133..f583a04 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Strum has implemented the following macros: set to `Default::default()`. The macro implements `strum::IntoEnumIter` on your enum and creates a new type called `YourEnumIter` that is the iterator object. You cannot derive `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely - create [unbounded lifetimes] (https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). + create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). ```rust // You need to bring the type into scope to use it!!! @@ -269,6 +269,9 @@ applied to a variant by adding #[strum(parameter="value")] to the variant. - `serialize="..."`: Changes the text that `FromStr()` looks for when parsing a string. This attribute can be applied multiple times to an element and the enum variant will be parsed if any of them match. +- `to_string="..."`: Similar to `serialize`. This value will be included when using `FromStr()`. More importantly, + this specifies what text to use when calling `variant.to_string()` with the `ToString` derivation. + - `default="true"`: Applied to a single variant of an enum. The variant must be a Tuple-like variant with a single piece of data that can be create from a `&str` i.e. `T: From<&str>`. The generated code will now return the variant with the input string captured as shown below diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs index 9969f22..9716021 100644 --- a/strum_tests/tests/from_str.rs +++ b/strum_tests/tests/from_str.rs @@ -12,6 +12,8 @@ enum Color { Yellow, #[strum(default="true")] Green(String), + #[strum(to_string="purp")] + Purple, } #[test] @@ -30,6 +32,11 @@ fn color_serialize() { assert_eq!(Color::Yellow, Color::from_str("yellow").unwrap()); } +#[test] +fn color_to_string() { + assert_eq!(Color::Purple, Color::from_str("purp").unwrap()); +} + #[test] fn color_default() { assert_eq!(Color::Green(String::from("not found")),