patternize_internals 0.1.1

Internals of patternize
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

/// A trait provides `build()` method
///
/// - Auto implemented to struct generated by `#[generate_builder]`
/// - if there are any `None` in struct, it returns `None`
///
/// # Examples
/// ```
/// # extern crate patternize;
/// use patternize::builder::generate_builder;
/// // don't have to use for struct generated by `#[generate_builder]`
/// // use patternize::builder::Builder
///
///
/// #[derive(Debug, PartialEq)]
/// #[generate_builder]
/// struct Example {
///     data: usize
/// }
///
/// fn main(){
///     let example: Option<Example> = ExampleBuilder::new()
///     .with_data(0)
///     .build(); // same as <ExampleBuilder as Builder>::build(self)
///
///     let none = ExampleBuilder::new()
///     .build();
///
///     assert_eq!(none, None);
/// }
/// ```
pub trait Builder {
    type Output;
    fn build(self) -> Self::Output;
}

/// Generate Builder struct
/// #[GenerateBuilder]
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(), // TODO: don't use std
                }
            })
            .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 {// Option<#struct_name #ty_generics>
                    <Self as crate::patternize::builder::Builder>::build(self)
                }

                #(#methods)*
            }
        }
    };

    let builder_trait_impl = {
        let unwrap_each_field_impl = struct_data // TODO: variable name is inappropriate
            .fields
            .iter()
            .map(|field| field.ident.as_ref().unwrap())
            .map(|ident| {
                quote! {
                    // field type must be Option<T>
                    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
    }
}