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());
}
}