#![doc = include_str!("../README.md")]
extern crate proc_macro;
use convert_case::{self, Casing};
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{DeriveInput, Fields, parse_macro_input, spanned::Spanned};
use crate::convert_string::CaseType;
mod convert_string;
#[proc_macro_derive(
EnumDisplay,
attributes(
Snake, Constant, UpperSnake, Ada, Kebab, Cobol, UpperKebab, Train, Flat, UpperFlat, Pascal,
UpperCamel, Camel, Lower, Upper, Title, Sentence, None
)
)]
pub fn derive_enum_display(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let DeriveInput {
ident, data, attrs, ..
} = input.clone();
let transform = CaseType::from_attributes(attrs);
let enum_data = match data {
syn::Data::Enum(data_enum) => data_enum,
_ => {
return token_stream_error(input.span(), "Must be a enum.");
}
};
let mut enum_items = Vec::new();
for variant in enum_data.variants {
let variant_transform = CaseType::from_attributes(variant.attrs);
let variant_ident = &variant.ident;
let val = if let Some(ct) = variant_transform {
variant_ident.to_string().to_case(ct.into())
} else if let Some(ct) = transform {
variant_ident.to_string().to_case(ct.into())
} else {
variant_ident.to_string()
};
let line_quote = quote! {
#ident::#variant_ident => write!(f, #val)
};
enum_items.push(line_quote);
}
quote! {
#[automatically_derived]
impl ::std::fmt::Display for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
#(#enum_items),*
}
}
}}
.into()
}
fn token_stream_error(span: Span, msg: &'static str) -> TokenStream {
TokenStream::from(syn::Error::new(span, msg).into_compile_error())
}
#[proc_macro_derive(EnumVec)]
pub fn derive_enum_vec(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let DeriveInput { ident, data, .. } = input.clone();
let enum_data = match data {
syn::Data::Enum(data_enum) => data_enum,
_ => {
return token_stream_error(input.span(), "Must be of type 'enum'.");
}
};
let mut enum_items = Vec::new();
for variant in enum_data.variants {
if variant.fields != Fields::Unit {
return token_stream_error(
variant.span(),
"Must be a unit type i.e. no EnumItemTuple | EnumItemStruct.\n enum Foo { Bar, Baz }",
);
}
let variant_ident = &variant.ident;
enum_items.push(quote! {#ident::#variant_ident});
}
let quoted = quote! {
#[automatically_derived]
impl #ident {
fn all_variants() -> Vec<Self> {
vec![#(#enum_items),*]
}
}
};
quoted.into()
}
#[proc_macro_derive(
EnumAsStr,
attributes(
Snake, Constant, UpperSnake, Ada, Kebab, Cobol, UpperKebab, Train, Flat, UpperFlat, Pascal,
UpperCamel, Camel, Lower, Upper, Title, Sentence, None
)
)]
pub fn derive_enum_as_str(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let DeriveInput {
ident, data, attrs, ..
} = input.clone();
let transform = CaseType::from_attributes(attrs);
let enum_data = match data {
syn::Data::Enum(data_enum) => data_enum,
_ => {
return token_stream_error(input.span(), "Must be an enum.");
}
};
let mut enum_items = Vec::new();
for variant in enum_data.variants {
if variant.fields != Fields::Unit {
return token_stream_error(variant.span(), "EnumAsStr only supports unit variants.");
}
let variant_transform = CaseType::from_attributes(variant.attrs);
let variant_ident = &variant.ident;
let value = if let Some(case) = variant_transform {
variant_ident.to_string().to_case(case.into())
} else if let Some(case) = transform {
variant_ident.to_string().to_case(case.into())
} else {
variant_ident.to_string()
};
enum_items.push(quote! {
Self::#variant_ident => #value
});
}
quote! {
#[automatically_derived]
impl #ident {
pub const fn as_str(&self) -> &'static str {
match self {
#(#enum_items),*
}
}
}
}
.into()
}