mirror of
https://github.com/danog/strum.git
synced 2024-11-26 20:14:40 +01:00
Merge pull request #3 from Peternator7/features/tostring
Features/tostring
This commit is contained in:
commit
2fa1b869de
47
README.md
47
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,11 +96,44 @@ 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
|
||||
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!!!
|
||||
@ -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
|
||||
@ -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
|
||||
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
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "strum"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
|
||||
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" }
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "strum_macros"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
|
||||
license = "MIT"
|
||||
|
||||
|
100
strum_macros/src/enum_iter.rs
Normal file
100
strum_macros/src/enum_iter.rs
Normal 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| ¶m.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
strum_macros/src/enum_messages.rs
Normal file
100
strum_macros/src/enum_messages.rs
Normal 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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
strum_macros/src/enum_properties.rs
Normal file
108
strum_macros/src/enum_properties.rs
Normal 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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
95
strum_macros/src/from_string.rs
Normal file
95
strum_macros/src/from_string.rs
Normal 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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
strum_macros/src/helpers.rs
Normal file
45
strum_macros/src/helpers.rs
Normal 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'"),
|
||||
}
|
||||
}
|
@ -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::<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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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::<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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let toks = enum_properties::enum_properties_inner(&ast);
|
||||
debug_print_generated(&ast, &toks);
|
||||
toks.parse().unwrap()
|
||||
}
|
||||
|
60
strum_macros/src/to_string.rs
Normal file
60
strum_macros/src/to_string.rs
Normal 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
20
strum_tests/src/main.rs
Normal 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");
|
||||
}
|
@ -39,7 +39,9 @@ enum Complicated<U: Default, V: Default> {
|
||||
#[test]
|
||||
fn complicated_test() {
|
||||
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);
|
||||
}
|
||||
|
@ -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")),
|
||||
|
40
strum_tests/tests/to_string.rs
Normal file
40
strum_tests/tests/to_string.rs
Normal 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());
|
||||
}
|
Loading…
Reference in New Issue
Block a user