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::quote;

mod data;
mod output;

/// 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-js 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);

    // Generate the code using quote!()
    let expanded = {
        let def = style.class_definition();
        let trait_impl = style.trait_impl();
        quote! {
            #def
            #trait_impl
        }
    };

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

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

    use crate::data::Style;

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

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

        let class_def = style.class_definition();
        let class_def = quote!(#class_def).to_string();

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

        assert_eq!(class_def, expected.to_string());

        let trait_impl = style.trait_impl();
        let trait_impl = quote!(#trait_impl).to_string();

        let expected = quote! {
            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;
                    ::core::writeln!(css, "div.css-{} {{", start + 1u64).unwrap();
                    ::core::writeln!(css, "  {}: {};", "color", "red").unwrap();
                    ::core::writeln!(css, "}}").unwrap();
                    ::core::writeln!(css, "div.css-{} {{", start + 0u64).unwrap();
                    ::core::writeln!(css, "  {}: {};", "color", "blue").unwrap();
                    ::core::writeln!(css, "}}").unwrap();
                    *counter = start + 2u64;
                }

                fn new(start: u64) -> Self {
                    Self {
                        blue_color: ::std::format!("css-{}", start + 0u64),
                        red_color: ::std::format!("css-{}", start + 1u64),
                    }
                }
            }
        };

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