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