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:
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:
|
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
|
||||||
|
@ -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" }
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
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 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: "e::Tokens) {
|
fn debug_print_generated(ast: &syn::DeriveInput, toks: "e::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: "e::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| ¶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),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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]
|
#[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);
|
||||||
}
|
}
|
||||||
|
@ -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")),
|
||||||
|
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