1
0
mirror of https://github.com/danog/strum.git synced 2024-11-30 04:28:59 +01:00

Merge pull request #3 from Peternator7/features/tostring

Features/tostring
This commit is contained in:
Peter Glotfelty 2017-04-15 19:11:41 -07:00 committed by GitHub
commit 2fa1b869de
15 changed files with 704 additions and 457 deletions

View File

@ -45,7 +45,8 @@ only dump the code generated on a type named `YourType`.
Strum has implemented the following macros: 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 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, 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. they will be set to their default values upon deserialization.
@ -95,11 +96,44 @@ Strum has implemented the following macros:
is potentially an expensive operation. If you do need that behavior, consider the more powerful is potentially an expensive operation. If you do need that behavior, consider the more powerful
Serde library for your serialization. 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 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 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 `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 ```rust
// You need to bring the type into scope to use it!!! // You need to bring the type into scope to use it!!!
@ -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 the `strum::EnumMessage` trait. `EnumMessage` looks for
`#[strum(message="...")]` attributes on your variants. `#[strum(message="...")]` attributes on your variants.
You can also provided a `detailed_message="..."` attribute to create a 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 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. 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 The first match statement matches on the type of the enum, and the inner match statement
@ -235,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 - `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. 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 - `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>`. 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 The generated code will now return the variant with the input string captured as shown below

View File

@ -1,6 +1,6 @@
[package] [package]
name = "strum" name = "strum"
version = "0.5.1" version = "0.6.0"
authors = ["Peter Glotfelty <peglotfe@microsoft.com>"] authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
license = "MIT" license = "MIT"
@ -13,7 +13,7 @@ homepage = "https://github.com/Peternator7/strum"
readme = "../README.md" readme = "../README.md"
[dev-dependencies] [dev-dependencies]
strum_macros = "0.5.0" strum_macros = "0.6.0"
# strum_macros = { path = "../strum_macros" } # strum_macros = { path = "../strum_macros" }

View File

@ -33,7 +33,7 @@
//! Strum has implemented the following macros: //! 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 //! 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, //! 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. //! 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 //! is potentially an expensive operation. If you do need that behavior, consider the more powerful
//! Serde library for your serialization. //! 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 //! 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 //! 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 //! `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 //! the `strum::EnumMessage` trait. `EnumMessage` looks for
//! `#[strum(message="...")]` attributes on your variants. //! `#[strum(message="...")]` attributes on your variants.
//! You can also provided a `detailed_message="..."` attribute to create a //! You can also provided a `detailed_message="..."` attribute to create a
@ -181,7 +214,7 @@
//! # fn main() {} //! # 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 //! 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. //! 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 //! The first match statement matches on the type of the enum, and the inner match statement

View File

@ -1,6 +1,6 @@
[package] [package]
name = "strum_macros" name = "strum_macros"
version = "0.5.0" version = "0.6.0"
authors = ["Peter Glotfelty <peglotfe@microsoft.com>"] authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
license = "MIT" license = "MIT"

View File

@ -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| &param.ident)
.collect::<Vec<_>>();
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::<Vec<_>>()
.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::<Vec<_>>()
.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
}
}
}
}

View File

@ -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),*
}
}
}
}
}

View File

@ -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),*
}
}
}
}
}

View File

@ -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::<Vec<_>>()
.join(", ");
quote::Ident::from(&*format!("({})", default))
}
Struct(ref fields) => {
let default = fields
.iter()
.map(|field| {
format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()")
})
.collect::<Vec<_>>()
.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),*
}
}
}
}
}

View File

@ -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'"),
}
}

View File

