1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::ToTokens;

mod data;
mod output;
mod result;

/// Introduces dynamic CSS code which can be injected.
///
/// # Example
/// ```
/// # use css_in_rs_macro::make_styles;
/// #[derive(Clone)]
/// struct MyTheme {
///     pub primary_color: String,
///     pub header_color: String,
/// }
/// impl css_in_rs::Theme for MyTheme {
///     /* ... */
///     # fn fast_cmp(&self, other: &MyTheme) -> bool { false }
/// }
///
/// make_styles! {
///     (theme: MyTheme) -> MyClasses {
///         "*" {
///             padding: "0px",
///         },
///         text {
///             margin: "10px",
///             color: theme.primary_color,
///         },
///         header {
///             margin: "20px",
///             color: theme.header_color,
///         },
///     }
/// }
/// ```
/// This essentially expands to
/// ```
/// // Generated code; expanded form of the macro above
/// struct MyClasses {
///     pub text: String,
///     pub header: String,
/// }
///
/// impl ::css_in_rs::Classes for MyClasses {
///     # type Theme = css_in_rs::EmptyTheme;
///     # fn generate(_a: &css_in_rs::EmptyTheme, _b: &mut String, _c: &mut u64) {}
///     # fn new(_: u64) -> Self { todo!() }
///     /* ... */
/// }
/// ```
/// You can inject this style into the DOM using a `StyleProvider` (see
/// css-in-rs crate). It will hand you a `MyClasses` instance with uniquely
/// generated classnames (usually something like `css-17`).
#[proc_macro]
pub fn make_styles(input: TokenStream) -> TokenStream {
    let style = syn::parse_macro_input!(input as data::Style);

    let result = result::Result::new(style);
    let expanded = result.to_token_stream();

    // Convert the generated code to a TokenStream
    TokenStream::from(expanded)
}

#[cfg(test)]
mod tests {
    use quote::{quote, ToTokens};

    use crate::{data::Style, result};

    #[test]
    fn simple() {
        let input = quote! {
            (theme: MyTheme) -> MyClasses {
                "div.red_color" {
                    color: "red",
                },
                "div.blue_color" {
                    color: "blue",
                },
                "@media (hover: none)" {
                    "body *" {
                        pointer_events: "none",
                    }
                }
            }
        };

        let style = syn::parse2::<Style>(input).unwrap();
        let result = result::Result::new(style);

        // Uncomment to see nice version of the output
        /*{
            let ts = result.to_token_stream();
            let file = syn::parse2::<syn::File>(ts).unwrap();
            let nice = prettyplease::unparse(&file);
            eprintln!("{nice}");
            todo!();
        }*/

        let result = result.to_token_stream().to_string();

        let expected = quote! {
            struct MyClasses {
                pub blue_color: String,
                pub red_color: String,
            }

            impl ::css_in_rs::Classes for MyClasses {
                type Theme = MyTheme;

                fn generate(theme: &Self::Theme, css: &mut String, counter: &mut u64) {
                    use ::core::fmt::Write;
                    let start = *counter;
                    let _ = write!(
                        css,
                        "div.css-{} {{\n  {}: {};\n}}\ndiv.css-{} {{\n  {}: {};\n}}\n@media (hover: none) {{\nbody * {{\n  {}: {};\n}}\n}}\n",
                        start + 1u64, "color", "red", start + 0u64, "color", "blue",
                        "pointer-events", "none"
                    );
                    *counter = start + 2u64;
                }
                fn new(start: u64) -> Self {
                    Self {
                        blue_color: format!("css-{}", start + 0u64),
                        red_color: format!("css-{}", start + 1u64),
                    }
                }
            }
        };

        assert_eq!(result, expected.to_string());
    }
}