cgp_macro_lib/entrypoints/
cgp_preset.rs1use 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}