mirror of
https://github.com/danog/strum.git
synced 2024-12-02 09:27:57 +01:00
Merge branch 'master' into peternator7/0.19
This commit is contained in:
commit
f997ee6c86
21
CHANGELOG.md
21
CHANGELOG.md
@ -8,6 +8,27 @@
|
||||
* **Breaking Change**: Use Associated Constant for EnumCount instead of const fn and free constant. [#99](https://github.com/Peternator7/strum/pull/99)
|
||||
* **Breaking Change**: EnumVariantNames now properly adjusts to the `to_string` and `serialize` attributes.
|
||||
This behavior is consistent with the other derives.
|
||||
* **Breaking Change**. `default` and `disabled` should now be written as markers instead of key value pairs.
|
||||
Here is the old way of adding these attributes to a variant.
|
||||
```rust
|
||||
// OLD WAY
|
||||
enum Test {
|
||||
#[strum(disabled = "true", default = "true")]
|
||||
Variant(String)
|
||||
}
|
||||
```
|
||||
|
||||
Here is the new way. There is less ambiguity in the new syntax.
|
||||
|
||||
```rust
|
||||
enum Test {
|
||||
#[strum(disabled, default)]
|
||||
Variant(String)
|
||||
}
|
||||
```
|
||||
* **Breaking Change**. Most of the strum plugins will now error more aggresively on invalid options being
|
||||
used. Historically, the plugins have ignore invalid options, but most of these should error now. Silent
|
||||
errors are a rust anti-pattern.
|
||||
|
||||
## 0.18.0
|
||||
|
||||
|
@ -192,12 +192,12 @@ pub trait EnumMessage {
|
||||
/// }
|
||||
/// ```
|
||||
pub trait EnumProperty {
|
||||
fn get_str(&self, &str) -> Option<&'static str>;
|
||||
fn get_int(&self, &str) -> Option<usize> {
|
||||
fn get_str(&self, prop: &str) -> Option<&'static str>;
|
||||
fn get_int(&self, _prop: &str) -> Option<usize> {
|
||||
Option::None
|
||||
}
|
||||
|
||||
fn get_bool(&self, &str) -> Option<bool> {
|
||||
fn get_bool(&self, _prop: &str) -> Option<bool> {
|
||||
Option::None
|
||||
}
|
||||
}
|
||||
@ -215,7 +215,7 @@ where
|
||||
/// A trait for capturing the number of variants in Enum. This trait can be autoderived by
|
||||
/// `strum_macros`.
|
||||
pub trait EnumCount {
|
||||
fn count() -> usize;
|
||||
const COUNT: usize;
|
||||
}
|
||||
|
||||
/// A trait for retrieving the names of each variant in Enum. This trait can
|
||||
|
@ -10,6 +10,7 @@ categories = ["development-tools::procedural-macro-helpers", "parsing"]
|
||||
|
||||
documentation = "https://docs.rs/strum"
|
||||
homepage = "https://github.com/Peternator7/strum"
|
||||
repository = "https://github.com/Peternator7/strum"
|
||||
readme = "../README.md"
|
||||
|
||||
[lib]
|
||||
|
43
strum_macros/src/helpers/has_metadata.rs
Normal file
43
strum_macros/src/helpers/has_metadata.rs
Normal file
@ -0,0 +1,43 @@
|
||||
///Represents a type that can have strum metadata associated with it.
|
||||
pub trait HasMetadata {
|
||||
/// Get all the metadata associated with a specific "tag".
|
||||
/// All of strum's metadata is nested inside a path such as
|
||||
/// #[strum(...)] so this let's us quickly filter down to only our metadata.
|
||||
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta>;
|
||||
}
|
||||
|
||||
fn get_metadata_inner<'a>(
|
||||
ident: &str,
|
||||
it: impl IntoIterator<Item = &'a syn::Attribute>,
|
||||
) -> Vec<syn::Meta> {
|
||||
it.into_iter()
|
||||
.map(|attr| attr.parse_meta().unwrap())
|
||||
.filter_map(|meta| match meta {
|
||||
syn::Meta::List(syn::MetaList { path, nested, .. }) => {
|
||||
if path.is_ident(ident) {
|
||||
Some(nested)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flat_map(|id| id)
|
||||
.map(|nested| match nested {
|
||||
syn::NestedMeta::Meta(meta) => meta,
|
||||
_ => panic!("unexpected literal parsing strum attributes"),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl HasMetadata for syn::Variant {
|
||||
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta> {
|
||||
get_metadata_inner(ident, &self.attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasMetadata for syn::DeriveInput {
|
||||
fn get_metadata(&self, ident: &str) -> Vec<syn::Meta> {
|
||||
get_metadata_inner(ident, &self.attrs)
|
||||
}
|
||||
}
|
@ -1,30 +1,63 @@
|
||||
use syn::{Meta, MetaList};
|
||||
use syn::{Meta, MetaList, NestedMeta};
|
||||
|
||||
pub trait MetaHelpers {
|
||||
fn try_metalist(&self) -> Option<&MetaList>;
|
||||
fn try_path(&self) -> Option<&syn::Path>;
|
||||
fn try_namevalue(&self) -> Option<&syn::MetaNameValue>;
|
||||
fn expect_metalist(&self, msg: &str) -> &MetaList;
|
||||
fn expect_path(&self, msg: &str) -> &syn::Path;
|
||||
fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue;
|
||||
}
|
||||
|
||||
impl MetaHelpers for syn::Meta {
|
||||
fn try_metalist(&self) -> Option<&MetaList> {
|
||||
fn expect_metalist(&self, msg: &str) -> &MetaList {
|
||||
match self {
|
||||
Meta::List(list) => Some(list),
|
||||
_ => None,
|
||||
Meta::List(list) => list,
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_path(&self) -> Option<&syn::Path> {
|
||||
fn expect_path(&self, msg: &str) -> &syn::Path {
|
||||
match self {
|
||||
Meta::Path(path) => Some(path),
|
||||
_ => None,
|
||||
Meta::Path(path) => path,
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_namevalue(&self) -> Option<&syn::MetaNameValue> {
|
||||
fn expect_namevalue(&self, msg: &str) -> &syn::MetaNameValue {
|
||||
match self {
|
||||
Meta::NameValue(pair) => Some(pair),
|
||||
_ => None,
|
||||
Meta::NameValue(pair) => pair,
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NestedMetaHelpers {
|
||||
fn expect_meta(&self, msg: &str) -> &syn::Meta;
|
||||
fn expect_lit(&self, msg: &str) -> &syn::Lit;
|
||||
}
|
||||
|
||||
impl NestedMetaHelpers for NestedMeta {
|
||||
fn expect_meta(&self, msg: &str) -> &Meta {
|
||||
match self {
|
||||
syn::NestedMeta::Meta(m) => m,
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
fn expect_lit(&self, msg: &str) -> &syn::Lit {
|
||||
match self {
|
||||
syn::NestedMeta::Lit(l) => l,
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LitHelpers {
|
||||
fn expect_string(&self, msg: &str) -> String;
|
||||
}
|
||||
|
||||
impl LitHelpers for syn::Lit {
|
||||
fn expect_string(&self, msg: &str) -> String {
|
||||
match self {
|
||||
syn::Lit::Str(s) => s.value(),
|
||||
_ => panic!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
use super::MetaHelpers;
|
||||
use super::MetaListHelpers;
|
||||
use syn::Meta;
|
||||
|
||||
pub trait MetaIteratorHelpers {
|
||||
fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta>;
|
||||
fn find_properties(&self, attr: &str, prop: &str) -> Vec<String>;
|
||||
|
||||
fn find_unique_property(&self, attr: &str, prop: &str) -> Option<String> {
|
||||
let mut curr = self.find_properties(attr, prop);
|
||||
if curr.len() > 1 {
|
||||
panic!("More than one property: {} found on variant", prop);
|
||||
}
|
||||
|
||||
curr.pop()
|
||||
}
|
||||
|
||||
fn is_disabled(&self) -> bool {
|
||||
let v = self.find_properties("strum", "disabled");
|
||||
match v.len() {
|
||||
0 => false,
|
||||
1 => v[0] == "true",
|
||||
_ => panic!("Can't have multiple values for 'disabled'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//impl MetaIteratorHelpers for [Meta]
|
||||
impl<T> MetaIteratorHelpers for [T]
|
||||
where
|
||||
T: std::borrow::Borrow<Meta>,
|
||||
{
|
||||
fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta> {
|
||||
self.iter()
|
||||
.filter_map(|meta| meta.borrow().try_metalist())
|
||||
.filter(|list| list.path.is_ident(attr))
|
||||
.flat_map(|list| list.expand_inner())
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
fn find_properties(&self, attr: &str, prop: &str) -> Vec<String> {
|
||||
use syn::{Lit, MetaNameValue};
|
||||
self.iter()
|
||||
// Only look at MetaList style attributes `[strum(...)]`
|
||||
.filter_map(|meta| meta.borrow().try_metalist())
|
||||
.filter(|list| list.path.is_ident(attr))
|
||||
.flat_map(|list| list.expand_inner())
|
||||
// Match all the properties with a given ident `[strum(serialize = "value")]`
|
||||
.filter_map(|meta| match *meta {
|
||||
Meta::NameValue(MetaNameValue {
|
||||
ref path,
|
||||
lit: Lit::Str(ref s),
|
||||
..
|
||||
}) => {
|
||||
if path.is_ident(prop) {
|
||||
Some(s.value())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
use syn::Meta;
|
||||
|
||||
pub trait MetaListHelpers {
|
||||
fn expand_inner(&self) -> Vec<&Meta>;
|
||||
}
|
||||
|
||||
impl MetaListHelpers for syn::MetaList {
|
||||
fn expand_inner(&self) -> Vec<&Meta> {
|
||||
use syn::NestedMeta;
|
||||
self.nested
|
||||
.iter()
|
||||
.filter_map(|nested| match *nested {
|
||||
NestedMeta::Meta(ref meta) => Some(meta),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,18 +1,11 @@
|
||||
use syn::{Attribute, Meta};
|
||||
pub use self::case_style::CaseStyleHelpers;
|
||||
pub use self::meta_helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers};
|
||||
pub use self::type_props::HasTypeProperties;
|
||||
pub use self::variant_props::HasStrumVariantProperties;
|
||||
|
||||
pub mod case_style;
|
||||
pub mod type_props;
|
||||
pub mod variant_props;
|
||||
mod has_metadata;
|
||||
mod meta_helpers;
|
||||
mod meta_iterator_helpers;
|
||||
mod metalist_helpers;
|
||||
|
||||
pub use self::case_style::CaseStyleHelpers;
|
||||
pub use self::meta_helpers::MetaHelpers;
|
||||
pub use self::meta_iterator_helpers::MetaIteratorHelpers;
|
||||
pub use self::metalist_helpers::MetaListHelpers;
|
||||
|
||||
pub fn extract_meta(attrs: &[Attribute]) -> Vec<Meta> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|attribute| attribute.parse_meta().ok())
|
||||
.collect()
|
||||
}
|
||||
|
86
strum_macros/src/helpers/type_props.rs
Normal file
86
strum_macros/src/helpers/type_props.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use std::convert::From;
|
||||
use std::default::Default;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use crate::helpers::has_metadata::HasMetadata;
|
||||
use crate::helpers::{MetaHelpers, NestedMetaHelpers};
|
||||
|
||||
pub trait HasTypeProperties {
|
||||
fn get_type_properties(&self) -> StrumTypeProperties;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct StrumTypeProperties {
|
||||
pub case_style: Option<CaseStyle>,
|
||||
pub discriminant_derives: Vec<syn::Path>,
|
||||
pub discriminant_name: Option<syn::Path>,
|
||||
pub discriminant_others: Vec<syn::Meta>,
|
||||
}
|
||||
|
||||
impl HasTypeProperties for syn::DeriveInput {
|
||||
fn get_type_properties(&self) -> StrumTypeProperties {
|
||||
let mut output = StrumTypeProperties::default();
|
||||
|
||||
let strum_meta = self.get_metadata("strum");
|
||||
let discriminants_meta = self.get_metadata("strum_discriminants");
|
||||
|
||||
for meta in strum_meta {
|
||||
let meta = match meta {
|
||||
syn::Meta::NameValue(mv) => mv,
|
||||
_ => panic!("strum on types only supports key-values"),
|
||||
};
|
||||
|
||||
if meta.path.is_ident("serialize_all") {
|
||||
let style = match meta.lit {
|
||||
syn::Lit::Str(s) => s.value(),
|
||||
_ => panic!("expected string value for 'serialize_all'"),
|
||||
};
|
||||
|
||||
if output.case_style.is_some() {
|
||||
panic!("found multiple values of serialize_all");
|
||||
}
|
||||
|
||||
output.case_style = Some(CaseStyle::from(&*style));
|
||||
} else {
|
||||
panic!("unrecognized attribue found on strum(..)");
|
||||
}
|
||||
}
|
||||
|
||||
for meta in discriminants_meta {
|
||||
match meta {
|
||||
syn::Meta::List(ref ls) => {
|
||||
if ls.path.is_ident("derive") {
|
||||
let paths = ls
|
||||
.nested
|
||||
.iter()
|
||||
.map(|meta| meta.expect_meta("unexpected literal").path().clone());
|
||||
|
||||
output.discriminant_derives.extend(paths);
|
||||
} else if ls.path.is_ident("name") {
|
||||
if ls.nested.len() != 1 {
|
||||
panic!("name expects exactly 1 value");
|
||||
}
|
||||
|
||||
let value = ls.nested.first().expect("unexpected error");
|
||||
let name = value
|
||||
.expect_meta("unexpected literal")
|
||||
.expect_path("name must be an identifier");
|
||||
|
||||
if output.discriminant_name.is_some() {
|
||||
panic!("multiple occurrences of 'name'");
|
||||
}
|
||||
|
||||
output.discriminant_name = Some(name.clone());
|
||||
} else {
|
||||
output.discriminant_others.push(meta);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
output.discriminant_others.push(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
123
strum_macros/src/helpers/variant_props.rs
Normal file
123
strum_macros/src/helpers/variant_props.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use std::collections::HashMap;
|
||||
use std::default::Default;
|
||||
|
||||
use crate::helpers::case_style::{CaseStyle, CaseStyleHelpers};
|
||||
use crate::helpers::has_metadata::HasMetadata;
|
||||
use crate::helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers};
|
||||
|
||||
pub trait HasStrumVariantProperties {
|
||||
fn get_variant_properties(&self) -> StrumVariantProperties;
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default)]
|
||||
pub struct StrumVariantProperties {
|
||||
pub is_disabled: bool,
|
||||
pub default: bool,
|
||||
pub message: Option<String>,
|
||||
pub detailed_message: Option<String>,
|
||||
pub string_props: HashMap<String, String>,
|
||||
serialize: Vec<String>,
|
||||
to_string: Option<String>,
|
||||
ident: Option<syn::Ident>,
|
||||
}
|
||||
|
||||
impl StrumVariantProperties {
|
||||
pub fn get_preferred_name(&self, case_style: Option<CaseStyle>) -> String {
|
||||
if let Some(ref to_string) = self.to_string {
|
||||
to_string.clone()
|
||||
} else {
|
||||
let mut serialized = self.serialize.clone();
|
||||
serialized.sort_by_key(|s| s.len());
|
||||
if let Some(n) = serialized.pop() {
|
||||
n
|
||||
} else {
|
||||
self.ident.as_ref().expect("identifier").convert_case(case_style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_serializations(&self, case_style: Option<CaseStyle>) -> Vec<String> {
|
||||
let mut attrs = self.serialize.clone();
|
||||
if let Some(ref to_string) = self.to_string {
|
||||
attrs.push(to_string.clone());
|
||||
}
|
||||
|
||||
if attrs.is_empty() {
|
||||
attrs.push(self.ident.as_ref().expect("identifier").convert_case(case_style));
|
||||
}
|
||||
|
||||
attrs
|
||||
}
|
||||
}
|
||||
|
||||
impl HasStrumVariantProperties for syn::Variant {
|
||||
fn get_variant_properties(&self) -> StrumVariantProperties {
|
||||
let mut output = StrumVariantProperties::default();
|
||||
output.ident = Some(self.ident.clone());
|
||||
|
||||
for meta in self.get_metadata("strum") {
|
||||
match meta {
|
||||
syn::Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => {
|
||||
if path.is_ident("message") {
|
||||
if output.message.is_some() {
|
||||
panic!("message is set twice on the same variant");
|
||||
}
|
||||
|
||||
output.message = Some(lit.expect_string("expected string"));
|
||||
} else if path.is_ident("detailed_message") {
|
||||
if output.detailed_message.is_some() {
|
||||
panic!("detailed message set twice on the same variant");
|
||||
}
|
||||
|
||||
output.detailed_message = Some(lit.expect_string("expected string"));
|
||||
} else if path.is_ident("serialize") {
|
||||
output.serialize.push(lit.expect_string("expected string"));
|
||||
} else if path.is_ident("to_string") {
|
||||
if output.to_string.is_some() {
|
||||
panic!("to_string is set twice on the same variant");
|
||||
}
|
||||
|
||||
output.to_string = Some(lit.expect_string("expected string"));
|
||||
} else if path.is_ident("disabled") {
|
||||
panic!("this method is deprecated. Prefer #[strum(disabled)] instead of #[strum(disabled=\"true\")]");
|
||||
} else if path.is_ident("default") {
|
||||
panic!("this method is deprecated. Prefer #[strum(default)] instead of #[strum(default=\"true\")]");
|
||||
} else {
|
||||
panic!("unrecognized value in strum(..) attribute");
|
||||
}
|
||||
}
|
||||
syn::Meta::Path(p) => {
|
||||
if p.is_ident("disabled") {
|
||||
output.is_disabled = true;
|
||||
} else if p.is_ident("default") {
|
||||
output.default = true;
|
||||
} else {
|
||||
panic!("unrecognized value in strum(..) attribute");
|
||||
}
|
||||
}
|
||||
syn::Meta::List(syn::MetaList { path, nested, .. }) => {
|
||||
if path.is_ident("props") {
|
||||
for p in nested {
|
||||
let p = p
|
||||
.expect_meta("unexpected literal found in props")
|
||||
.expect_namevalue("props must be key-value pairs");
|
||||
|
||||
let key = p
|
||||
.path
|
||||
.get_ident()
|
||||
.expect("key must be an identifier")
|
||||
.to_string();
|
||||
|
||||
let value = p.lit.expect_string("expected string");
|
||||
output.string_props.insert(key, value);
|
||||
}
|
||||
} else {
|
||||
panic!("unrecognized value in strum(..) attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
pub(crate) fn enum_count_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
@ -9,23 +9,14 @@ pub(crate) fn enum_count_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
|
||||
// Used in the quasi-quotation below as `#name`
|
||||
let name = &ast.ident;
|
||||
let const_name = &syn::Ident::new(
|
||||
&format!("{}_COUNT", name.to_string().to_uppercase()),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
// Helper is provided for handling complex generic types correctly and effortlessly
|
||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
// Implementation
|
||||
impl #impl_generics ::strum::EnumCount for #name #ty_generics #where_clause {
|
||||
fn count() -> usize {
|
||||
#n
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code, missing_docs)]
|
||||
pub const #const_name: usize = #n;
|
||||
// Implementation
|
||||
impl #impl_generics ::strum::EnumCount for #name #ty_generics #where_clause {
|
||||
const COUNT: usize = #n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::helpers::{MetaHelpers, MetaIteratorHelpers, MetaListHelpers};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use syn;
|
||||
|
||||
use helpers::extract_meta;
|
||||
use crate::helpers::HasTypeProperties;
|
||||
|
||||
/// Attributes to copy from the main enum's variants to the discriminant enum's variants.
|
||||
///
|
||||
@ -20,15 +19,9 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
};
|
||||
|
||||
// Derives for the generated enum
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let discriminant_attrs = type_meta
|
||||
.find_attribute("strum_discriminants")
|
||||
.collect::<Vec<&syn::Meta>>();
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
let derives = discriminant_attrs
|
||||
.find_attribute("derive")
|
||||
.map(|meta| meta.path())
|
||||
.collect::<Vec<_>>();
|
||||
let derives = type_properties.discriminant_derives;
|
||||
|
||||
let derives = quote! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, #(#derives),*)]
|
||||
@ -40,28 +33,12 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
Span::call_site(),
|
||||
));
|
||||
|
||||
let discriminants_name = discriminant_attrs
|
||||
.iter()
|
||||
.filter_map(|meta| meta.try_metalist())
|
||||
.filter(|list| list.path.is_ident("name"))
|
||||
// We want exactly zero or one items. Start with the assumption we have zero, i.e. None
|
||||
// Then set our output to the first value we see. If fold is called again and we already
|
||||
// have a value, panic.
|
||||
.fold(None, |acc, val| match acc {
|
||||
Some(_) => panic!("Expecting a single attribute 'name' in EnumDiscriminants."),
|
||||
None => Some(val),
|
||||
})
|
||||
.map(|meta| meta.expand_inner())
|
||||
.and_then(|metas| metas.into_iter().map(|meta| meta.path()).next())
|
||||
.unwrap_or(&default_name);
|
||||
let discriminants_name = type_properties.discriminant_name.unwrap_or(default_name);
|
||||
|
||||
// Pass through all other attributes
|
||||
let pass_though_attributes = discriminant_attrs
|
||||
.iter()
|
||||
.filter(|meta| {
|
||||
let path = meta.path();
|
||||
!path.is_ident("derive") && !path.is_ident("name")
|
||||
})
|
||||
let pass_though_attributes = type_properties
|
||||
.discriminant_others
|
||||
.into_iter()
|
||||
.map(|meta| quote! { #[ #meta ] })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use helpers::{extract_meta, MetaIteratorHelpers};
|
||||
use crate::helpers::HasStrumVariantProperties;
|
||||
|
||||
pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -31,7 +31,7 @@ pub fn enum_iter_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let mut arms = Vec::new();
|
||||
let enabled = variants
|
||||
.iter()
|
||||
.filter(|variant| !extract_meta(&variant.attrs).is_disabled());
|
||||
.filter(|variant| !variant.get_variant_properties().is_disabled);
|
||||
|
||||
for (idx, variant) in enabled.enumerate() {
|
||||
use syn::Fields::*;
|
||||
|
@ -1,8 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{HasStrumVariantProperties, HasTypeProperties};
|
||||
|
||||
pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -12,19 +11,16 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
_ => panic!("EnumMessage only works on Enums"),
|
||||
};
|
||||
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
let mut arms = Vec::new();
|
||||
let mut detailed_arms = Vec::new();
|
||||
let mut serializations = Vec::new();
|
||||
|
||||
for variant in variants {
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let messages = meta.find_unique_property("strum", "message");
|
||||
let detailed_messages = meta.find_unique_property("strum", "detailed_message");
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
let messages = variant_properties.message.as_ref();
|
||||
let detailed_messages = variant_properties.detailed_message.as_ref();
|
||||
let ident = &variant.ident;
|
||||
|
||||
use syn::Fields::*;
|
||||
@ -36,10 +32,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
|
||||
// You can't disable getting the serializations.
|
||||
{
|
||||
let mut serialization_variants = meta.find_properties("strum", "serialize");
|
||||
if serialization_variants.len() == 0 {
|
||||
serialization_variants.push(ident.convert_case(case_style));
|
||||
}
|
||||
let serialization_variants = variant_properties.get_serializations(type_properties.case_style);
|
||||
|
||||
let count = serialization_variants.len();
|
||||
serializations.push(quote! {
|
||||
@ -51,7 +44,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
}
|
||||
|
||||
// But you can disable the messages.
|
||||
if meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
use syn::Meta;
|
||||
|
||||
use crate::helpers::{extract_meta, MetaHelpers, MetaIteratorHelpers, MetaListHelpers};
|
||||
|
||||
fn extract_properties(meta: &[Meta]) -> Vec<(&syn::Path, &syn::Lit)> {
|
||||
meta.iter()
|
||||
// Filter down to the strum(..) attribute
|
||||
.filter_map(|meta| meta.try_metalist())
|
||||
.filter(|list| list.path.is_ident("strum"))
|
||||
.flat_map(|list| list.expand_inner())
|
||||
// Filter down to the `strum(props(..))` attribute
|
||||
.filter_map(|meta| meta.try_metalist())
|
||||
.filter(|inner_list| inner_list.path.is_ident("props"))
|
||||
.flat_map(|inner_list| inner_list.expand_inner())
|
||||
// Expand all the pairs `strum(props(key = value, ..))`
|
||||
.filter_map(|prop| match *prop {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
ref path, ref lit, ..
|
||||
}) => Some((path, lit)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
use crate::helpers::HasStrumVariantProperties;
|
||||
|
||||
pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -35,12 +14,12 @@ pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let mut arms = Vec::new();
|
||||
for variant in variants {
|
||||
let ident = &variant.ident;
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
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 meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -51,17 +30,8 @@ pub fn enum_properties_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
Named(..) => quote! { {..} },
|
||||
};
|
||||
|
||||
for (key, value) in extract_properties(&meta) {
|
||||
use syn::Lit::*;
|
||||
let key = key.segments.last().unwrap().ident.to_string();
|
||||
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 )}),
|
||||
_ => {}
|
||||
}
|
||||
for (key, value) in variant_properties.string_props {
|
||||
string_arms.push(quote! { #key => ::std::option::Option::Some( #value )})
|
||||
}
|
||||
|
||||
string_arms.push(quote! { _ => ::std::option::Option::None });
|
||||
|
@ -1,7 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::{case_style::CaseStyle, extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{CaseStyleHelpers, HasTypeProperties};
|
||||
|
||||
pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -14,14 +14,11 @@ pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
};
|
||||
|
||||
// Derives for the generated enum
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
let names = variants
|
||||
.iter()
|
||||
.map(|v| v.ident.convert_case(case_style))
|
||||
.map(|v| v.ident.convert_case(type_properties.case_style))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote! {
|
||||
|
@ -1,8 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{HasTypeProperties, HasStrumVariantProperties};
|
||||
|
||||
fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
|
||||
let name = &ast.ident;
|
||||
@ -12,36 +11,21 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
|
||||
_ => panic!("This macro only works on Enums"),
|
||||
};
|
||||
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
for variant in variants {
|
||||
use syn::Fields::*;
|
||||
let ident = &variant.ident;
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
|
||||
if meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look at all the serialize attributes.
|
||||
// Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent
|
||||
// (i.e. always `enum.as_ref().to_string() == enum.to_string()`).
|
||||
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
|
||||
n
|
||||
} else {
|
||||
let mut attrs = meta.find_properties("strum", "serialize");
|
||||
// We always take the longest one. This is arbitary, but is *mostly* deterministic
|
||||
attrs.sort_by_key(|s| s.len());
|
||||
if let Some(n) = attrs.pop() {
|
||||
n
|
||||
} else {
|
||||
ident.convert_case(case_style)
|
||||
}
|
||||
};
|
||||
|
||||
let output = variant_properties.get_preferred_name(type_properties.case_style);
|
||||
let params = match variant.fields {
|
||||
Unit => quote! {},
|
||||
Unnamed(..) => quote! { (..) },
|
||||
|
@ -1,8 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{HasTypeProperties, HasStrumVariantProperties};
|
||||
|
||||
pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -12,39 +11,24 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
_ => panic!("Display only works on Enums"),
|
||||
};
|
||||
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
let mut arms = Vec::new();
|
||||
for variant in variants {
|
||||
use syn::Fields::*;
|
||||
let ident = &variant.ident;
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
|
||||
if meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look at all the serialize attributes.
|
||||
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
|
||||
n
|
||||
} else {
|
||||
let mut attrs = meta.find_properties("strum", "serialize");
|
||||
// We always take the longest one. This is arbitary, but is *mostly* deterministic
|
||||
attrs.sort_by_key(|s| s.len());
|
||||
if let Some(n) = attrs.pop() {
|
||||
n
|
||||
} else {
|
||||
ident.convert_case(case_style)
|
||||
}
|
||||
};
|
||||
let output = variant_properties.get_preferred_name(type_properties.case_style);
|
||||
|
||||
let params = match variant.fields {
|
||||
Unit => quote! {},
|
||||
Unnamed(..) => quote! { (..) },
|
||||
Named(..) => quote! { {..} },
|
||||
syn::Fields::Unit => quote! {},
|
||||
syn::Fields::Unnamed(..) => quote! { (..) },
|
||||
syn::Fields::Named(..) => quote! { {..} },
|
||||
};
|
||||
|
||||
arms.push(quote! { #name::#ident #params => f.pad(#output) });
|
||||
|
@ -1,8 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use crate::helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{HasTypeProperties, HasStrumVariantProperties};
|
||||
|
||||
pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -12,10 +11,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
_ => panic!("FromString only works on Enums"),
|
||||
};
|
||||
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
let type_properties = ast.get_type_properties();
|
||||
|
||||
let mut has_default = false;
|
||||
let mut default =
|
||||
@ -24,24 +20,13 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
for variant in variants {
|
||||
use syn::Fields::*;
|
||||
let ident = &variant.ident;
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
|
||||
// Look at all the serialize attributes.
|
||||
// let mut attrs = find_properties(&meta, "strum", "serialize");
|
||||
// attrs.extend(find_properties(&meta, "strum", "to_string"));
|
||||
|
||||
let mut attrs = meta.find_properties("strum", "serialize");
|
||||
attrs.extend(meta.find_properties("strum", "to_string"));
|
||||
|
||||
// if is_disabled(&meta) {
|
||||
if meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
if meta
|
||||
.find_unique_property("strum", "default")
|
||||
.map_or(false, |s| s == "true")
|
||||
{
|
||||
if variant_properties.default {
|
||||
if has_default {
|
||||
panic!("Can't have multiple default variants");
|
||||
}
|
||||
@ -63,9 +48,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
}
|
||||
|
||||
// If we don't have any custom variants, add the default serialized name.
|
||||
if attrs.len() == 0 {
|
||||
attrs.push(ident.convert_case(case_style));
|
||||
}
|
||||
let attrs = variant_properties.get_serializations(type_properties.case_style);
|
||||
|
||||
let params = match variant.fields {
|
||||
Unit => quote! {},
|
||||
|
@ -1,8 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use syn;
|
||||
|
||||
use crate::helpers::case_style::CaseStyle;
|
||||
use crate::helpers::{extract_meta, CaseStyleHelpers, MetaIteratorHelpers};
|
||||
use crate::helpers::{HasStrumVariantProperties, HasTypeProperties};
|
||||
|
||||
pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
@ -12,34 +11,19 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
|
||||
_ => panic!("ToString only works on Enums"),
|
||||
};
|
||||
|
||||
let type_meta = extract_meta(&ast.attrs);
|
||||
let case_style = type_meta
|
||||
.find_unique_property("strum", "serialize_all")
|
||||
.map(|style| CaseStyle::from(style.as_ref()));
|
||||
|
||||
let type_properties = ast.get_type_properties();
|
||||
let mut arms = Vec::new();
|
||||
for variant in variants {
|
||||
use syn::Fields::*;
|
||||
let ident = &variant.ident;
|
||||
let meta = extract_meta(&variant.attrs);
|
||||
let variant_properties = variant.get_variant_properties();
|
||||
|
||||
if meta.is_disabled() {
|
||||
if variant_properties.is_disabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look at all the serialize attributes.
|
||||
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
|
||||
n
|
||||
} else {
|
||||
let mut attrs = meta.find_properties("strum", "serialize");
|
||||
// We always take the longest one. This is arbitary, but is *mostly* deterministic
|
||||
attrs.sort_by_key(|s| s.len());
|
||||
if let Some(n) = attrs.pop() {
|
||||
n
|
||||
} else {
|
||||
ident.convert_case(case_style)
|
||||
}
|
||||
};
|
||||
let output = variant_properties.get_preferred_name(type_properties.case_style);
|
||||
|
||||
let params = match variant.fields {
|
||||
Unit => quote! {},
|
||||
|
@ -14,6 +14,6 @@ pub enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(disabled = "true")]
|
||||
#[strum(disabled)]
|
||||
Green(String),
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(disabled = "true")]
|
||||
#[strum(disabled)]
|
||||
Green(String),
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(default = "true")]
|
||||
#[strum(default)]
|
||||
Green(String),
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(default = "true")]
|
||||
#[strum(default)]
|
||||
Green(String),
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(default = "true")]
|
||||
#[strum(default)]
|
||||
Green(String),
|
||||
}
|
||||
|
||||
@ -21,9 +21,18 @@ fn to_blue_string() {
|
||||
|
||||
#[test]
|
||||
fn test_formatters() {
|
||||
assert_eq!(String::from(" blue"), format!("{:>6}", Color::Blue { hue: 0 }));
|
||||
assert_eq!(String::from("blue "), format!("{:<6}", Color::Blue { hue: 0 }));
|
||||
assert_eq!(String::from(" blue "), format!("{:^6}", Color::Blue { hue: 0 }));
|
||||
assert_eq!(
|
||||
String::from(" blue"),
|
||||
format!("{:>6}", Color::Blue { hue: 0 })
|
||||
);
|
||||
assert_eq!(
|
||||
String::from("blue "),
|
||||
format!("{:<6}", Color::Blue { hue: 0 })
|
||||
);
|
||||
assert_eq!(
|
||||
String::from(" blue "),
|
||||
format!("{:^6}", Color::Blue { hue: 0 })
|
||||
);
|
||||
assert_eq!(String::from("bl"), format!("{:.2}", Color::Blue { hue: 0 }));
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ enum Week {
|
||||
|
||||
#[test]
|
||||
fn simple_test() {
|
||||
assert_eq!(7, Week::count());
|
||||
assert_eq!(Week::count(), WEEK_COUNT);
|
||||
assert_eq!(Week::iter().count(), WEEK_COUNT);
|
||||
assert_eq!(7, Week::COUNT);
|
||||
assert_eq!(Week::iter().count(), Week::COUNT);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ enum Pets {
|
||||
#[strum(detailed_message = "My fish is named Charles McFish")]
|
||||
Fish,
|
||||
Bird,
|
||||
#[strum(disabled = "true")]
|
||||
#[strum(disabled)]
|
||||
Hamster,
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ enum Color {
|
||||
},
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(default = "true")]
|
||||
#[strum(default)]
|
||||
Green(String),
|
||||
#[strum(to_string = "purp")]
|
||||
Purple,
|
||||
|
@ -13,7 +13,7 @@ enum Color {
|
||||
Blue { hue: usize },
|
||||
#[strum(serialize = "y", serialize = "yellow")]
|
||||
Yellow,
|
||||
#[strum(default = "true")]
|
||||
#[strum(default)]
|
||||
Green(String),
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user