use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, LitStr};
#[proc_macro_derive(KindStr, attributes(kind))]
pub fn derive_kind_str(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let self_name = &input.ident;
let de = match &input.data {
Data::Enum(DataEnum { variants, .. }) => variants,
_ => {
return syn::Error::new_spanned(self_name, "KindStr requires an enum")
.to_compile_error()
.into();
}
};
let mut as_str_arms = Vec::new();
let mut from_str_arms = Vec::new();
let mut errors = Vec::new();
for v in de.iter() {
let variant_name = &v.ident;
if !matches!(v.fields, Fields::Unit) {
errors
.push(
syn::Error::new_spanned(
variant_name,
"KindStr requires unit (fieldless) variants",
)
.to_compile_error(),
);
continue;
}
let mut name = variant_name.to_string();
let mut aliases: Vec<String> = Vec::new();
for attr in &v.attrs {
if !attr.path().is_ident("kind") {
continue;
}
let r = attr
.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let s: LitStr = meta.value()?.parse()?;
name = s.value();
} else if meta.path.is_ident("alias") {
let s: LitStr = meta.value()?.parse()?;
aliases.push(s.value());
} else {
return Err(
meta.error("unknown `kind` key (expected name/alias/byte)"),
);
}
Ok(())
});
if let Err(e) = r {
errors.push(e.to_compile_error());
}
}
as_str_arms
.push(
quote! {
Self::# variant_name => # name,
},
);
from_str_arms
.push(
quote! {
# name => ::core::option::Option::Some(Self::# variant_name),
},
);
for a in &aliases {
from_str_arms
.push(
quote! {
# a => ::core::option::Option::Some(Self::# variant_name),
},
);
}
}
if !errors.is_empty() {
let mut ts = proc_macro2::TokenStream::new();
for e in errors {
ts.extend(e);
}
return ts.into();
}
let expanded = quote! {
impl # self_name { #[doc =
"Wire string for this variant (paired inverse of `from_str_kind`)."] pub fn
as_str(& self) -> &'static str { match self { # (# as_str_arms) * } } #[doc =
"Parse a variant from its wire string (paired inverse of `as_str`)."] pub fn
from_str_kind(s : & str) -> ::core::option::Option < Self > { match s { # (#
from_str_arms) * _ => ::core::option::Option::None, } } }
};
TokenStream::from(expanded)
}