css_in_rs_macro/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::ToTokens;
5
6mod data;
7mod output;
8mod result;
9
10/// Introduces dynamic CSS code which can be injected.
11///
12/// # Example
13/// ```
14/// # use css_in_rs_macro::make_styles;
15/// #[derive(Clone)]
16/// struct MyTheme {
17///     pub primary_color: String,
18///     pub header_color: String,
19/// }
20/// impl css_in_rs::Theme for MyTheme {
21///     /* ... */
22///     # fn fast_cmp(&self, other: &MyTheme) -> bool { false }
23/// }
24///
25/// make_styles! {
26///     (theme: MyTheme) -> MyClasses {
27///         "*" {
28///             padding: "0px",
29///         },
30///         text {
31///             margin: "10px",
32///             color: theme.primary_color,
33///         },
34///         header {
35///             margin: "20px",
36///             color: theme.header_color,
37///         },
38///     }
39/// }
40/// ```
41/// This essentially expands to
42/// ```
43/// // Generated code; expanded form of the macro above
44/// struct MyClasses {
45///     pub text: String,
46///     pub header: String,
47/// }
48///
49/// impl ::css_in_rs::Classes for MyClasses {
50///     # type Theme = css_in_rs::EmptyTheme;
51///     # fn generate(_a: &css_in_rs::EmptyTheme, _b: &mut String, _c: &mut u64) {}
52///     # fn new(_: u64) -> Self { todo!() }
53///     /* ... */
54/// }
55/// ```
56/// You can inject this style into the DOM using a `StyleProvider` (see
57/// css-in-rs crate). It will hand you a `MyClasses` instance with uniquely
58/// generated classnames (usually something like `css-17`).
59#[proc_macro]
60pub fn make_styles(input: TokenStream) -> TokenStream {
61    let style = syn::parse_macro_input!(input as data::Style);
62
63    let result = result::Result::new(style);
64    let expanded = result.to_token_stream();
65
66    // Convert the generated code to a TokenStream
67    TokenStream::from(expanded)
68}
69
70#[cfg(test)]
71mod tests {
72    use quote::{quote, ToTokens};
73
74    use crate::{data::Style, result};
75
76    #[test]
77    fn simple() {
78        let input = quote! {
79            (theme: MyTheme) -> MyClasses {
80                "div.red_color" {
81                    color: "red",
82                },
83                "div.blue_color" {
84                    color: "blue",
85                },
86                "@media (hover: none)" {
87                    "body *" {
88                        pointer_events: "none",
89                    },
90                    some_class {
91                    },
92                },
93            }
94        };
95
96        let style = syn::parse2::<Style>(input).unwrap();
97        let result = result::Result::new(style);
98
99        // Uncomment to see nice version of the output
100        /*{
101            let ts = result.to_token_stream();
102            let file = syn::parse2::<syn::File>(ts).unwrap();
103            let nice = prettyplease::unparse(&file);
104            eprintln!("{nice}");
105            todo!();
106        }*/
107
108        let result = result.to_token_stream().to_string();
109
110        let expected = quote! {
111            struct MyClasses {
112                pub blue_color: String,
113                pub red_color: String,
114                pub some_class: String,
115            }
116
117            impl ::css_in_rs::Classes for MyClasses {
118                type Theme = MyTheme;
119
120                fn generate(theme: &Self::Theme, css: &mut String, counter: &mut u64) {
121                    use ::core::fmt::Write;
122                    let start = *counter;
123                    let _ = write!(
124                        css,
125                        "div.css-{} {{\n  {}: {};\n}}\ndiv.css-{} {{\n  {}: {};\n}}\n@media (hover: none) {{\nbody * {{\n  {}: {};\n}}\n.css-{} {{\n}}\n}}\n",
126                        start + 1u64, "color", "red", start + 0u64, "color", "blue",
127                        "pointer-events", "none", start + 2u64
128                    );
129                    *counter = start + 3u64;
130                }
131                fn new(start: u64) -> Self {
132                    Self {
133                        blue_color: format!("css-{}", start + 0u64),
134                        red_color: format!("css-{}", start + 1u64),
135                        some_class: format!("css-{}", start + 2u64),
136                    }
137                }
138            }
139        };
140
141        assert_eq!(result, expected.to_string());
142    }
143}