use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Attribute, Fields, ItemEnum, Meta, Token, parse::Parse, parse2, punctuated::Punctuated};
use crate::{AttributeParam, CongenItemAttribute};
pub fn enum_configuration(input: ItemEnum) -> TokenStream {
let mut errors = Vec::new();
let congen_attr = CongenItemAttribute::from_attrs(input.attrs.iter(), &mut errors);
let mut default_variant = None;
let variants: Vec<_> = input
.variants
.into_iter()
.map(|variant| {
let attrs = VariantAttribute::from_attrs(variant.attrs.iter(), &mut errors);
if attrs.is_default {
if default_variant.is_some() {
errors.push(syn::Error::new_spanned(
&variant,
"\"default\" must be specified at most once",
));
}
default_variant = Some(variant.ident.clone());
}
if !matches!(variant.fields, Fields::Unit) {
errors.push(syn::Error::new_spanned(
&variant,
"Configuration only supports Unit variants",
));
}
Variant { variant, attrs }
})
.collect();
let vis = &input.vis;
let typ = &input.ident;
let change_type = format_ident!("{}Change", input.ident);
let derive_debug = if congen_attr.debug {
quote! { #[derive(Debug)] }
} else {
quote! {}
};
let derive_clone = if congen_attr.clone {
quote! { #[derive(Clone)] }
} else {
quote! {}
};
let has_default = default_variant.is_some();
let enum_default = if let Some(default_variant) = default_variant.as_ref() {
quote! {
Ok(Self::#default_variant)
}
} else {
quote! {
Err(congen::internal::NotSupported)
}
};
let cong_default_impl = if default_variant.is_some() {
quote! {
impl congen::internal::CongenDefault for #typ {
}
}
} else {
quote! {}
};
let variant_from_str_matches = variants.iter().map(|variant| {
let ident = &variant.variant.ident;
let as_str = ident.to_string();
quote! {
#as_str => Ok(Ok(Self::Apply(#typ::#ident)))
}
});
let errors = errors.iter().map(|e| e.to_compile_error());
quote! {
#(#errors)*
impl congen::Configuration for #typ {
}
impl congen::internal::CongenInternal for #typ {
type CongenChange = #change_type;
fn apply_change_with_inner_default(
&mut self,
change: Self::CongenChange,
_inner_default: Option<fn() -> std::boxed::Box<dyn std::any::Any>>
) {
if let #change_type::Apply(value) = change {
*self = value;
}
}
fn description(field_name: &'static str) -> congen::internal::Description {
congen::internal::FieldDescription {
field_name,
type_name: Self::type_name(),
is_flag: false,
allow_unset: false,
has_default: #has_default,
clap_data: None,
}.into()
}
fn default() -> Result<Self, congen::internal::NotSupported> {
#enum_default
}
}
#cong_default_impl
#[derive(Default)]
#derive_debug
#derive_clone
#vis enum #change_type {
Apply(#typ),
#[default]
NoChange
}
impl congen::internal::CongenChange for #change_type {
type Configuration = #typ;
fn empty() -> Self {
Self::NoChange
}
fn parse(input: &std::ffi::OsStr)
-> std::result::Result<std::result::Result<Self, congen::internal::ParseError>, congen::internal::NotSupported>
{
let Some(input) = input.to_str() else {
return Ok(Err(congen::internal::ParseError("expected utf-8 encoded value".to_string())));
};
match input {
#(#variant_from_str_matches,)*
_ => Ok(Err(congen::internal::ParseError(format!("found unknown variant: {input}")))),
}
}
fn apply_change(&mut self, change: Self) {
if let Self::Apply(new_change) = change {
*self = Self::Apply(new_change);
}
}
fn from_path_and_verb<'a, P>(mut path: P, verb: congen::internal::ChangeVerb)
-> std::result::Result<Self, congen::internal::VerbError>
where
P: std::iter::Iterator<Item = &'a str>,
{
assert!(path.next().is_none(), concat!(stringify!(#change_type), " implies this is a field"));
match verb {
congen::internal::ChangeVerb::Set(value) => Ok(Self::parse(&value)??),
congen::internal::ChangeVerb::SetAny(value) => Ok(Self::Apply(
*value.downcast().map_err(|_| congen::internal::VerbError::DowncastFailed)?
)),
congen::internal::ChangeVerb::UseDefault =>
Ok(Self::Apply(<#typ as congen::internal::CongenInternal>::default()?)),
congen::internal::ChangeVerb::SetFlag
| congen::internal::ChangeVerb::Unset
| congen::internal::ChangeVerb::List(_) =>
Err(congen::internal::VerbError::UnsupportedVerb(verb)),
}
}
}
}
}
struct Variant {
variant: syn::Variant,
#[expect(unused)]
attrs: VariantAttribute,
}
#[derive(Default)]
struct VariantAttribute {
is_default: bool,
}
impl VariantAttribute {
fn combine(&self, other: &Self) -> Self {
Self {
is_default: self.is_default || other.is_default,
}
}
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(Self::default(), |a, b| a.combine(&b))
}
}
impl Parse for VariantAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args = Punctuated::<AttributeParam, Token![,]>::parse_terminated(input)?;
let mut is_default = false;
for arg in args {
match arg.ident().to_string().as_str() {
"default" => {
if is_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",
));
}
is_default = true;
}
_ => {
return Err(syn::Error::new_spanned(
arg,
"unknown argument to `congen` attribute",
));
}
}
}
Ok(VariantAttribute { is_default })
}
}