use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use syn::DeriveInput;
use crate::utils::{ConfigContainer, ConfigContainerFields, ConfigField};
impl ConfigField {
fn example_initializer(
&self,
cr: &proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
let name = &self.name;
let name_span = self.name_span();
let val = if let Some(example) = &self.attrs.example {
quote!(#example)
} else if let Some(default) = &self.attrs.default {
default.instance(name_span)
} else if self.attrs.nest {
quote_spanned!(name_span=> #cr::ExampleConfig::example_config())
} else if Self::is_option(&self.ty) {
quote_spanned!(name_span=> ::core::option::Option::None)
} else {
let msg = "example or default value required to derive `ExampleConfig`";
return Err(syn::Error::new(name_span, msg));
};
Ok(quote_spanned!(name_span=> #name: #val))
}
}
impl ConfigContainer {
fn derive_example(&self) -> syn::Result<proc_macro2::TokenStream> {
let name = &self.name;
let cr = self.cr(Span::call_site());
let example_impl = match &self.fields {
ConfigContainerFields::Struct(fields) => {
let fields: syn::Result<Vec<_>> = fields
.iter()
.map(|field| field.example_initializer(&cr))
.collect();
let fields = fields?;
quote!(Self { #(#fields,)* })
}
ConfigContainerFields::Enum { .. } => {
let mut msg = "Deriving `ExampleConfig` for enum configs isn't supported yet; implement `ExampleConfig` manually".to_owned();
if self.attrs.derive_default {
msg += " (e.g., by delegating to `Default::default()`)";
}
return Err(syn::Error::new(name.span(), msg));
}
};
Ok(quote! {
impl #cr::ExampleConfig for #name {
fn example_config() -> Self {
#example_impl
}
}
})
}
}
pub(crate) fn impl_example_config(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
let trait_impl = match ConfigContainer::new(&input) {
Ok(trait_impl) => trait_impl,
Err(err) => return err.into_compile_error().into(),
};
match trait_impl.derive_example() {
Ok(derived) => derived.into(),
Err(err) => err.into_compile_error().into(),
}
}