diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..c9f539ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +## Unreleased + +### Added + +* Serialization case can be controlled using `#[strum(serialize_all = "snake_case")]`. ([#21][#21]) +* `#[derive(EnumDiscriminants)]` generates enum with variants without fields. ([#33][#33]) + +[#21]: https://github.com/Peternator7/strum/issues/21 +[#33]: https://github.com/Peternator7/strum/issues/33 + +## 0.10.0 + +### Added + +* Implemented `Clone` for `EnumIter`s. ([#18][#18]) +* Added `AsStaticRef` derive to allow enums to `impl AsStaticRef`. ([#23][#23]) + +### Fixed + +* `#[allow(missing_docs)]` on generated `EnumIter`s. ([#19][#19]) + +[#18]: https://github.com/Peternator7/strum/pull/18 +[#19]: https://github.com/Peternator7/strum/issues/19 +[#23]: https://github.com/Peternator7/strum/issues/23 diff --git a/README.md b/README.md index b7b63317..614b8101 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Cargo.toml. Strum_macros contains the macros needed to derive all the traits in ```toml [dependencies] -strum = "0.10.0" -strum_macros = "0.10.0" +strum = "0.11.0" +strum_macros = "0.11.0" ``` And add these lines to the root of your project, either lib.rs or main.rs. @@ -91,10 +91,10 @@ Strum has implemented the following macros: */ ``` - Note that the implementation of `FromStr` only matches on the name of the variant. - Strum, where possible, avoids operations that have an unknown runtime cost, and parsing strings - is potentially an expensive operation. If you do need that behavior, consider the more powerful - Serde library for your serialization. + Note that the implementation of `FromStr` by default only matches on the name of the + variant. There is an option to match on different case conversions through the + `#[strum(serialize_all = "snake_case")]` type attribute. See the **Additional Attributes** + Section for more information on using this feature. 2. `Display` / `ToString`: prints out the given enum. This enables you to perform round trip style conversions from enum into string and back again for unit style variants. `ToString` and @@ -296,10 +296,129 @@ Strum has implemented the following macros: } ``` +7. `EnumDiscriminants`: Given an enum named `MyEnum`, generates another enum called + `MyEnumDiscriminants` with the same variants, without any data fields. This is useful when you + wish to determine the variant of an enum from a String, but the variants contain any + non-`Default` fields. By default, the generated enum has the following derives: + `Clone, Copy, Debug, PartialEq, Eq`. You can add additional derives using the + `#[strum_discriminants(derive(AdditionalDerive))]` attribute. + + Here's an example: + + ```rust + extern crate strum; + #[macro_use] extern crate strum_macros; + + // Bring trait into scope + use std::str::FromStr; + + #[derive(Debug)] + struct NonDefault; + + #[allow(dead_code)] + #[derive(Debug, EnumDiscriminants)] + #[strum_discriminants(derive(EnumString))] + enum MyEnum { + Variant0(NonDefault), + Variant1 { a: NonDefault }, + } + + fn main() { + assert_eq!( + MyEnumDiscriminants::Variant0, + MyEnumDiscriminants::from_str("Variant0").unwrap() + ); + } + ``` + + You can also rename the generated enum using the `#[strum_discriminants(name(OtherName))]` + attribute: + + ```rust + extern crate strum; + #[macro_use] extern crate strum_macros; + // You need to bring the type into scope to use it!!! + use strum::IntoEnumIterator; + + #[allow(dead_code)] + #[derive(Debug, EnumDiscriminants)] + #[strum_discriminants(derive(EnumIter))] + #[strum_discriminants(name(MyVariants))] + enum MyEnum { + Variant0(bool), + Variant1 { a: bool }, + } + + fn main() { + assert_eq!( + vec![MyVariants::Variant0, MyVariants::Variant1], + MyVariants::iter().collect::>() + ); + } + ``` + + The derived enum also has the following trait implementations: + + * `impl From for MyEnumDiscriminants` + * `impl<'_enum> From<&'_enum MyEnum> for MyEnumDiscriminants` + + These allow you to get the *Discriminants* enum variant from the original enum: + + ```rust + extern crate strum; + #[macro_use] extern crate strum_macros; + + #[derive(Debug, EnumDiscriminants)] + #[strum_discriminants(name(MyVariants))] + enum MyEnum { + Variant0(bool), + Variant1 { a: bool }, + } + + fn main() { + assert_eq!(MyVariants::Variant0, MyEnum::Variant0(true).into()); + } + ``` + # Additional Attributes -Strum supports several custom attributes to modify the generated code. Custom attributes are -applied to a variant by adding #[strum(parameter="value")] to the variant. +Strum supports several custom attributes to modify the generated code. At the enum level, the +`#[strum(serialize_all = "snake_case")]` attribute can be used to change the case used when +serializing to and deserializing from strings: + +```rust +extern crate strum; +#[macro_use] +extern crate strum_macros; + +#[derive(Debug, Eq, PartialEq, ToString)] +#[strum(serialize_all = "snake_case")] +enum Brightness { + DarkBlack, + Dim { + glow: usize, + }, + #[strum(serialize = "bright")] + BrightWhite, +} + +fn main() { + assert_eq!( + String::from("dark_black"), + Brightness::DarkBlack.to_string().as_ref() + ); + assert_eq!( + String::from("dim"), + Brightness::Dim { glow: 0 }.to_string().as_ref() + ); + assert_eq!( + String::from("bright"), + Brightness::BrightWhite.to_string().as_ref() + ); +} +``` + +Custom attributes are 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. diff --git a/strum/Cargo.toml b/strum/Cargo.toml index 99aab30e..055929d0 100644 --- a/strum/Cargo.toml +++ b/strum/Cargo.toml @@ -13,8 +13,7 @@ homepage = "https://github.com/Peternator7/strum" readme = "../README.md" [dev-dependencies] -strum_macros = "0.10.0" -# strum_macros = { path = "../strum_macros" } +strum_macros = { path = "../strum_macros", version = "0.10.0" } [badges] travis-ci = { repository = "Peternator7/strum" } \ No newline at end of file diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 2c1388d3..81bcaf66 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -14,8 +14,8 @@ //! //! ```toml //! [dependencies] -//! strum = "0.9.0" -//! strum_macros = "0.9.0" +//! strum = "0.11.0" +//! strum_macros = "0.11.0" //! ``` //! //! And add these lines to the root of your project, either lib.rs or main.rs. @@ -79,10 +79,10 @@ //! # fn main() {} //! ``` //! -//! Note that the implementation of `FromStr` only matches on the name of the variant. -//! Strum, where possible, avoids operations that have an unknown runtime cost, and parsing strings -//! is potentially an expensive operation. If you do need that behavior, consider the more powerful -//! Serde library for your serialization. +//! Note that the implementation of `FromStr` by default only matches on the name of the +//! variant. There is an option to match on different case conversions through the +//! `#[strum(serialize_all = "snake_case")]` type attribute. See the **Additional Attributes** +//! Section for more information on using this feature. //! //! 2. `Display`, `ToString`: both derives print out the given enum variant. This enables you to perform round trip //! style conversions from enum into string and back again for unit style variants. `ToString` and `Display` @@ -124,14 +124,14 @@ //! a borrowed `str` instead of a `String` so you can save an allocation. //! //! 4. `AsStaticStr`: this is similar to `AsRefStr`, but returns a `'static` reference to a string which is helpful -//! in some scenarios. This macro implements `strum::AsStaticRef` which adds a method `.as_static()` that +//! in some scenarios. This macro implements `strum::AsStaticRef` which adds a method `.as_static()` that //! returns a `&'static str`. -//! +//! //! ```rust //! # extern crate strum; //! # #[macro_use] extern crate strum_macros; //! use strum::AsStaticRef; -//! +//! //! #[derive(AsStaticStr)] //! enum State<'a> { //! Initial(&'a str), @@ -143,14 +143,14 @@ //! // The following won't work because the lifetime is incorrect so we can use.as_static() instead. //! // let wrong: &'static str = state.as_ref(); //! let right: &'static str = state.as_static(); -//! println!("{}", right); +//! println!("{}", right); //! } -//! +//! //! fn main() { //! print_state(&"hello world".to_string()) //! } //! ``` -//! +//! //! 4. `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 implements both `Iterator` and `ExactSizeIterator`. @@ -290,11 +290,128 @@ //! } //! ``` //! +//! 7. `EnumDiscriminants`: Given an enum named `MyEnum`, generates another enum called +//! `MyEnumDiscriminants` with the same variants, without any data fields. This is useful when you +//! wish to determine the variant of an enum from a String, but the variants contain any +//! non-`Default` fields. By default, the generated enum has the following derives: +//! `Clone, Copy, Debug, PartialEq, Eq`. You can add additional derives using the +//! `#[strum_discriminants(derive(AdditionalDerive))]` attribute. +//! +//! Here's an example: +//! +//! ```rust +//! # extern crate strum; +//! # #[macro_use] extern crate strum_macros; +//! +//! // Bring trait into scope +//! use std::str::FromStr; +//! +//! #[derive(Debug)] +//! struct NonDefault; +//! +//! #[allow(dead_code)] +//! #[derive(Debug, EnumDiscriminants)] +//! #[strum_discriminants(derive(EnumString))] +//! enum MyEnum { +//! Variant0(NonDefault), +//! Variant1 { a: NonDefault }, +//! } +//! +//! fn main() { +//! assert_eq!( +//! MyEnumDiscriminants::Variant0, +//! MyEnumDiscriminants::from_str("Variant0").unwrap() +//! ); +//! } +//! ``` +//! +//! You can also rename the generated enum using the `#[strum_discriminants(name(OtherName))]` +//! attribute: +//! +//! ```rust +//! # extern crate strum; +//! # #[macro_use] extern crate strum_macros; +//! // You need to bring the type into scope to use it!!! +//! use strum::IntoEnumIterator; +//! +//! #[allow(dead_code)] +//! #[derive(Debug, EnumDiscriminants)] +//! #[strum_discriminants(name(MyVariants), derive(EnumIter))] +//! enum MyEnum { +//! Variant0(bool), +//! Variant1 { a: bool }, +//! } +//! +//! fn main() { +//! assert_eq!( +//! vec![MyVariants::Variant0, MyVariants::Variant1], +//! MyVariants::iter().collect::>() +//! ); +//! } +//! ``` +//! +//! The derived enum also has the following trait implementations: +//! +//! * `impl From for MyEnumDiscriminants` +//! * `impl<'_enum> From<&'_enum MyEnum> for MyEnumDiscriminants` +//! +//! These allow you to get the *Discriminants* enum variant from the original enum: +//! +//! ```rust +//! extern crate strum; +//! #[macro_use] extern crate strum_macros; +//! +//! #[derive(Debug, EnumDiscriminants)] +//! #[strum_discriminants(name(MyVariants))] +//! enum MyEnum { +//! Variant0(bool), +//! Variant1 { a: bool }, +//! } +//! +//! fn main() { +//! assert_eq!(MyVariants::Variant0, MyEnum::Variant0(true).into()); +//! } +//! ``` //! //! # Additional Attributes //! -//! Strum supports several custom attributes to modify the generated code. Custom attributes are -//! applied to a variant by adding #[strum(parameter="value")] to the variant. +//! Strum supports several custom attributes to modify the generated code. At the enum level, the +//! `#[strum(serialize_all = "snake_case")]` attribute can be used to change the case used when +//! serializing to and deserializing from strings: +//! +//! ```rust +//! extern crate strum; +//! #[macro_use] +//! extern crate strum_macros; +//! +//! #[derive(Debug, Eq, PartialEq, ToString)] +//! #[strum(serialize_all = "snake_case")] +//! enum Brightness { +//! DarkBlack, +//! Dim { +//! glow: usize, +//! }, +//! #[strum(serialize = "bright")] +//! BrightWhite, +//! } +//! +//! fn main() { +//! assert_eq!( +//! String::from("dark_black"), +//! Brightness::DarkBlack.to_string().as_ref() +//! ); +//! assert_eq!( +//! String::from("dim"), +//! Brightness::Dim { glow: 0 }.to_string().as_ref() +//! ); +//! assert_eq!( +//! String::from("bright"), +//! Brightness::BrightWhite.to_string().as_ref() +//! ); +//! } +//! ``` +//! +//! Custom attributes are 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. @@ -402,7 +519,7 @@ /// The ParseError enum is a collection of all the possible reasons /// an enum can fail to parse from a string. -#[derive(Debug,Clone,Copy,Eq,PartialEq,Hash)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum ParseError { VariantNotFound, } @@ -544,7 +661,6 @@ pub trait EnumProperty { } } - /// A cheap reference-to-reference conversion. Used to convert a value to a /// reference value with `'static` lifetime within generic code. pub trait AsStaticRef diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index be868d82..0af14fde 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -17,6 +17,7 @@ proc-macro = true name = "strum_macros" [dependencies] +heck = "0.3" proc-macro2 = "0.4" quote = "0.6" -syn = { version = "0.14", features = ["parsing"] } +syn = { version = "0.15", features = ["parsing", "extra-traits"] } diff --git a/strum_macros/src/as_ref_str.rs b/strum_macros/src/as_ref_str.rs index 09cbc081..e89ece58 100644 --- a/strum_macros/src/as_ref_str.rs +++ b/strum_macros/src/as_ref_str.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use syn; -use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled}; +use helpers::{extract_attrs, extract_meta, is_disabled, unique_attr}; fn get_arms(ast: &syn::DeriveInput) -> Vec { let name = &ast.ident; diff --git a/strum_macros/src/case_style.rs b/strum_macros/src/case_style.rs new file mode 100644 index 00000000..17acb17c --- /dev/null +++ b/strum_macros/src/case_style.rs @@ -0,0 +1,34 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CaseStyle { + CamelCase, + KebabCase, + MixedCase, + ShoutySnakeCase, + SnakeCase, + TitleCase, +} + +impl<'s> From<&'s str> for CaseStyle { + fn from(text: &'s str) -> CaseStyle { + match text { + "camel_case" => CaseStyle::CamelCase, + "kebab_case" => CaseStyle::KebabCase, + "mixed_case" => CaseStyle::MixedCase, + "shouty_snake_case" | "shouty_snek_case" => CaseStyle::ShoutySnakeCase, + "snake_case" | "snek_case" => CaseStyle::SnakeCase, + "title_case" => CaseStyle::TitleCase, + _ => panic!( + "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`", + text, + [ + "camel_case", + "kebab_case", + "mixed_case", + "shouty_snake_case", + "snake_case", + "title_case" + ] + ), + } + } +} diff --git a/strum_macros/src/display.rs b/strum_macros/src/display.rs index 340f4760..c0223bd1 100644 --- a/strum_macros/src/display.rs +++ b/strum_macros/src/display.rs @@ -1,7 +1,8 @@ use proc_macro2::TokenStream; use syn; -use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled}; +use case_style::CaseStyle; +use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr}; pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -11,6 +12,10 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { _ => panic!("Display only works on Enums"), }; + let type_meta = extract_meta(&ast.attrs); + let case_style = unique_attr(&type_meta, "strum", "serialize_all") + .map(|style| CaseStyle::from(style.as_ref())); + let mut arms = Vec::new(); for variant in variants { use syn::Fields::*; @@ -31,7 +36,7 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream { if let Some(n) = attrs.pop() { n } else { - ident.to_string() + convert_case(ident, case_style) } }; diff --git a/strum_macros/src/enum_discriminants.rs b/strum_macros/src/enum_discriminants.rs new file mode 100644 index 00000000..2108f679 --- /dev/null +++ b/strum_macros/src/enum_discriminants.rs @@ -0,0 +1,143 @@ +use proc_macro2::{Span, TokenStream}; +use syn; + +use helpers::{ + extract_list_metas, extract_meta, filter_metas, get_meta_ident, get_meta_list, unique_meta_list, +}; + +pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let vis = &ast.vis; + + let variants = match ast.data { + syn::Data::Enum(ref v) => &v.variants, + _ => panic!("EnumDiscriminants only works on Enums"), + }; + + // Derives for the generated enum + let type_meta = extract_meta(&ast.attrs); + let discriminant_attrs = get_meta_list(type_meta.iter(), "strum_discriminants") + .flat_map(|meta| extract_list_metas(meta).collect::>()) + .collect::>(); + let derives = get_meta_list(discriminant_attrs.iter().map(|&m| m), "derive") + .flat_map(extract_list_metas) + .filter_map(get_meta_ident) + .collect::>(); + + let derives = quote! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)] + }; + + // Work out the name + let default_name = syn::Ident::new( + &format!("{}Discriminants", name.to_string()), + Span::call_site(), + ); + + let discriminants_name = unique_meta_list(discriminant_attrs.iter().map(|&m| m), "name") + .map(extract_list_metas) + .and_then(|metas| metas.filter_map(get_meta_ident).next()) + .unwrap_or(&default_name); + + // Pass through all other attributes + let pass_though_attributes = + filter_metas(discriminant_attrs.iter().map(|&m| m), |meta| match meta { + syn::Meta::List(ref metalist) => metalist.ident != "derive" && metalist.ident != "name", + _ => true, + }).map(|meta| quote! { #[ #meta ] }) + .collect::>(); + + // Add the variants without fields, but exclude the `strum` meta item + let mut discriminants = Vec::new(); + for variant in variants { + let ident = &variant.ident; + + // Don't copy across the "strum" meta attribute. + let attrs = variant.attrs.iter().filter(|attr| { + attr.interpret_meta().map_or(true, |meta| match meta { + syn::Meta::List(ref metalist) => metalist.ident != "strum", + _ => true, + }) + }); + + discriminants.push(quote!{ #(#attrs)* #ident }); + } + + // Ideally: + // + // * For `Copy` types, we `impl From for TheEnumDiscriminants` + // * For `!Copy` types, we `impl<'enum> From<&'enum TheEnum> for TheEnumDiscriminants` + // + // That way we ensure users are not able to pass a `Copy` type by reference. However, the + // `#[derive(..)]` attributes are not in the parsed tokens, so we are not able to check if a + // type is `Copy`, so we just implement both. + // + // See + // --- + // let is_copy = unique_meta_list(type_meta.iter(), "derive") + // .map(extract_list_metas) + // .map(|metas| { + // metas + // .filter_map(get_meta_ident) + // .any(|derive| derive.to_string() == "Copy") + // }).unwrap_or(false); + + let arms = variants + .iter() + .map(|variant| { + let ident = &variant.ident; + + use syn::Fields::*; + let params = match variant.fields { + Unit => quote!{}, + Unnamed(ref _fields) => { + quote! { (..) } + } + Named(ref _fields) => { + quote! { { .. } } + } + }; + + quote! { #name::#ident #params => #discriminants_name::#ident } + }).collect::>(); + let from_fn_body = quote! { match val { #(#arms),* } }; + + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let impl_from = quote! { + impl #impl_generics ::std::convert::From< #name #ty_generics > for #discriminants_name #where_clause { + fn from(val: #name #ty_generics) -> #discriminants_name { + #from_fn_body + } + } + }; + let impl_from_ref = { + let mut generics = ast.generics.clone(); + + let lifetime = parse_quote!('_enum); + let enum_life = quote! { & #lifetime }; + generics.params.push(lifetime); + + // Shadows the earlier `impl_generics` + let (impl_generics, _, _) = generics.split_for_impl(); + + quote! { + impl #impl_generics ::std::convert::From< #enum_life #name #ty_generics > for #discriminants_name #where_clause { + fn from(val: #enum_life #name #ty_generics) -> #discriminants_name { + #from_fn_body + } + } + } + }; + + quote!{ + /// Auto-generated discriminant enum variants + #derives + #(#pass_though_attributes)* + #vis enum #discriminants_name { + #(#discriminants),* + } + + #impl_from + #impl_from_ref + } +} diff --git a/strum_macros/src/enum_iter.rs b/strum_macros/src/enum_iter.rs index 69fdec7e..1cb3af61 100644 --- a/strum_macros/src/enum_iter.rs +++ b/strum_macros/src/enum_iter.rs @@ -10,8 +10,10 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream { let vis = &ast.vis; if gen.lifetimes().count() > 0 { - panic!("Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \ - be unbounded."); + panic!( + "Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \ + be unbounded." + ); } let phantom_data = if gen.type_params().count() > 0 { @@ -42,7 +44,10 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream { quote! { (#(#defaults),*) } } Named(ref fields) => { - let fields = fields.named.iter().map(|field| field.ident.as_ref().unwrap()); + let fields = fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); quote! { {#(#fields: ::std::default::Default::default()),*} } } }; diff --git a/strum_macros/src/enum_messages.rs b/strum_macros/src/enum_messages.rs index feed3758..f380de69 100644 --- a/strum_macros/src/enum_messages.rs +++ b/strum_macros/src/enum_messages.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use syn; -use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled}; +use helpers::{extract_attrs, extract_meta, is_disabled, unique_attr}; pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; diff --git a/strum_macros/src/enum_properties.rs b/strum_macros/src/enum_properties.rs index b859ae0f..bc63ea95 100644 --- a/strum_macros/src/enum_properties.rs +++ b/strum_macros/src/enum_properties.rs @@ -8,35 +8,40 @@ fn extract_properties(meta: &[Meta]) -> Vec<(&syn::Ident, &syn::Lit)> { use syn::{MetaList, MetaNameValue, NestedMeta}; meta.iter() .filter_map(|meta| match *meta { - Meta::List(MetaList { ref ident, ref nested, .. }) => { + Meta::List(MetaList { + ref ident, + ref nested, + .. + }) => { if ident == "strum" { Some(nested) } else { None } - }, + } _ => None, - }) - .flat_map(|prop| prop) + }).flat_map(|prop| prop) .filter_map(|prop| match *prop { - NestedMeta::Meta(Meta::List(MetaList { ref ident, ref nested, .. })) => { + NestedMeta::Meta(Meta::List(MetaList { + ref ident, + ref nested, + .. + })) => { if ident == "props" { Some(nested) } else { None } - }, + } _ => None, - }) - .flat_map(|prop| prop) + }).flat_map(|prop| prop) // Only look at key value pairs .filter_map(|prop| match *prop { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref ident, ref lit, .. })) => { - Some((ident, lit)) - }, + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + ref ident, ref lit, .. + })) => Some((ident, lit)), _ => None, - }) - .collect() + }).collect() } pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream { diff --git a/strum_macros/src/from_string.rs b/strum_macros/src/from_string.rs index 95755792..a6883692 100644 --- a/strum_macros/src/from_string.rs +++ b/strum_macros/src/from_string.rs @@ -1,7 +1,8 @@ use proc_macro2::TokenStream; use syn; -use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled}; +use case_style::CaseStyle; +use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr}; pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -11,6 +12,10 @@ 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 = unique_attr(&type_meta, "strum", "serialize_all") + .map(|style| CaseStyle::from(style.as_ref())); + let mut has_default = false; let mut default = quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) }; @@ -48,20 +53,23 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream { continue; } - // If we don't have any custom variants, add the default name. + // If we don't have any custom variants, add the default serialized name. if attrs.len() == 0 { - attrs.push(ident.to_string()); + attrs.push(convert_case(ident, case_style)); } let params = match variant.fields { Unit => quote!{}, Unnamed(ref fields) => { - let defaults = ::std::iter::repeat(quote!(Default::default())) - .take(fields.unnamed.len()); + let defaults = + ::std::iter::repeat(quote!(Default::default())).take(fields.unnamed.len()); quote! { (#(#defaults),*) } } Named(ref fields) => { - let fields = fields.named.iter().map(|field| field.ident.as_ref().unwrap()); + let fields = fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); quote! { {#(#fields: Default::default()),*} } } }; diff --git a/strum_macros/src/helpers.rs b/strum_macros/src/helpers.rs index b56a6d2c..265500fe 100644 --- a/strum_macros/src/helpers.rs +++ b/strum_macros/src/helpers.rs @@ -1,12 +1,99 @@ +use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase}; +use syn::{Attribute, Ident, Meta, MetaList}; -use syn::{Attribute, Meta}; +use case_style::CaseStyle; pub fn extract_meta(attrs: &[Attribute]) -> Vec { - attrs.iter() + attrs + .iter() .filter_map(|attribute| attribute.interpret_meta()) .collect() } +pub fn filter_metas<'meta, MetaIt, F>(metas: MetaIt, filter: F) -> impl Iterator +where + MetaIt: Iterator, + F: Fn(&Meta) -> bool, +{ + metas.filter_map(move |meta| if filter(meta) { Some(meta) } else { None }) +} + +pub fn filter_meta_lists<'meta, MetaIt, F>( + metas: MetaIt, + filter: F, +) -> impl Iterator +where + MetaIt: Iterator, + F: Fn(&MetaList) -> bool, +{ + metas.filter_map(move |meta| match meta { + Meta::List(ref metalist) => { + if filter(metalist) { + Some(metalist) + } else { + None + } + } + _ => None, + }) +} + +/// Returns the `MetaList`s with the given attr name. +/// +/// For example, `get_meta_list(type_meta.iter(), "strum_discriminant")` for the following snippet +/// will return an iterator with `#[strum_discriminant(derive(EnumIter))]` and +/// `#[strum_discriminant(name(MyEnumVariants))]`. +/// +/// ```rust,ignore +/// #[derive(Debug)] +/// #[strum_discriminant(derive(EnumIter))] +/// #[strum_discriminant(name(MyEnumVariants))] +/// enum MyEnum { A } +/// ``` +pub fn get_meta_list<'meta, MetaIt>( + metas: MetaIt, + attr: &'meta str, +) -> impl Iterator +where + MetaIt: Iterator, +{ + filter_meta_lists(metas, move |metalist| metalist.ident == attr) +} + +/// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`. +/// +/// # Panics +/// +/// Panics if more than one `Meta` exists with the name. +pub fn unique_meta_list<'meta, MetaIt>(metas: MetaIt, attr: &'meta str) -> Option<&'meta MetaList> +where + MetaIt: Iterator, +{ + let mut curr = get_meta_list(metas.into_iter(), attr).collect::>(); + if curr.len() > 1 { + panic!("More than one `{}` attribute found on type", attr); + } + + curr.pop() +} + +/// Returns an iterator of the `Meta`s from the given `MetaList`. +pub fn extract_list_metas<'meta>(metalist: &'meta MetaList) -> impl Iterator { + use syn::NestedMeta; + metalist.nested.iter().filter_map(|nested| match *nested { + NestedMeta::Meta(ref meta) => Some(meta), + _ => None, + }) +} + +/// Returns the `Ident` of the `Meta::Word`, or `None`. +pub fn get_meta_ident<'meta>(meta: &'meta Meta) -> Option<&'meta Ident> { + match *meta { + Meta::Word(ref ident) => Some(ident), + _ => None, + } +} + pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec { use syn::{Lit, MetaNameValue, NestedMeta}; meta.iter() @@ -18,10 +105,9 @@ pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec { } else { None } - }, + } _ => None, - }) - .flat_map(|nested| nested) + }).flat_map(|nested| nested) // Get all the inner elements as long as they start with ser. .filter_map(|meta| match *meta { NestedMeta::Meta(Meta::NameValue(MetaNameValue { @@ -34,10 +120,9 @@ pub fn extract_attrs(meta: &[Meta], attr: &str, prop: &str) -> Vec { } else { None } - }, + } _ => None, - }) - .collect() + }).collect() } pub fn unique_attr(attrs: &[Meta], attr: &str, prop: &str) -> Option { @@ -57,3 +142,19 @@ pub fn is_disabled(attrs: &[Meta]) -> bool { _ => panic!("Can't have multiple values for 'disabled'"), } } + +pub fn convert_case(ident: &Ident, case_style: Option) -> String { + let ident_string = ident.to_string(); + if let Some(case_style) = case_style { + match case_style { + CaseStyle::CamelCase => ident_string.to_camel_case(), + CaseStyle::KebabCase => ident_string.to_kebab_case(), + CaseStyle::MixedCase => ident_string.to_mixed_case(), + CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(), + CaseStyle::SnakeCase => ident_string.to_snake_case(), + CaseStyle::TitleCase => ident_string.to_title_case(), + } + } else { + ident_string + } +} diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 7bb1b7a9..3003a645 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -7,25 +7,29 @@ //! //! The documentation for this crate is found in the `strum` crate. -#![recursion_limit="128"] +#![recursion_limit = "128"] +extern crate heck; +#[macro_use] extern crate syn; #[macro_use] extern crate quote; extern crate proc_macro; extern crate proc_macro2; -mod helpers; mod as_ref_str; +mod case_style; mod display; -mod to_string; -mod from_string; +mod enum_discriminants; mod enum_iter; mod enum_messages; mod enum_properties; +mod from_string; +mod helpers; +mod to_string; -use std::env; use proc_macro2::TokenStream; +use std::env; fn debug_print_generated(ast: &syn::DeriveInput, toks: &TokenStream) { let debug = env::var("STRUM_DEBUG"); @@ -40,7 +44,7 @@ fn debug_print_generated(ast: &syn::DeriveInput, toks: &TokenStream) { } } -#[proc_macro_derive(EnumString,attributes(strum))] +#[proc_macro_derive(EnumString, attributes(strum))] pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -49,7 +53,7 @@ pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(AsRefStr,attributes(strum))] +#[proc_macro_derive(AsRefStr, attributes(strum))] pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -58,7 +62,7 @@ pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(AsStaticStr,attributes(strum))] +#[proc_macro_derive(AsStaticStr, attributes(strum))] pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -67,7 +71,7 @@ pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream toks.into() } -#[proc_macro_derive(ToString,attributes(strum))] +#[proc_macro_derive(ToString, attributes(strum))] pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -76,7 +80,7 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(Display,attributes(strum))] +#[proc_macro_derive(Display, attributes(strum))] pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -85,7 +89,7 @@ pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(EnumIter,attributes(strum))] +#[proc_macro_derive(EnumIter, attributes(strum))] pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -94,7 +98,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(EnumMessage,attributes(strum))] +#[proc_macro_derive(EnumMessage, attributes(strum))] pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -103,7 +107,7 @@ pub fn enum_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream toks.into() } -#[proc_macro_derive(EnumProperty,attributes(strum))] +#[proc_macro_derive(EnumProperty, attributes(strum))] pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); @@ -111,3 +115,12 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea debug_print_generated(&ast, &toks); toks.into() } + +#[proc_macro_derive(EnumDiscriminants, attributes(strum, strum_discriminants))] +pub fn enum_discriminants(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse(input).unwrap(); + + let toks = enum_discriminants::enum_discriminants_inner(&ast); + debug_print_generated(&ast, &toks); + toks.into() +} diff --git a/strum_macros/src/to_string.rs b/strum_macros/src/to_string.rs index e8ee9963..a8a94a9a 100644 --- a/strum_macros/src/to_string.rs +++ b/strum_macros/src/to_string.rs @@ -1,7 +1,8 @@ use proc_macro2::TokenStream; use syn; -use helpers::{unique_attr, extract_attrs, extract_meta, is_disabled}; +use case_style::CaseStyle; +use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr}; pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; @@ -11,6 +12,10 @@ 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 = unique_attr(&type_meta, "strum", "serialize_all") + .map(|style| CaseStyle::from(style.as_ref())); + let mut arms = Vec::new(); for variant in variants { use syn::Fields::*; @@ -31,7 +36,7 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream { if let Some(n) = attrs.pop() { n } else { - ident.to_string() + convert_case(ident, case_style) } }; diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml index adddc944..eacb8bae 100644 --- a/strum_tests/Cargo.toml +++ b/strum_tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "strum_tests" -version = "0.9.1" +version = "0.10.0" authors = ["Peter Glotfelty "] [dependencies] diff --git a/strum_tests/src/main.rs b/strum_tests/src/main.rs index aefed814..67e2e360 100644 --- a/strum_tests/src/main.rs +++ b/strum_tests/src/main.rs @@ -3,18 +3,18 @@ extern crate strum; extern crate strum_macros; #[allow(dead_code)] -#[derive(Debug,Eq,PartialEq,EnumString,ToString)] +#[derive(Debug, Eq, PartialEq, EnumString, ToString)] enum Color { - #[strum(to_string="RedRed")] + #[strum(to_string = "RedRed")] Red, - #[strum(serialize="b", to_string="blue")] + #[strum(serialize = "b", to_string = "blue")] Blue { hue: usize }, - #[strum(serialize="y",serialize="yellow")] + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(disabled="true")] + #[strum(disabled = "true")] Green(String), } fn main() { println!("Tests crate"); -} \ No newline at end of file +} diff --git a/strum_tests/tests/as_ref_no_strum.rs b/strum_tests/tests/as_ref_no_strum.rs index 12d68f66..446a0824 100644 --- a/strum_tests/tests/as_ref_no_strum.rs +++ b/strum_tests/tests/as_ref_no_strum.rs @@ -1,15 +1,15 @@ #[macro_use] extern crate strum_macros; -#[derive(Debug,Eq,PartialEq,AsRefStr)] +#[derive(Debug, Eq, PartialEq, AsRefStr)] enum Color { - #[strum(to_string="RedRed")] + #[strum(to_string = "RedRed")] Red, - #[strum(serialize="b", to_string="blue")] + #[strum(serialize = "b", to_string = "blue")] Blue { hue: usize }, - #[strum(serialize="y", serialize="yellow")] + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default="true")] + #[strum(default = "true")] Green(String), } diff --git a/strum_tests/tests/as_ref_str.rs b/strum_tests/tests/as_ref_str.rs index 9db2a70a..c2b1e20d 100644 --- a/strum_tests/tests/as_ref_str.rs +++ b/strum_tests/tests/as_ref_str.rs @@ -5,15 +5,15 @@ extern crate strum_macros; use std::str::FromStr; use strum::AsStaticRef; -#[derive(Debug,Eq,PartialEq,EnumString,AsRefStr,AsStaticStr)] +#[derive(Debug, Eq, PartialEq, EnumString, AsRefStr, AsStaticStr)] enum Color { - #[strum(to_string="RedRed")] + #[strum(to_string = "RedRed")] Red, - #[strum(serialize="b", to_string="blue")] + #[strum(serialize = "b", to_string = "blue")] Blue { hue: usize }, - #[strum(serialize="y", serialize="yellow")] + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default="true")] + #[strum(default = "true")] Green(String), } diff --git a/strum_tests/tests/display.rs b/strum_tests/tests/display.rs index 000b0df6..3642f607 100644 --- a/strum_tests/tests/display.rs +++ b/strum_tests/tests/display.rs @@ -2,15 +2,15 @@ extern crate strum; #[macro_use] extern crate strum_macros; -#[derive(Debug,Eq,PartialEq, EnumString, Display)] +#[derive(Debug, Eq, PartialEq, EnumString, Display)] enum Color { - #[strum(to_string="RedRed")] + #[strum(to_string = "RedRed")] Red, - #[strum(serialize="b", to_string="blue")] + #[strum(serialize = "b", to_string = "blue")] Blue { hue: usize }, - #[strum(serialize="y", serialize="yellow")] + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default="true")] + #[strum(default = "true")] Green(String), } @@ -28,3 +28,30 @@ fn to_yellow_string() { fn to_red_string() { assert_eq!(String::from("RedRed"), format!("{}", Color::Red)); } + +#[derive(Display, Debug, Eq, PartialEq)] +#[strum(serialize_all = "snake_case")] +enum Brightness { + DarkBlack, + Dim { + glow: usize, + }, + #[strum(serialize = "bright")] + BrightWhite, +} + +#[test] +fn brightness_to_string() { + assert_eq!( + String::from("dark_black"), + Brightness::DarkBlack.to_string().as_ref() + ); + assert_eq!( + String::from("dim"), + Brightness::Dim { glow: 0 }.to_string().as_ref() + ); + assert_eq!( + String::from("bright"), + Brightness::BrightWhite.to_string().as_ref() + ); +} diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs new file mode 100644 index 00000000..e64a6687 --- /dev/null +++ b/strum_tests/tests/enum_discriminants.rs @@ -0,0 +1,186 @@ +extern crate strum; +#[macro_use] +extern crate strum_macros; + +use strum::IntoEnumIterator; + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants(derive(EnumIter))] +enum Simple { + Variant0, + Variant1, +} + +#[test] +fn simple_test() { + let discriminants = SimpleDiscriminants::iter().collect::>(); + let expected = vec![SimpleDiscriminants::Variant0, SimpleDiscriminants::Variant1]; + + assert_eq!(expected, discriminants); +} + +#[derive(Debug)] +struct NonDefault; + +#[allow(dead_code)] +#[derive(Debug, EnumDiscriminants)] +#[strum_discriminants(derive(EnumIter))] +enum WithFields { + Variant0(NonDefault), + Variant1 { a: NonDefault }, +} + +#[test] +fn fields_test() { + let discriminants = WithFieldsDiscriminants::iter().collect::>(); + let expected = vec![ + WithFieldsDiscriminants::Variant0, + WithFieldsDiscriminants::Variant1, + ]; + + assert_eq!(expected, discriminants); +} + +trait Foo {} +trait Bar {} + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants(derive(EnumIter))] +enum Complicated { + /// With Docs + A(U), + B { + v: V, + }, + C, +} + +#[test] +fn complicated_test() { + let discriminants = ComplicatedDiscriminants::iter().collect::>(); + let expected = vec![ + ComplicatedDiscriminants::A, + ComplicatedDiscriminants::B, + ComplicatedDiscriminants::C, + ]; + + assert_eq!(expected, discriminants); +} + +// This test exists to ensure that we do not copy across the `#[strum(default = "true")]` meta +// attribute. If we do without deriving any `strum` derivations on the generated discriminant enum, +// Rust will generate a compiler error saying it doesn't understand the `strum` attribute. +#[allow(dead_code)] +#[derive(Debug, EnumDiscriminants)] +enum WithDefault { + #[strum(default = "true")] + A(String), + B, +} + +#[test] +fn with_default_test() { + assert!(WithDefaultDiscriminants::A != WithDefaultDiscriminants::B); +} + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants(name(EnumBoo), derive(EnumIter))] +enum Renamed { + Variant0(bool), + Variant1(i32), +} + +#[test] +fn renamed_test() { + let discriminants = EnumBoo::iter().collect::>(); + let expected = vec![EnumBoo::Variant0, EnumBoo::Variant1]; + + assert_eq!(expected, discriminants); +} + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants(name(SplitAttributesBoo), derive(Display))] +#[strum_discriminants(derive(EnumIter))] +enum SplitAttributes { + Variant0(bool), + Variant1(i32), +} + +#[test] +fn split_attributes_test() { + let discriminants = SplitAttributesBoo::iter().collect::>(); + let expected = vec![SplitAttributesBoo::Variant0, SplitAttributesBoo::Variant1]; + + assert_eq!(expected, discriminants); + assert_eq!("Variant0", format!("{}", SplitAttributesBoo::Variant0)); +} + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants( + name(PassThroughBoo), + derive(Display, EnumIter, EnumString), + strum(serialize_all = "snake_case"), +)] +enum PassThrough { + DarkBlack(bool), + BrightWhite(i32), +} + +#[test] +fn arbitrary_attributes_pass_through() { + use std::str::FromStr; + + let discriminants = PassThroughBoo::iter().collect::>(); + let expected = vec![PassThroughBoo::DarkBlack, PassThroughBoo::BrightWhite]; + + assert_eq!(expected, discriminants); + assert_eq!("dark_black", PassThroughBoo::DarkBlack.to_string()); + assert_eq!( + PassThroughBoo::DarkBlack, + PassThroughBoo::from_str("dark_black").unwrap() + ); +} + +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +enum EnumInto { + A(bool), + B(i32), +} + +#[test] +fn from_test() { + assert_eq!(EnumIntoDiscriminants::A, EnumInto::A(true).into()); + assert_eq!(EnumIntoDiscriminants::B, EnumInto::B(1).into()); +} + +#[test] +fn from_ref_test() { + assert_eq!(EnumIntoDiscriminants::A, (&EnumInto::A(true)).into()); + assert_eq!(EnumIntoDiscriminants::B, (&EnumInto::B(1)).into()); +} + +#[derive(Debug)] +struct Rara; + +#[derive(Debug, Eq, PartialEq, EnumDiscriminants)] +#[strum_discriminants(name(EnumIntoComplexVars),)] +enum EnumIntoComplex<'a, T: 'a> { + A(&'a T), +} + +#[test] +fn from_test_complex() { + let rara = Rara; + assert_eq!(EnumIntoComplexVars::A, EnumIntoComplex::A(&rara).into()); +} + +#[test] +fn from_ref_test_complex() { + let rara = Rara; + assert_eq!(EnumIntoComplexVars::A, (&EnumIntoComplex::A(&rara)).into()); +} diff --git a/strum_tests/tests/enum_iter.rs b/strum_tests/tests/enum_iter.rs index bd66b3bd..e3cffa58 100644 --- a/strum_tests/tests/enum_iter.rs +++ b/strum_tests/tests/enum_iter.rs @@ -4,7 +4,7 @@ extern crate strum_macros; use strum::IntoEnumIterator; -#[derive(Debug,Eq,PartialEq,EnumIter)] +#[derive(Debug, Eq, PartialEq, EnumIter)] enum Week { Sunday, Monday, @@ -18,18 +18,20 @@ enum Week { #[test] fn simple_test() { let results = Week::iter().collect::>(); - let expected = vec![Week::Sunday, - Week::Monday, - Week::Tuesday, - Week::Wednesday, - Week::Thursday, - Week::Friday, - Week::Saturday]; + let expected = vec![ + Week::Sunday, + Week::Monday, + Week::Tuesday, + Week::Wednesday, + Week::Thursday, + Week::Friday, + Week::Saturday, + ]; assert_eq!(expected, results); } -#[derive(Debug,Eq,PartialEq,EnumIter)] +#[derive(Debug, Eq, PartialEq, EnumIter)] enum Complicated { A(U), B { v: V }, @@ -39,19 +41,21 @@ 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); } #[test] fn len_test() { - let mut i = Complicated::<(),()>::iter(); + let mut i = Complicated::<(), ()>::iter(); assert_eq!(3, i.len()); i.next(); - + assert_eq!(2, i.len()); i.next(); @@ -82,15 +86,17 @@ fn clone_test() { #[test] fn cycle_test() { let results = Week::iter().cycle().take(10).collect::>(); - let expected = vec![Week::Sunday, - Week::Monday, - Week::Tuesday, - Week::Wednesday, - Week::Thursday, - Week::Friday, - Week::Saturday, - Week::Sunday, - Week::Monday, - Week::Tuesday]; + let expected = vec![ + Week::Sunday, + Week::Monday, + Week::Tuesday, + Week::Wednesday, + Week::Thursday, + Week::Friday, + Week::Saturday, + Week::Sunday, + Week::Monday, + Week::Tuesday, + ]; assert_eq!(expected, results); } diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs index a0683d5e..4e5415b6 100644 --- a/strum_tests/tests/enum_message.rs +++ b/strum_tests/tests/enum_message.rs @@ -4,17 +4,17 @@ extern crate strum_macros; use strum::EnumMessage; -#[derive(Debug,Eq,PartialEq,EnumMessage)] +#[derive(Debug, Eq, PartialEq, EnumMessage)] enum Pets { - #[strum(message="I'm a dog")] + #[strum(message = "I'm a dog")] Dog, - #[strum(message="I'm a cat")] - #[strum(detailed_message="I'm a very exquisite striped cat")] + #[strum(message = "I'm a cat")] + #[strum(detailed_message = "I'm a very exquisite striped cat")] Cat, - #[strum(detailed_message="My fish is named Charles McFish")] + #[strum(detailed_message = "My fish is named Charles McFish")] Fish, Bird, - #[strum(disabled="true")] + #[strum(disabled = "true")] Hamster, } @@ -27,15 +27,19 @@ fn simple_message() { #[test] fn detailed_message() { assert_eq!("I'm a cat", (Pets::Cat).get_message().unwrap()); - assert_eq!("I'm a very exquisite striped cat", - (Pets::Cat).get_detailed_message().unwrap()); + assert_eq!( + "I'm a very exquisite striped cat", + (Pets::Cat).get_detailed_message().unwrap() + ); } #[test] fn only_detailed_message() { assert_eq!(None, (Pets::Fish).get_message()); - assert_eq!("My fish is named Charles McFish", - (Pets::Fish).get_detailed_message().unwrap()); + assert_eq!( + "My fish is named Charles McFish", + (Pets::Fish).get_detailed_message().unwrap() + ); } #[test] @@ -48,4 +52,4 @@ fn no_message() { fn disabled_message() { assert_eq!(None, (Pets::Hamster).get_message()); assert_eq!(None, (Pets::Hamster).get_detailed_message()); -} \ No newline at end of file +} diff --git a/strum_tests/tests/enum_props.rs b/strum_tests/tests/enum_props.rs index db361640..7af0aec0 100644 --- a/strum_tests/tests/enum_props.rs +++ b/strum_tests/tests/enum_props.rs @@ -6,7 +6,7 @@ use strum::EnumProperty; #[derive(Debug, EnumProperty)] enum Test { - #[strum(props(key="value"))] + #[strum(props(key = "value"))] A, B, } @@ -27,4 +27,4 @@ fn prop_test_not_found() { fn prop_test_not_found_2() { let b = Test::B; assert_eq!(None, b.get_str("key")); -} \ No newline at end of file +} diff --git a/strum_tests/tests/from_str.rs b/strum_tests/tests/from_str.rs index 9716021a..3203f19f 100644 --- a/strum_tests/tests/from_str.rs +++ b/strum_tests/tests/from_str.rs @@ -4,15 +4,17 @@ extern crate strum_macros; use std::str::FromStr; -#[derive(Debug,Eq,PartialEq,EnumString)] +#[derive(Debug, Eq, PartialEq, EnumString)] enum Color { Red, - Blue { hue: usize }, - #[strum(serialize="y",serialize="yellow")] + Blue { + hue: usize, + }, + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default="true")] + #[strum(default = "true")] Green(String), - #[strum(to_string="purp")] + #[strum(to_string = "purp")] Purple, } @@ -39,11 +41,40 @@ fn color_to_string() { #[test] fn color_default() { - assert_eq!(Color::Green(String::from("not found")), - Color::from_str("not found").unwrap()); + assert_eq!( + Color::Green(String::from("not found")), + Color::from_str("not found").unwrap() + ); } -#[derive(Debug,Eq,PartialEq,EnumString)] +#[derive(Debug, Eq, PartialEq, EnumString)] +#[strum(serialize_all = "snake_case")] +enum Brightness { + DarkBlack, + Dim { + glow: usize, + }, + #[strum(serialize = "Bright")] + BrightWhite, +} + +#[test] +fn brightness_serialize_all() { + assert_eq!( + Brightness::DarkBlack, + Brightness::from_str("dark_black").unwrap() + ); + assert_eq!( + Brightness::Dim { glow: 0 }, + Brightness::from_str("dim").unwrap() + ); + assert_eq!( + Brightness::BrightWhite, + Brightness::from_str("Bright").unwrap() + ); +} + +#[derive(Debug, Eq, PartialEq, EnumString)] enum Week { Sunday, Monday, @@ -56,8 +87,10 @@ enum Week { #[test] fn week_not_found() { - assert_eq!(Result::Err(::strum::ParseError::VariantNotFound), - Week::from_str("Humpday")); + assert_eq!( + Result::Err(::strum::ParseError::VariantNotFound), + Week::from_str("Humpday") + ); } #[test] @@ -71,7 +104,7 @@ fn week_found() { assert_eq!(Result::Ok(Week::Saturday), Week::from_str("Saturday")); } -#[derive(Debug,Eq,PartialEq,EnumString)] +#[derive(Debug, Eq, PartialEq, EnumString)] enum Lifetime<'a> { Life(&'a str), None, @@ -82,7 +115,7 @@ fn lifetime_test() { assert_eq!(Lifetime::Life(""), Lifetime::from_str("Life").unwrap()); } -#[derive(Debug,Eq,PartialEq,EnumString)] +#[derive(Debug, Eq, PartialEq, EnumString)] enum Generic { Gen(T), None, diff --git a/strum_tests/tests/to_string.rs b/strum_tests/tests/to_string.rs index e7db30ee..a345b6fa 100644 --- a/strum_tests/tests/to_string.rs +++ b/strum_tests/tests/to_string.rs @@ -5,22 +5,24 @@ extern crate strum_macros; use std::str::FromStr; use std::string::ToString; -#[derive(Debug,Eq,PartialEq, EnumString, ToString)] +#[derive(Debug, Eq, PartialEq, EnumString, ToString)] enum Color { - #[strum(to_string="RedRed")] + #[strum(to_string = "RedRed")] Red, - #[strum(serialize="b", to_string="blue")] + #[strum(serialize = "b", to_string = "blue")] Blue { hue: usize }, - #[strum(serialize="y", serialize="yellow")] + #[strum(serialize = "y", serialize = "yellow")] Yellow, - #[strum(default="true")] + #[strum(default = "true")] Green(String), } #[test] fn to_blue_string() { - assert_eq!(String::from("blue"), - (Color::Blue { hue: 0 }).to_string().as_ref()); + assert_eq!( + String::from("blue"), + (Color::Blue { hue: 0 }).to_string().as_ref() + ); } #[test] @@ -31,6 +33,35 @@ fn to_yellow_string() { #[test] fn to_red_string() { assert_eq!(String::from("RedRed"), (Color::Red).to_string()); - assert_eq!(Color::Red, - Color::from_str((Color::Red).to_string().as_ref()).unwrap()); + assert_eq!( + Color::Red, + Color::from_str((Color::Red).to_string().as_ref()).unwrap() + ); +} + +#[derive(Debug, Eq, PartialEq, ToString)] +#[strum(serialize_all = "snake_case")] +enum Brightness { + DarkBlack, + Dim { + glow: usize, + }, + #[strum(serialize = "bright")] + BrightWhite, +} + +#[test] +fn brightness_to_string() { + assert_eq!( + String::from("dark_black"), + Brightness::DarkBlack.to_string().as_ref() + ); + assert_eq!( + String::from("dim"), + Brightness::Dim { glow: 0 }.to_string().as_ref() + ); + assert_eq!( + String::from("bright"), + Brightness::BrightWhite.to_string().as_ref() + ); }