@ -12,50 +12,16 @@ extern crate syn;
extern crate quote; extern crate quote;
extern crate proc_macro; 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 proc_macro::TokenStream;
use syn::Attribute;
use std::env; 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: &quote::Tokens) { fn debug_print_generated(ast: &syn::DeriveInput, toks: &quote::Tokens) {
let ident = ast.ident.as_ref(); let ident = ast.ident.as_ref();
let debug = env::var("STRUM_DEBUG"); let debug = env::var("STRUM_DEBUG");
@ -70,418 +36,52 @@ fn debug_print_generated(ast: &syn::DeriveInput, toks: &quote::Tokens) {
} }
} }
fn extract_attrs<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Vec<&'a str> { #[proc_macro_derive(EnumString,attributes(strum))]
attrs.iter() pub fn from_string(input: TokenStream) -> TokenStream {
// Get all the attributes with our tag on them. let s = input.to_string();
.filter_map(|attribute| { let ast = syn::parse_derive_input(&s).unwrap();
use syn::MetaItem::*;
if let List(ref i, ref nested) = attribute.value { let toks = from_string::from_string_inner(&ast);
if i == attr { Some(nested) } else { None } debug_print_generated(&ast, &toks);
} else { toks.parse().unwrap()
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()
} }
fn unique_attr<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Option<&'a str> { #[proc_macro_derive(ToString,attributes(strum))]
let mut curr = extract_attrs(attrs, attr, prop); pub fn to_string(input: TokenStream) -> TokenStream {
if curr.len() > 1 { let s = input.to_string();
panic!("More than one property: {} found on variant", prop); 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 { #[proc_macro_derive(EnumIter,attributes(strum))]
let v = extract_attrs(attrs, "strum", "disabled"); pub fn enum_iter(input: TokenStream) -> TokenStream {
match v.len() { let s = input.to_string();
0 => false, let ast = syn::parse_derive_input(&s).unwrap();
1 => v[0] == "true",
_ => panic!("Can't have multiple values for 'disabled'"), 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 { #[proc_macro_derive(EnumMessage,attributes(strum))]
let name = &ast.ident; pub fn enum_messages(input: TokenStream) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); let s = input.to_string();
let variants = match ast.body { let ast = syn::parse_derive_input(&s).unwrap();
syn::Body::Enum(ref v) => v,
_ => panic!("FromString only works on Enums"),
};
let mut has_default = false; let toks = enum_messages::enum_message_inner(&ast);
let mut default = debug_print_generated(&ast, &toks);
quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) }; toks.parse().unwrap()
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::<Vec<_>>()
.join(", ");
quote::Ident::from(&*format!("({})", default))
}
Struct(ref fields) => {
let default = fields.iter()
.map(|field| {
format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()")
})
.collect::<Vec<_>>()
.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),*
}
}
}
}
} }
fn enum_iter_inner(ast: &syn::DeriveInput) -> quote::Tokens { #[proc_macro_derive(EnumProperty,attributes(strum))]
let name = &ast.ident; pub fn enum_properties(input: TokenStream) -> TokenStream {
let gen = &ast.generics; let s = input.to_string();
let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); let ast = syn::parse_derive_input(&s).unwrap();
let vis = &ast.vis;
if gen.lifetimes.len() > 0 { let toks = enum_properties::enum_properties_inner(&ast);
panic!("Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \ debug_print_generated(&ast, &toks);
be unbounded."); toks.parse().unwrap()
}
let phantom_data = if gen.ty_params.len() > 0 {
let g = gen.ty_params.iter().map(|param| &param.ident).collect::<Vec<_>>();
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::<Vec<_>>()
.join(", ");
quote::Ident::from(&*format!("({})", default))
}
Struct(ref fields) => {
let default = fields.iter()
.map(|field| {
format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()")
})
.collect::<Vec<_>>()
.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),*
}
}
}
}
} }

View File

@ -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),*
}
}
}
}
}

20
strum_tests/src/main.rs Normal file
View File

@ -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");
}

View File

@ -39,7 +39,9 @@ enum Complicated<U: Default, V: Default> {
#[test] #[test]
fn complicated_test() { fn complicated_test() {
let results = Complicated::iter().collect::<Vec<_>>(); let results = Complicated::iter().collect::<Vec<_>>();
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); assert_eq!(expected, results);
} }

View File

@ -12,6 +12,8 @@ enum Color {
Yellow, Yellow,
#[strum(default="true")] #[strum(default="true")]
Green(String), Green(String),
#[strum(to_string="purp")]
Purple,
} }
#[test] #[test]
@ -30,6 +32,11 @@ fn color_serialize() {
assert_eq!(Color::Yellow, Color::from_str("yellow").unwrap()); assert_eq!(Color::Yellow, Color::from_str("yellow").unwrap());
} }
#[test]
fn color_to_string() {
assert_eq!(Color::Purple, Color::from_str("purp").unwrap());
}
#[test] #[test]
fn color_default() { fn color_default() {
assert_eq!(Color::Green(String::from("not found")), assert_eq!(Color::Green(String::from("not found")),

View File

@ -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());
}