cgp-macro-lib 0.7.0

Context-generic programming core component macros implemented as a library.
Documentation
use std::collections::HashSet;

use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt, quote};
use syn::punctuated::Punctuated;
use syn::token::{At, Comma};
use syn::{GenericParam, Ident, ItemTrait, TypeParamBound, parse_quote, parse2};

use crate::delegate_components::{define_struct, impl_delegate_components};
use crate::parse::{DefinePreset, DelegateEntry, ImplGenerics, SimpleType};
use crate::preset::{define_substitution_macro, impl_components_is_preset};
use crate::replace_self::to_snake_case_str;

pub fn define_preset(body: TokenStream) -> syn::Result<TokenStream> {
    let ast: DefinePreset = syn::parse2(body)?;

    let delegate_entries: Punctuated<DelegateEntry<SimpleType>, Comma> = ast
        .delegate_entries
        .iter()
        .map(|entry| entry.entry.clone())
        .collect();

    let mut parent_presets = ast.parent_presets.clone();

    let mut remaining_parents = parent_presets
        .iter_mut()
        .filter(|parent| parent.has_expanded.is_none());

    let m_parent = if let Some(parent_preset) = remaining_parents.next() {
        parent_preset.has_expanded = Some(At(Span::call_site()));
        Some(parent_preset.parent_type.clone())
    } else {
        None
    };

    if let Some(parent) = m_parent {
        let parent_ident = &parent.name;
        let parent_generics = &parent.generics;

        let parent_components_ident = Ident::new(
            &format!("__{parent_ident}Components__"),
            parent_ident.span(),
        );

        let wrapper_attribute = match ast.provider_wrapper {
            Some(wrapper) => quote! {
                #[wrap_provider( #wrapper )]
            },
            None => TokenStream::new(),
        };

        let preset_type_spec = &ast.preset;

        let mut overrides: Punctuated<&Ident, Comma> = Punctuated::default();

        for entry in ast.delegate_entries.iter() {
            if entry.is_override.is_some() {
                for component in entry.entry.keys.iter() {
                    overrides.push(&component.ty.name);
                }
            }
        }

        let filter = if !overrides.is_empty() {
            quote! {
                [ #overrides ],
            }
        } else {
            TokenStream::new()
        };

        let preset_entries = &ast.delegate_entries;

        let output = quote! {
            use #parent_ident ::components::*;

            #parent_ident :: with_components! {
                #filter
                | #parent_components_ident | {
                    cgp_preset! {
                        #wrapper_attribute
                        #preset_type_spec: #parent_presets {
                            #parent_components_ident -> #parent_ident :: Components #parent_generics,
                            #preset_entries
                        }
                    }
                }
            }
        };

        return Ok(output);
    }

    let provider_struct_name = Ident::new("Components", Span::call_site());

    let preset_module_name = &ast.preset.name;

    let preset_generic_args = &ast.preset.generics;

    let preset_generics: ImplGenerics = syn::parse2(quote!( #preset_generic_args ))?;

    let provider_type = {
        let type_generics = preset_generics.as_type_generics();
        parse2(quote! { #provider_struct_name #type_generics })?
    };

    let preset_trait_name = Ident::new("IsPreset", Span::call_site());

    let preset_trait: ItemTrait = parse_quote! {
        #[doc(hidden)]
        pub trait #preset_trait_name <Component> {}
    };

    let impl_delegate_items = {
        let namespaces_preset_type = parse2(quote! {
            #preset_module_name :: #provider_type
        })?;

        let items =
            impl_delegate_components(&namespaces_preset_type, &preset_generics, &delegate_entries)?;

        let mut stream = TokenStream::new();
        stream.append_all(items);

        stream
    };

    let impl_is_preset_items = impl_components_is_preset(
        &preset_trait_name,
        &provider_type,
        &preset_generics,
        &delegate_entries,
    );

    let provider_struct = define_struct(&provider_struct_name, &preset_generics.generics)?;

    let export_provider = match ast.provider_wrapper {
        Some(wrapper) => {
            let (impl_generics, type_generics, _) = preset_generics.generics.split_for_impl();

            quote! {
                pub type Provider #impl_generics = #wrapper < #provider_struct_name #type_generics >;
            }
        }
        None => {
            quote! {
                pub use #provider_struct_name as Provider;
            }
        }
    };

    let mut mod_output = quote! {
        #provider_struct

        #export_provider

        #preset_trait
    };

    mod_output.append_all(impl_is_preset_items);

    {
        let with_components_macro_name = Ident::new(
            &format!(
                "with_{}",
                to_snake_case_str(&preset_module_name.to_string())
            ),
            Span::call_site(),
        );

        let all_components: Punctuated<_, Comma> = delegate_entries
            .iter()
            .flat_map(|entry| entry.keys.clone().into_iter())
            .collect();

        let with_components_macro = define_substitution_macro(
            &with_components_macro_name,
            &all_components.to_token_stream(),
        );

        mod_output.extend(with_components_macro);
        mod_output.extend(quote! {
            pub use #with_components_macro_name as with_components;
        })
    }

    let re_exports_mod = {
        let mut parent_exports = TokenStream::new();

        for parent in parent_presets.iter() {
            let parent_ident = &parent.parent_type.name;
            parent_exports.append_all(quote! {
                #[doc(hidden)]
                #[doc(no_inline)]
                pub use super:: #parent_ident ::components::*;
            });
        }

        quote! {
            #[doc(hidden)]
            #[allow(unused_imports)]
            mod re_exports {
                #[doc(hidden)]
                #[doc(no_inline)]
                pub use super::super::super::re_exports::*;

                #[doc(hidden)]
                #[doc(no_inline)]
                pub use super::super::*;

                #parent_exports
            }
        }
    };

    let components_mod = {
        let mut components: HashSet<Ident> = HashSet::default();

        for entry in delegate_entries.iter() {
            for component in entry.keys.iter() {
                let component_name = &component.ty.name;
                components.insert(component_name.clone());

                for param in component.generics.generics.params.iter() {
                    if let GenericParam::Type(param) = param {
                        for bound in param.bounds.iter() {
                            if let TypeParamBound::Trait(bound) = bound
                                && let Some(segment) = bound.path.segments.first()
                            {
                                components.insert(segment.ident.clone());
                            }
                        }
                    }
                }
            }
        }

        let components_list: Punctuated<Ident, Comma> = Punctuated::from_iter(components);

        quote! {
            #[doc(hidden)]
            pub mod components {
                #[doc(hidden)]
                #[doc(no_inline)]
                pub use super::re_exports::{ #components_list };
            }
        }
    };

    mod_output.append_all(re_exports_mod);
    mod_output.append_all(components_mod);

    let output = quote! {
        #impl_delegate_items

        #[allow(non_snake_case)]
        pub mod #preset_module_name {
            use super::*;

            #mod_output
        }
    };

    Ok(output)
}