use quote::{ToTokens, format_ident, quote};
use syn::{
Attribute, Ident, Item, Meta, Token, parse::Parse, parse_macro_input, parse2,
punctuated::Punctuated,
};
use crate::{enum_config::enum_configuration, struct_config::struct_configuration};
mod enum_config;
mod struct_config;
#[proc_macro_derive(Configuration, attributes(congen))]
pub fn configuration(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as Item);
match input {
Item::Struct(input) => struct_configuration(input),
Item::Enum(input) => enum_configuration(input),
_ => quote! { compile_error!("Configuration only supports structs and enums") },
}
.into()
}
enum AttributeParam {
Flag(Ident),
NameValue {
ident: Ident,
value: syn::Expr,
eq: Token![=],
},
}
impl AttributeParam {
pub fn ident(&self) -> &Ident {
match self {
AttributeParam::Flag(ident) => ident,
AttributeParam::NameValue { ident, .. } => ident,
}
}
}
impl Parse for AttributeParam {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
if input.peek(Token![=]) {
let eq = input.parse()?;
let value = input.parse()?;
Ok(AttributeParam::NameValue { ident, value, eq })
} else {
Ok(AttributeParam::Flag(ident))
}
}
}
impl ToTokens for AttributeParam {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
AttributeParam::Flag(ident) => ident.to_tokens(tokens),
AttributeParam::NameValue { ident, value, eq } => {
ident.to_tokens(tokens);
eq.to_tokens(tokens);
value.to_tokens(tokens);
}
}
}
}
#[derive(Default)]
struct CongenItemAttribute {
debug: bool,
default: bool,
clone: bool,
}
impl CongenItemAttribute {
fn combine(&self, other: &Self) -> Self {
Self {
debug: self.debug || other.debug,
default: self.default || other.default,
clone: self.clone || other.clone,
}
}
fn from_attrs<'a, I: IntoIterator<Item = &'a Attribute>>(
iter: I,
errors: &mut Vec<syn::Error>,
) -> Self {
iter.into_iter()
.filter_map(|attr| match &attr.meta {
Meta::List(meta_list) if meta_list.path.is_ident(&format_ident!("congen")) => {
match parse2(meta_list.tokens.clone()) {
Ok(attr) => Some(attr),
Err(err) => {
errors.push(err);
None
}
}
}
_ => None,
})
.fold(CongenItemAttribute::default(), |a, b| a.combine(&b))
}
}
impl Parse for CongenItemAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args = Punctuated::<AttributeParam, Token![,]>::parse_terminated(input)?;
let mut impl_debug = false;
let mut gen_default = false;
let mut clone = false;
for arg in args {
match arg.ident().to_string().as_str() {
"default" => {
if gen_default {
return Err(syn::Error::new_spanned(
arg,
"\"default\" should only be specified once in `congen` attribute",
));
}
if !matches!(arg, AttributeParam::Flag(_)) {
return Err(syn::Error::new_spanned(
arg,
"\"default\" is a flag and takes no arguments",
));
}
gen_default = true;
}
"debug" => {
if impl_debug {
return Err(syn::Error::new_spanned(
arg,
"\"debug\" should only be specified once in `congen` attribute",
));
}
if !matches!(arg, AttributeParam::Flag(_)) {
return Err(syn::Error::new_spanned(
arg,
"\"debug\" is a flag and takes no arguments",
));
}
impl_debug = true;
}
"clone" => {
if clone {
return Err(syn::Error::new_spanned(
arg,
"\"clone\" should only be specified once in `congen` attribute",
));
}
if !matches!(arg, AttributeParam::Flag(_)) {
return Err(syn::Error::new_spanned(
arg,
"\"clone\" is a flag and takes no arguments",
));
}
clone = true;
}
_ => {
return Err(syn::Error::new_spanned(
arg,
"unknown argument to `congen` attribute",
));
}
}
}
Ok(CongenItemAttribute {
debug: impl_debug,
default: gen_default,
clone,
})
}
}