cgp_macro_lib/entrypoints/
cgp_preset.rs

1use std::collections::HashSet;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{ToTokens, TokenStreamExt, quote};
5use syn::punctuated::Punctuated;
6use syn::token::{At, Comma};
7use syn::{GenericParam, Ident, ItemTrait, TypeParamBound, parse_quote, parse2};
8
9use crate::delegate_components::{define_struct, impl_delegate_components};
10use crate::parse::{DefinePreset, DelegateEntry, ImplGenerics, SimpleType};
11use crate::preset::{define_substitution_macro, impl_components_is_preset};
12use crate::replace_self::to_snake_case_str;
13
14pub fn define_preset(body: TokenStream) -> syn::Result<TokenStream> {
15    let ast: DefinePreset = syn::parse2(body)?;
16
17    let delegate_entries: Punctuated<DelegateEntry<SimpleType>, Comma> = ast
18        .delegate_entries
19        .iter()
20        .map(|entry| entry.entry.clone())
21        .collect();
22
23    let mut parent_presets = ast.parent_presets.clone();
24
25    let mut remaining_parents = parent_presets
26        .iter_mut()
27        .filter(|parent| parent.has_expanded.is_none());
28
29    let m_parent = if let Some(parent_preset) = remaining_parents.next() {
30        parent_preset.has_expanded = Some(At(Span::call_site()));
31        Some(parent_preset.parent_type.clone())
32    } else {
33        None
34    };
35
36    if let Some(parent) = m_parent {
37        let parent_ident = &parent.name;
38        let parent_generics = &parent.generics;
39
40        let parent_components_ident = Ident::new(
41            &format!("__{parent_ident}Components__"),
42            parent_ident.span(),
43        );
44
45        let wrapper_attribute = match ast.provider_wrapper {
46            Some(wrapper) => quote! {
47                #[wrap_provider( #wrapper )]
48            },
49            None => TokenStream::new(),
50        };
51
52        let preset_type_spec = &ast.preset;
53
54        let mut overrides: Punctuated<&Ident, Comma> = Punctuated::default();
55
56        for entry in ast.delegate_entries.iter() {
57            if entry.is_override.is_some() {
58                for component in entry.entry.keys.iter() {
59                    overrides.push(&component.ty.name);
60                }
61            }
62        }
63
64        let filter = if !overrides.is_empty() {
65            quote! {
66                [ #overrides ],
67            }
68        } else {
69            TokenStream::new()
70        };
71
72        let preset_entries = &ast.delegate_entries;
73
74        let output = quote! {
75            use #parent_ident ::components::*;
76
77            #parent_ident :: with_components! {
78                #filter
79                | #parent_components_ident | {
80                    cgp_preset! {
81                        #wrapper_attribute
82                        #preset_type_spec: #parent_presets {
83                            #parent_components_ident -> #parent_ident :: Components #parent_generics,
84                            #preset_entries
85                        }
86                    }
87                }
88            }
89        };
90
91        return Ok(output);
92    }
93
94    let provider_struct_name = Ident::new("Components", Span::call_site());
95
96    let preset_module_name = &ast.preset.name;
97
98    let preset_generic_args = &ast.preset.generics;
99
100    let preset_generics: ImplGenerics = syn::parse2(quote!( #preset_generic_args ))?;
101
102    let provider_type = {
103        let type_generics = preset_generics.as_type_generics();
104        parse2(quote! { #provider_struct_name #type_generics })?
105    };
106
107    let preset_trait_name = Ident::new("IsPreset", Span::call_site());
108
109    let preset_trait: ItemTrait = parse_quote! {
110        #[doc(hidden)]
111        pub trait #preset_trait_name <Component> {}
112    };
113
114    let impl_delegate_items = {
115        let namespaces_preset_type = parse2(quote! {
116            #preset_module_name :: #provider_type
117        })?;
118
119        let items =
120            impl_delegate_components(&namespaces_preset_type, &preset_generics, &delegate_entries)?;
121
122        let mut stream = TokenStream::new();
123        stream.append_all(items);
124
125        stream
126    };
127
128    let impl_is_preset_items = impl_components_is_preset(
129        &preset_trait_name,
130        &provider_type,
131        &preset_generics,
132        &delegate_entries,
133    );
134
135    let provider_struct = define_struct(&provider_struct_name, &preset_generics.generics)?;
136
137    let export_provider = match ast.provider_wrapper {
138        Some(wrapper) => {
139            let (impl_generics, type_generics, _) = preset_generics.generics.split_for_impl();
140
141            quote! {
142                pub type Provider #impl_generics = #wrapper < #provider_struct_name #type_generics >;
143            }
144        }
145        None => {
146            quote! {
147                pub use #provider_struct_name as Provider;
148            }
149        }
150    };
151
152    let mut mod_output = quote! {
153        #provider_struct
154
155        #export_provider
156
157        #preset_trait
158    };
159
160    mod_output.append_all(impl_is_preset_items);
161
162    {
163        let with_components_macro_name = Ident::new(
164            &format!(
165                "with_{}",
166                to_snake_case_str(&preset_module_name.to_string())
167            ),
168            Span::call_site(),
169        );
170
171        let all_components: Punctuated<_, Comma> = delegate_entries
172            .iter()
173            .flat_map(|entry| entry.keys.clone().into_iter())
174            .collect();
175
176        let with_components_macro = define_substitution_macro(
177            &with_components_macro_name,
178            &all_components.to_token_stream(),
179        );
180
181        mod_output.extend(with_components_macro);
182        mod_output.extend(quote! {
183            pub use #with_components_macro_name as with_components;
184        })
185    }
186
187    let re_exports_mod = {
188        let mut parent_exports = TokenStream::new();
189
190        for parent in parent_presets.iter() {
191            let parent_ident = &parent.parent_type.name;
192            parent_exports.append_all(quote! {
193                #[doc(hidden)]
194                #[doc(no_inline)]
195                pub use super:: #parent_ident ::components::*;
196            });
197        }
198
199        quote! {
200            #[doc(hidden)]
201            #[allow(unused_imports)]
202            mod re_exports {
203                #[doc(hidden)]
204                #[doc(no_inline)]
205                pub use super::super::super::re_exports::*;
206
207                #[doc(hidden)]
208                #[doc(no_inline)]
209                pub use super::super::*;
210
211                #parent_exports
212            }
213        }
214    };
215
216    let components_mod = {
217        let mut components: HashSet<Ident> = HashSet::default();
218
219        for entry in delegate_entries.iter() {
220            for component in entry.keys.iter() {
221                let component_name = &component.ty.name;
222                components.insert(component_name.clone());
223
224                for param in component.generics.generics.params.iter() {
225                    if let GenericParam::Type(param) = param {
226                        for bound in param.bounds.iter() {
227                            if let TypeParamBound::Trait(bound) = bound
228                                && let Some(segment) = bound.path.segments.first()
229                            {
230                                components.insert(segment.ident.clone());
231                            }
232                        }
233                    }
234                }
235            }
236        }
237
238        let components_list: Punctuated<Ident, Comma> = Punctuated::from_iter(components);
239
240        quote! {
241            #[doc(hidden)]
242            pub mod components {
243                #[doc(hidden)]
244                #[doc(no_inline)]
245                pub use super::re_exports::{ #components_list };
246            }
247        }
248    };
249
250    mod_output.append_all(re_exports_mod);
251    mod_output.append_all(components_mod);
252
253    let output = quote! {
254        #impl_delegate_items
255
256        #[allow(non_snake_case)]
257        pub mod #preset_module_name {
258            use super::*;
259
260            #mod_output
261        }
262    };
263
264    Ok(output)
265}