dwind_macros/
lib.rs

1mod codegen;
2pub(crate) mod grammar;
3mod macro_inputs;
4use crate::codegen::string_rendering::class_name_to_struct_identifier;
5use crate::codegen::{render_classes, render_generate_dwind_class};
6use crate::grammar::{parse_class_string, parse_selector};
7use crate::macro_inputs::DwindInputSignal;
8use dwind_base::media_queries::Breakpoint;
9use macro_inputs::{DwGenerateInput, DwGenerateMapInput, DwindInput};
10use proc_macro::TokenStream;
11use quote::quote;
12use std::rc::Rc;
13
14/// Use dwind-macros macros on your DOMINATOR component
15///
16/// Basic usage:
17///
18/// # Example
19/// ```rust,no_run
20/// # use dominator::class;
21/// # const SOME_CLASS: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {class! { .raw("")}});
22/// # const SOME_OTHER_CLASS_RAW: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {class! { .raw("")}});
23/// use dwind_macros::dwclass;
24/// dominator::html!("div", {
25///     .dwclass!("some_class [>*]:hover:some-other-class")
26/// });
27/// ```
28#[proc_macro]
29pub fn dwclass(input: TokenStream) -> TokenStream {
30    let DwindInput {
31        self_ident,
32        classes,
33    } = syn::parse::<DwindInput>(input).unwrap();
34
35    let classes = parse_class_string(classes.value().as_str()).unwrap();
36    let classes = render_classes(classes);
37
38    let (generator_classes, normal_classes): (Vec<_>, Vec<_>) = classes
39        .into_iter()
40        .map(|class| match class {
41            (tokens, breakpoint, false) => (None, Some((tokens, breakpoint))),
42            (tokens, breakpoint, true) => (Some((tokens, breakpoint)), None),
43        })
44        .unzip();
45
46    let v = Rc::new(Some(""));
47
48    v.as_ref().as_ref().map(|_v| {});
49
50    let classes = normal_classes.into_iter().filter_map(|v| v).map(|(class, breakpoint)| {
51        if let Some(breakpoint) = breakpoint {
52            let bp = breakpoint.breakpoint;
53
54            if breakpoint.is_media_query {
55                let Breakpoint::MediaQuery(mq) = bp  else { unreachable!() };
56
57                quote! {
58                    .class_signal(#class, dominator::media_query(#mq))
59                }
60            } else {
61                let bp_value = match bp {
62                    Breakpoint::VerySmall => quote! { dwind::prelude::media_queries::Breakpoint::VerySmall },
63                    Breakpoint::Small => quote! { dwind::prelude::media_queries::Breakpoint::Small },
64                    Breakpoint::Medium => quote! { dwind::prelude::media_queries::Breakpoint::Medium },
65                    Breakpoint::Large => quote! { dwind::prelude::media_queries::Breakpoint::Large },
66                    Breakpoint::VeryLarge => quote! { dwind::prelude::media_queries::Breakpoint::VeryLarge },
67                    _ => { panic!("media query breakpoint not supported here")}
68                };
69
70                if let Some(_modifier) = breakpoint.modifier {
71                    quote! {
72                        .class_signal(#class, dwind::prelude::media_queries::breakpoint_less_than_signal(#bp_value))
73                    }
74                    } else {
75                        quote! {
76                        .class_signal(#class, dwind::prelude::media_queries::breakpoint_active_signal(#bp_value))
77                    }
78                }
79            }
80        } else {
81            quote! {
82                .class(#class)
83            }
84        }
85    });
86
87    let gen_classes = generator_classes.into_iter().filter_map(|v| v).map(|(class_tokens, breakpoint)| {
88        if let Some(breakpoint) = breakpoint {
89            let bp = breakpoint.breakpoint;
90
91            let apply = if breakpoint.is_media_query {
92                let Breakpoint::MediaQuery(mq) = bp  else { unreachable!() };
93
94                quote! {
95                    .class_signal(&*foobar, dominator::media_query(#mq))
96                }
97            } else {
98                let bp_value = match bp {
99                    Breakpoint::VerySmall => quote! { dwind::prelude::media_queries::Breakpoint::VerySmall },
100                    Breakpoint::Small => quote! { dwind::prelude::media_queries::Breakpoint::Small },
101                    Breakpoint::Medium => quote! { dwind::prelude::media_queries::Breakpoint::Medium },
102                    Breakpoint::Large => quote! { dwind::prelude::media_queries::Breakpoint::Large },
103                    Breakpoint::VeryLarge => quote! { dwind::prelude::media_queries::Breakpoint::VeryLarge },
104                    _ => { panic!("media query breakpoint not supported here")}
105                };
106
107                if let Some(_modifier) = breakpoint.modifier {
108                    quote! {
109                        .class_signal(&*foobar, dwind::prelude::media_queries::breakpoint_less_than_signal(#bp_value))
110                    }
111                } else {
112                    quote! {
113                        .class_signal(&*foobar, dwind::prelude::media_queries::breakpoint_active_signal(#bp_value))
114                    }
115                }
116            };
117
118            quote! {
119               let #self_ident = {
120                    #[doc(hidden)]
121                    pub static foobar: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {
122                        #class_tokens
123                    });
124
125                    #self_ident . #apply
126                };
127            }
128        } else {
129            quote! {
130               let #self_ident = {
131                    #[doc(hidden)]
132                    pub static foobar: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {
133                        #class_tokens
134                    });
135
136                    #self_ident . class(&*foobar)
137                };
138            }
139        }
140    });
141
142    quote! {
143        {
144            #(#gen_classes)*
145            #self_ident #(#classes)*
146        }
147    }
148    .into()
149}
150
151/// Use dwind-macros macros on your DOMINATOR component
152///
153/// Basic usage:
154///
155/// # Example
156/// ```rust,no_run
157/// # use dominator::class;
158/// # use futures_signals::signal::always;
159/// # static SOME_CLASS: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {class! { .raw("")}});
160/// use dwind_macros::{dwclass, dwclass_signal};
161/// dominator::html!("div", {
162///     .dwclass_signal!("some_class", always(true))
163/// });
164/// ```
165#[proc_macro]
166pub fn dwclass_signal(input: TokenStream) -> TokenStream {
167    let DwindInputSignal {
168        input: DwindInput {
169            self_ident,
170            classes,
171        },
172        signal,
173    } = syn::parse::<DwindInputSignal>(input).unwrap();
174
175    let classes = parse_class_string(classes.value().as_str()).unwrap();
176    let classes = render_classes(classes);
177
178    let (generator_classes, normal_classes): (Vec<_>, Vec<_>) = classes
179        .into_iter()
180        .map(|class| match class {
181            (tokens, breakpoint, false) => (None, Some((tokens, breakpoint))),
182            (tokens, breakpoint, true) => (Some((tokens, breakpoint)), None),
183        })
184        .unzip();
185
186    let classes = normal_classes
187        .into_iter()
188        .filter_map(|v| v)
189        .map(|(class, _breakpoint)| {
190            quote! {
191                .class_signal(#class, #signal)
192            }
193        });
194
195    let gen_classes = generator_classes.into_iter().filter_map(|v| v).map(|(class_tokens, _breakpoint)| {
196        quote! {
197           let #self_ident = {
198                #[doc(hidden)]
199                pub static foobar: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| {
200                    #class_tokens
201                });
202
203                #self_ident . class_signal(&*foobar, #signal)
204            };
205        }
206    });
207
208    quote! {
209        {
210            #(#gen_classes)*
211            #self_ident #(#classes)*
212        }
213    }
214    .into()
215}
216
217/// Generates a dwind class that can later be used by the 'dwclass!()' macro.
218///
219/// Using this will create a lazy static in the scope from which the macro is invoked, so it can be used to create
220/// styling modules.
221///
222///
223///
224/// # Examples
225///
226/// ```rust,no_run
227/// # use dwind_macros::dwclass;
228/// # use dominator::html;
229/// mod my_custom_theme {
230///     use dwind_macros::dwgenerate;
231///     macro_rules! padding_generator {
232///         ($padding:tt) => {
233///             const_format::formatcp!("padding: {};", $padding)
234///         };
235///     }
236///
237///     dwgenerate!("nth-2-padding", "nth-child(2):hover:padding-[20px]");
238/// }
239///
240/// use my_custom_theme::*;
241///
242/// // Now use the generated pseudoclass on an html element
243/// html!("div", {
244///   .text("hi there")
245///   .dwclass!("nth-2-padding")
246/// });
247/// ```
248#[proc_macro]
249pub fn dwgenerate(input: TokenStream) -> TokenStream {
250    let DwGenerateInput {
251        class_definition,
252        class_name,
253    } = syn::parse(input).unwrap();
254
255    let class_definition = parse_selector(class_definition.value().as_str())
256        .map(|v| v.1)
257        .unwrap();
258
259    let class_name = class_name_to_struct_identifier(&class_name.value());
260
261    let rendered = render_generate_dwind_class(class_name, class_definition);
262
263    rendered.into()
264}
265
266///
267/// # Examples
268///
269/// ```rust,no_run
270/// # use dwind_macros::dwgenerate_map;
271/// # use dominator::html;
272///
273/// macro_rules! bg_color_generator {
274///    ($color:tt) => {
275///     const_format::formatcp!("background-color: {};", $color)
276///    };
277/// }
278///
279/// dwgenerate_map!("bg-color-hover", "hover:bg-color-", [
280///     ("blue-900", "#aaaafa"),
281///     ("blue-800", "#9999da"),
282///     ("blue-700", "#8888ca"),
283///     ("blue-600", "#7777ba"),
284///     ("blue-500", "#6666aa"),
285///     ("blue-400", "#55559a"),
286///     ("blue-300", "#44448a"),
287///     ("blue-200", "#33337a"),
288///     ("blue-100", "#22226a"),
289///     ("blue-50", "#11115a")
290/// ]);
291/// ```
292#[proc_macro]
293pub fn dwgenerate_map(input: TokenStream) -> TokenStream {
294    let input: DwGenerateMapInput = syn::parse(input).unwrap();
295
296    let DwGenerateMapInput {
297        base_output_ident,
298        dwind_class_lit,
299        args,
300    } = input;
301
302    let output = args.tuples.into_iter().map(|input_tuple| {
303        // create the full name of the new class
304        let ident_name = class_name_to_struct_identifier(&format!(
305            "{}-{}",
306            base_output_ident.value(),
307            input_tuple.first.value()
308        ));
309
310        // create generator dwind string literal
311        let class_literal = format!(
312            "{}[{}]",
313            dwind_class_lit.value(),
314            input_tuple.second.value()
315        );
316
317        // parse the generator string into a class selector
318        let class = parse_selector(class_literal.as_str()).unwrap().1;
319
320        render_generate_dwind_class(ident_name, class)
321    });
322
323    quote! {
324        #(#output)*
325    }
326    .into()
327}