1
0
mirror of https://github.com/danog/strum.git synced 2024-11-26 20:14:40 +01:00

Did additional refactoring and added helpful trait methods to improve readability

This commit is contained in:
Peter Glotfelty 🚀 2019-08-17 09:40:39 -07:00
parent 8502a1cabc
commit ca17ae7581
12 changed files with 131 additions and 155 deletions

View File

@ -1,4 +1,6 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CaseStyle {
CamelCase,
KebabCase,
@ -43,3 +45,45 @@ impl<'s> From<&'s str> for CaseStyle {
}
}
}
pub trait CaseStyleHelpers {
fn convert_case(&self, case_style: Option<CaseStyle>) -> String;
}
impl CaseStyleHelpers for syn::Ident {
fn convert_case(&self, case_style: Option<CaseStyle>) -> String {
let ident_string = self.to_string();
if let Some(case_style) = case_style {
match case_style {
CaseStyle::PascalCase => ident_string.to_camel_case(),
CaseStyle::KebabCase => ident_string.to_kebab_case(),
CaseStyle::MixedCase => ident_string.to_mixed_case(),
CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(),
CaseStyle::SnakeCase => ident_string.to_snake_case(),
CaseStyle::TitleCase => ident_string.to_title_case(),
CaseStyle::UpperCase => ident_string.to_uppercase(),
CaseStyle::LowerCase => ident_string.to_lowercase(),
CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
CaseStyle::CamelCase => {
let camel_case = ident_string.to_camel_case();
let mut pascal = String::with_capacity(camel_case.len());
let mut it = camel_case.chars();
if let Some(ch) = it.next() {
pascal.extend(ch.to_lowercase());
}
pascal.extend(it);
pascal
}
}
} else {
ident_string
}
}
}
#[test]
fn test_convert_case() {
let id = syn::Ident::new("test_me", proc_macro2::Span::call_site());
assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
}

View File

