use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
pub trait Builder {
type Output;
fn build(self) -> Self::Output;
}
pub fn generate_builder_derive(attr: TokenStream, input: DeriveInput) -> TokenStream {
let struct_data = match &input.data {
syn::Data::Struct(v) => v,
_ => panic!("Auto-derive Builder can only be used in Struct"),
};
let struct_name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let vis = &input.vis;
let builder_struct_name: TokenStream = format!("{struct_name}Builder").parse().unwrap();
let input_struct = {
let generics = &input.generics;
let where_clause = &input.generics.where_clause;
let struct_fields = &struct_data
.fields
.iter()
.map(|field| {
quote! {
#field,
}
})
.collect::<Vec<TokenStream>>();
quote! {
#vis struct #struct_name #generics #where_clause {
#(#struct_fields)*
}
}
};
let builder_struct = {
let builder_struct_fields = struct_data
.fields
.iter()
.map(|field| {
let (ident, ty) = (field.ident.as_ref().unwrap(), &field.ty);
quote! {
#ident: Option<#ty>,
}
})
.collect::<Vec<TokenStream>>();
let init_each_field = struct_data
.fields
.iter()
.map(|field| {
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
quote! {
#ident: <Option<#ty> as std::default::Default>::default(), }
})
.collect::<Vec<TokenStream>>();
let methods = struct_data
.fields
.iter()
.map(|field| {
let ident: TokenStream = field.ident.as_ref().unwrap().to_string().parse().unwrap();
let ty = &field.ty;
let method_name: TokenStream = format!("with_{}", field.ident.as_ref().unwrap())
.parse()
.unwrap();
quote! {
pub fn #method_name(self, #ident: #ty) -> Self {
let #ident = Some(#ident);
Self {
#ident,
..self
}
}
}
})
.collect::<Vec<TokenStream>>();
let builder_struct_generics = &input.generics;
quote! {
#[derive(#attr)]
#vis struct #builder_struct_name #builder_struct_generics #where_clause {
#(#builder_struct_fields)*
}
impl #impl_generics #builder_struct_name #ty_generics #where_clause {
pub fn new() -> Self {
Self {
#(#init_each_field)*
}
}
pub fn build(self) -> <Self as crate::patternize::builder::Builder>::Output { <Self as crate::patternize::builder::Builder>::build(self)
}
#(#methods)*
}
}
};
let builder_trait_impl = {
let unwrap_each_field_impl = struct_data .fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.map(|ident| {
quote! {
let #ident = self.#ident?;
}
})
.collect::<Vec<TokenStream>>();
let output_fields = struct_data
.fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.map(|ident| quote! {#ident,})
.collect::<Vec<TokenStream>>();
quote! {
impl #impl_generics crate::patternize::builder::Builder for #builder_struct_name #ty_generics #where_clause {
type Output = Option<#struct_name #ty_generics>;
fn build(self) -> Self::Output {
#(#unwrap_each_field_impl)*
Some(#struct_name {
#(#output_fields)*
}) as Self::Output
}
}
}
};
quote! {
#input_struct
#builder_struct
#builder_trait_impl
}
}