case_insensitive key, refactoring

rename
Pantonshire 5 years ago
parent 13be79f632
commit 1a6feab1f2

@ -3,6 +3,7 @@ extern crate enumscribe;
#[derive(EnumToString)]
enum Foo {
#[enumscribe(str = "b", case_insensitive)]
Baa,
#[enumscribe(ignore)]
Baz(),

@ -125,7 +125,7 @@ impl Dict {
Ok(dict)
}
pub(crate) fn remove_typed_value<T, F>(&mut self, key: &str, converter: F) -> MacroResult<Option<(T, Span)>>
pub(crate) fn remove_typed<T, F>(&mut self, key: &str, converter: F) -> MacroResult<Option<(T, Span)>>
where
F: Fn(&Value) -> ValueTypeResult<T>
{
@ -141,6 +141,17 @@ impl Dict {
}
}
pub(crate) fn remove_typed_or_default<T, F>(&mut self, key: &str, default: (T, Span), converter: F) -> MacroResult<(T, Span)>
where
F: Fn(&Value) -> ValueTypeResult<T>
{
match self.remove_typed(key, converter) {
Ok(Some(value)) => Ok(value),
Ok(None) => Ok(default),
Err(err) => Err(err),
}
}
pub(crate) fn assert_empty(&self) -> MacroResult<()> {
if self.inner.is_empty() {
Ok(())

@ -0,0 +1,165 @@
use std::collections::HashSet;
use proc_macro2::Span;
use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr};
use syn::parse::{ParseBuffer, ParseStream};
use syn::spanned::Spanned;
use crate::{CRATE_ATTR, NAME, OTHER, IGNORE, CASE_INSENSITIVE};
use crate::attribute::*;
use crate::error::{MacroError, MacroResult};
#[derive(Clone, Debug)]
pub(crate) struct Enum {
pub(crate) variants: Vec<Variant>,
}
#[derive(Clone, Debug)]
pub(crate) struct Variant {
pub(crate) ident: String,
pub(crate) v_type: VariantType,
pub(crate) span: Span,
}
#[derive(Clone, Debug)]
pub(crate) enum VariantType {
Ignore,
Named {
name: String,
constructor: VariantConstructor,
case_insensitive: bool,
},
Other {
field_name: Option<String> //use {} for constructor if Some, use () if None
},
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum VariantConstructor {
None,
Paren,
Brace,
}
pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
let mut variants = Vec::with_capacity(data.variants.len());
let mut taken_names = HashSet::new();
let mut other_variant = false;
for variant in data.variants {
let variant_ident = variant.ident.to_string();
let variant_span = variant.span();
// Parse the `#[enumscribe(...)]` attributes for this variant into a single Dict
let mut dict = Dict::from_attrs(CRATE_ATTR, &variant.attrs)?;
// Convert the values in the Dict to the appropriate types
let name_opt = dict.remove_typed(NAME, Value::value_string)?;
let (other, other_span) = dict.remove_typed_or_default(OTHER, (false, variant_span), Value::value_bool)?;
let (ignore, _) = dict.remove_typed_or_default(IGNORE, (false, variant_span), Value::value_bool)?;
let (case_insensitive, _) = dict.remove_typed_or_default(CASE_INSENSITIVE, (false, variant_span), Value::value_bool)?;
// Return an error if there are any unrecognised keys in the Dict
dict.assert_empty()?;
let scribe_variant = if ignore {
Variant {
ident: variant_ident,
v_type: VariantType::Ignore,
span: variant_span,
}
} else if other {
// Return an error if there is already an "other" variant for this enum
if other_variant {
return Err(MacroError::new(
format!(
"cannot have multiple variants marked as {}",
OTHER
),
other_span,
));
}
other_variant = true;
// Return an error if a str name is provided for this variant
if let Some((_, name_span)) = name_opt {
return Err(MacroError::new(
format!(
"cannot use {} for variant {} because it is marked as {}",
NAME, variant_ident, OTHER
),
name_span,
));
}
// Return an error if this variant doesn't have exactly one field
if variant.fields.len() != 1 {
return Err(MacroError::new(
format!(
"the variant {} must have exactly one field because it is marked as {}",
variant_ident, OTHER
),
variant_span,
));
}
// Get the name of the variant's field (or None if it is unnamed)
let field_name = variant.fields.iter().next()
.and_then(|field| field.ident.as_ref().map(|ident| ident.to_string()));
Variant {
ident: variant_ident,
v_type: VariantType::Other { field_name },
span: variant_span,
}
} else {
// Use the str name if one is provided, otherwise use the variant's name
let (name, name_span) = match name_opt {
Some((name, name_span)) => (name, name_span),
None => (variant.ident.to_string(), variant.ident.span())
};
// Do not allow duplicate names
if taken_names.contains(&name) {
return Err(MacroError::new(
format!("duplicate name \"{}\"", name),
name_span,
));
}
// Return an error if the variant has any fields
if variant.fields.len() != 0 {
return Err(MacroError::new(
format!(
"the variant {} must not have any fields\n\
hint: if you do not want to remove {}\'s fields, try using \
#[enumscribe(ignore)] for {}",
variant_ident, variant_ident, variant_ident
),
variant_span,
));
}
// The variant is allowed to have an empty constructor, so find out if it has one
// and, if so, what type of constructor (parentheses or braces)
let constructor = match variant.fields {
Fields::Named(_) => VariantConstructor::Brace,
Fields::Unnamed(_) => VariantConstructor::Paren,
Fields::Unit => VariantConstructor::None,
};
taken_names.insert(name.clone());
Variant {
ident: variant_ident,
v_type: VariantType::Named { name, constructor, case_insensitive },
span: variant_span,
}
};
variants.push(scribe_variant);
}
Ok(Enum { variants })
}

@ -10,158 +10,16 @@ use syn::spanned::Spanned;
use attribute::*;
use error::{MacroError, MacroResult};
mod error;
mod enums;
mod attribute;
mod error;
const CRATE_ATTR: &'static str = "enumscribe";
#[derive(Clone, Debug)]
struct Enum {
variants: Vec<Variant>,
}
#[derive(Clone, Debug)]
struct Variant {
ident: String,
v_type: VariantType,
span: Span,
}
#[derive(Clone, Debug)]
enum VariantType {
Ignore,
Named { name: String, constructor: VariantConstructor },
Other { field_name: Option<String> }, //use {} for constructor if Some, use () if None
}
#[derive(Clone, Copy, Debug)]
enum VariantConstructor {
None,
Paren,
Brace,
}
fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
const NAME: &'static str = "str";
const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore";
let mut variants = Vec::with_capacity(data.variants.len());
let mut taken_names = HashSet::new();
let mut other_variant = false;
for variant in data.variants {
let variant_ident = variant.ident.to_string();
let variant_span = variant.span();
let mut dict = Dict::from_attrs(CRATE_ATTR, &variant.attrs)?;
let name_opt = dict.remove_typed_value(NAME, Value::value_string)?;
let other = match dict.remove_typed_value(OTHER, Value::value_bool)? {
Some((other, _)) => other,
None => false
};
let ignore = match dict.remove_typed_value(IGNORE, Value::value_bool)? {
Some((ignore, _)) => ignore,
None => false
};
dict.assert_empty()?;
let scribe_variant = if ignore {
Variant {
ident: variant_ident,
v_type: VariantType::Ignore,
span: variant_span,
}
} else if other {
if other_variant {
return Err(MacroError::new(
format!(
"cannot have multiple variants marked as {}",
OTHER
),
variant_span,
));
}
other_variant = true;
if let Some((_, name_span)) = name_opt {
return Err(MacroError::new(
format!(
"cannot use {} for variant {} because it is marked as {}",
NAME, variant_ident, OTHER
),
name_span,
));
}
if variant.fields.len() != 1 {
return Err(MacroError::new(
format!(
"the variant {} must have exactly one field because it is marked as {}",
variant_ident, OTHER
),
variant_span,
));
}
let field_name = variant.fields.iter().next()
.and_then(|field| field.ident.as_ref().map(|ident| ident.to_string()));
Variant {
ident: variant_ident,
v_type: VariantType::Other { field_name },
span: variant_span,
}
} else {
let (name, name_span) = match name_opt {
Some((name, name_span)) => (name, name_span),
None => (variant.ident.to_string(), variant.ident.span())
};
if taken_names.contains(&name) {
return Err(MacroError::new(
format!("duplicate name \"{}\"", name),
name_span,
));
}
if variant.fields.len() != 0 {
return Err(MacroError::new(
format!(
"the variant {} must not have any fields\n\
hint: if you do not want to remove {}\'s fields, try using \
#[enumscribe(ignore)] for {}",
variant_ident, variant_ident, variant_ident
),
variant_span,
));
}
let constructor = match variant.fields {
Fields::Named(_) => VariantConstructor::Brace,
Fields::Unnamed(_) => VariantConstructor::Paren,
Fields::Unit => VariantConstructor::None,
};
taken_names.insert(name.clone());
Variant {
ident: variant_ident,
v_type: VariantType::Named { name, constructor },
span: variant_span,
}
};
variants.push(scribe_variant);
}
Ok(Enum { variants })
}
const NAME: &'static str = "str";
const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore";
const CASE_INSENSITIVE: &'static str = "case_insensitive";
#[proc_macro_derive(EnumToString, attributes(enumscribe))]
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
@ -173,7 +31,7 @@ pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
Data::Union(_) => return MacroError::new("cannot use enumscribe for unions", input.ident.span()).to_token_stream()
};
let variants = match parse_enum(enum_data) {
let variants = match enums::parse_enum(enum_data) {
Ok(variants) => variants,
Err(err) => return err.to_token_stream()
};

Loading…
Cancel
Save