@ -1,18 +1,15 @@
use syn::{Meta, MetaList};
use syn::Meta;
use super::MetaHelpers;
use super::MetaListHelpers;
pub trait MetaIteratorHelpers {
type Item;
fn unique_meta_list(self, attr: &str) -> Option<Self::Item>;
fn extract_attrs(self, attr: &str, prop: &str) -> Vec<String>;
fn find_attribute(&self, attr: &str) -> std::vec::IntoIter<&Meta>;
fn find_properties(&self, attr: &str, prop: &str) -> Vec<String>;
fn unique_attr(self, attr: &str, prop: &str) -> Option<String>
where Self: Sized
fn find_unique_property(&self, attr: &str, prop: &str) -> Option<String>
{
let mut curr = self.extract_attrs(attr, prop);
let mut curr = self.find_properties(attr, prop);
if curr.len() > 1 {
panic!("More than one property: {} found on variant", prop);
}
@ -20,10 +17,9 @@ pub trait MetaIteratorHelpers {
curr.pop()
}
fn is_disabled(self) -> bool
where Self: Sized
fn is_disabled(&self) -> bool
{
let v = self.extract_attrs("strum", "disabled");
let v = self.find_properties("strum", "disabled");
match v.len() {
0 => false,
1 => v[0] == "true",
@ -32,44 +28,29 @@ pub trait MetaIteratorHelpers {
}
}
impl <'a, I> MetaIteratorHelpers for I
//impl MetaIteratorHelpers for [Meta]
impl <T> MetaIteratorHelpers for [T]
where
I: std::iter::IntoIterator<Item=&'a Meta>
T: std::borrow::Borrow<Meta>
{
type Item=&'a MetaList;
/// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`.
///
/// # Panics
///
/// Panics if more than one `Meta` exists with the name.
fn unique_meta_list(self, attr: &str) -> Option<&'a MetaList>
where
Self: Sized
{
// let mut curr = get_meta_list(metas.into_iter(), attr).collect::<Vec<_>>();
let mut curr = self.into_iter()
.filter_map(|meta| meta.try_metalist())
.filter(|list| list.path.is_ident(attr))
.collect::<Vec<_>>();
if curr.len() > 1 {
panic!("More than one `{}` attribute found on type", attr);
}
curr.pop()
}
fn extract_attrs(self, attr: &str, prop: &str) -> Vec<String>
where
Self: Sized
{
use syn::{Lit, MetaNameValue};
self.into_iter()
.filter_map(|meta| meta.try_metalist())
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())
// Get all the inner elements as long as they start with ser.
.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,

View File

@ -1,6 +1,5 @@
use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase};
use syn::{Attribute, Ident, Meta, MetaList};
use crate::helpers::case_style::CaseStyle;
use syn::{Attribute, Meta};
pub mod case_style;
mod metalist_helpers;
@ -10,6 +9,7 @@ mod meta_iterator_helpers;
pub use self::metalist_helpers::MetaListHelpers;
pub use self::meta_helpers::MetaHelpers;
pub use self::meta_iterator_helpers::MetaIteratorHelpers;
pub use self::case_style::CaseStyleHelpers;
pub fn extract_meta(attrs: &[Attribute]) -> Vec<Meta> {
attrs
@ -17,61 +17,3 @@ pub fn extract_meta(attrs: &[Attribute]) -> Vec<Meta> {
.filter_map(|attribute| attribute.parse_meta().ok())
.collect()
}
/// Returns the `MetaList` that matches the given name from the list of `Meta`s, or `None`.
///
/// # Panics
///
/// Panics if more than one `Meta` exists with the name.
pub fn unique_meta_list<'meta, MetaIt>(metas: MetaIt, attr: &'meta str) -> Option<&'meta MetaList>
where
MetaIt: Iterator<Item = &'meta Meta>,
{
// let mut curr = get_meta_list(metas.into_iter(), attr).collect::<Vec<_>>();
let mut curr = metas
.filter_map(|meta| meta.try_metalist())
.filter(|list| list.path.is_ident(attr))
.collect::<Vec<_>>();
if curr.len() > 1 {
panic!("More than one `{}` attribute found on type", attr);
}
curr.pop()
}
pub fn convert_case(ident: &Ident, case_style: Option<CaseStyle>) -> String {
let ident_string = ident.to_string();
if let Some(case_style) = case_style {
match case_style {
CaseStyle::PascalCase => ident_string.to_camel_case(),
CaseStyle::KebabCase => ident_string.to_kebab_case(),
CaseStyle::MixedCase => ident_string.to_mixed_case(),
CaseStyle::ShoutySnakeCase => ident_string.to_shouty_snake_case(),
CaseStyle::SnakeCase => ident_string.to_snake_case(),
CaseStyle::TitleCase => ident_string.to_title_case(),
CaseStyle::UpperCase => ident_string.to_uppercase(),
CaseStyle::LowerCase => ident_string.to_lowercase(),
CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
CaseStyle::CamelCase => {
let camel_case = ident_string.to_camel_case();
let mut pascal = String::with_capacity(camel_case.len());
let mut it = camel_case.chars();
if let Some(ch) = it.next() {
pascal.extend(ch.to_lowercase());
}
pascal.extend(it);
pascal
}
}
} else {
ident_string
}
}
#[test]
fn test_convert_case() {
let id = Ident::new("test_me", proc_macro2::Span::call_site());
assert_eq!("testMe", convert_case(&id, Some(CaseStyle::CamelCase)));
assert_eq!("TestMe", convert_case(&id, Some(CaseStyle::PascalCase)));
}

View File

@ -1,8 +1,8 @@
use crate::helpers::{MetaHelpers, MetaListHelpers};
use crate::helpers::{MetaHelpers, MetaListHelpers, MetaIteratorHelpers};
use proc_macro2::{Span, TokenStream};
use syn;
use helpers::{extract_meta, unique_meta_list};
use helpers::{extract_meta};
pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
@ -15,19 +15,12 @@ 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.iter()
// We are only looking at 'metalist' style attributes [list(item1,item2,...)]
.filter_map(|meta| meta.try_metalist())
// Find the list named `strum_discriminants`
.filter(|list| list.path.is_ident("strum_discriminants"))
// If there are multiple attributes, we combine them all together.
.flat_map(|list| list.expand_inner())
let discriminant_attrs = type_meta
.find_attribute("strum_discriminants")
.collect::<Vec<&syn::Meta>>();
let derives = discriminant_attrs.iter()
.filter_map(|meta| meta.try_metalist())
.filter(|list| list.path.is_ident("derive"))
.flat_map(|list| list.expand_inner())
let derives = discriminant_attrs
.find_attribute("derive")
.map(|meta| meta.path())
.collect::<Vec<_>>();
@ -41,7 +34,16 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream {
Span::call_site()
));
let discriminants_name = unique_meta_list(discriminant_attrs.iter().map(|&m| m), "name")
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);

View File

@ -17,8 +17,8 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
for variant in variants {
let meta = extract_meta(&variant.attrs);
let messages = meta.unique_attr("strum", "message");
let detailed_messages = meta.unique_attr("strum", "detailed_message");
let messages = meta.find_unique_property("strum", "message");
let detailed_messages = meta.find_unique_property("strum", "detailed_message");
let ident = &variant.ident;
use syn::Fields::*;
@ -30,7 +30,7 @@ pub fn enum_message_inner(ast: &syn::DeriveInput) -> TokenStream {
// You can't disable getting the serializations.
{
let mut serialization_variants = meta.extract_attrs("strum", "serialize");
let mut serialization_variants = meta.find_properties("strum", "serialize");
if serialization_variants.len() == 0 {
serialization_variants.push(ident.to_string());
}

View File

@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use syn;
use crate::helpers::case_style::CaseStyle;
use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers};
use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers};
pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
@ -14,12 +14,12 @@ 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.unique_attr("strum", "serialize_all")
let case_style = type_meta.find_unique_property("strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));
let names = variants
.iter()
.map(|v| convert_case(&v.ident, case_style))
.map(|v| v.ident.convert_case(case_style))
.collect::<Vec<_>>();
quote! {

View File

@ -1,10 +1,14 @@
pub mod as_ref_str;
pub mod display;
pub mod enum_count;
pub mod enum_discriminants;
pub mod enum_iter;
pub mod enum_messages;
pub mod enum_properties;
pub mod enum_variant_names;
pub mod from_string;
pub mod to_string;
mod strings;
pub use self::strings::as_ref_str;
pub use self::strings::display;
pub use self::strings::from_string;
pub use self::strings::to_string;

View File

@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use syn;
use crate::helpers::case_style::CaseStyle;
use helpers::{convert_case, extract_meta, MetaIteratorHelpers};
use helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers};
fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
let name = &ast.ident;
@ -13,7 +13,7 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
};
let type_meta = extract_meta(&ast.attrs);
let case_style = type_meta.unique_attr("strum", "serialize_all")
let case_style = type_meta.find_unique_property("strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));
for variant in variants {
@ -28,16 +28,16 @@ fn get_arms(ast: &syn::DeriveInput) -> Vec<TokenStream> {
// 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.unique_attr("strum", "to_string") {
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
n
} else {
let mut attrs = meta.extract_attrs("strum", "serialize");
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 {
convert_case(ident, case_style)
ident.convert_case(case_style)
}
};

View File

@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use syn;
use crate::helpers::case_style::CaseStyle;
use helpers::{convert_case, extract_meta, MetaIteratorHelpers};
use helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers};
pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
@ -13,7 +13,7 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
};
let type_meta = extract_meta(&ast.attrs);
let case_style = type_meta.unique_attr("strum", "serialize_all")
let case_style = type_meta.find_unique_property("strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));
let mut arms = Vec::new();
@ -27,16 +27,16 @@ pub fn display_inner(ast: &syn::DeriveInput) -> TokenStream {
}
// Look at all the serialize attributes.
let output = if let Some(n) = meta.unique_attr("strum", "to_string") {
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
n
} else {
let mut attrs = meta.extract_attrs("strum", "serialize");
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 {
convert_case(ident, case_style)
ident.convert_case(case_style)
}
};

View File

@ -2,8 +2,7 @@ use proc_macro2::TokenStream;
use syn;
use crate::helpers::case_style::CaseStyle;
// use helpers::{convert_case, extract_attrs, extract_meta, is_disabled, unique_attr, MetaIteratorHelpers};
use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers};
use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers};
pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
@ -14,7 +13,7 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
};
let type_meta = extract_meta(&ast.attrs);
let case_style = type_meta.unique_attr("strum", "serialize_all")
let case_style = type_meta.find_unique_property("strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));
let mut has_default = false;
@ -27,18 +26,18 @@ pub fn from_string_inner(ast: &syn::DeriveInput) -> TokenStream {
let meta = extract_meta(&variant.attrs);
// Look at all the serialize attributes.
// let mut attrs = extract_attrs(&meta, "strum", "serialize");
// attrs.extend(extract_attrs(&meta, "strum", "to_string"));
// let mut attrs = find_properties(&meta, "strum", "serialize");
// attrs.extend(find_properties(&meta, "strum", "to_string"));
let mut attrs = meta.extract_attrs("strum", "serialize");
attrs.extend(meta.extract_attrs("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() {
continue;
}
if meta.unique_attr("strum", "default").map_or(false, |s| s == "true") {
if meta.find_unique_property("strum", "default").map_or(false, |s| s == "true") {
if has_default {
panic!("Can't have multiple default variants");
}
@ -61,7 +60,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(convert_case(ident, case_style));
attrs.push(ident.convert_case(case_style));
}
let params = match variant.fields {

View File

@ -0,0 +1,4 @@
pub mod as_ref_str;
pub mod display;
pub mod from_string;
pub mod to_string;

View File

@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use syn;
use crate::helpers::case_style::CaseStyle;
use crate::helpers::{convert_case, extract_meta, MetaIteratorHelpers};
use crate::helpers::{CaseStyleHelpers, extract_meta, MetaIteratorHelpers};
pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
@ -13,7 +13,7 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
};
let type_meta = extract_meta(&ast.attrs);
let case_style = type_meta.unique_attr( "strum", "serialize_all")
let case_style = type_meta.find_unique_property( "strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));
let mut arms = Vec::new();
@ -27,16 +27,16 @@ pub fn to_string_inner(ast: &syn::DeriveInput) -> TokenStream {
}
// Look at all the serialize attributes.
let output = if let Some(n) = meta.unique_attr("strum", "to_string") {
let output = if let Some(n) = meta.find_unique_property("strum", "to_string") {
n
} else {
let mut attrs = meta.extract_attrs("strum", "serialize");
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 {
convert_case(ident, case_style)
ident.convert_case(case_style)
}
};