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}