rustyle/
lib.rs

1//! This crate provides the [`rustyle!`] macro that allows using CSS-in-Rust for Rust frontend application.
2//!
3//! [`rustyle!`]: macro.rustyle.html
4
5#![feature(proc_macro_diagnostic)]
6#![feature(proc_macro_span)]
7extern crate fasthash;
8extern crate proc_macro;
9
10mod core;
11mod css_use_impl;
12
13use crate::core::name_mangler::mangle;
14use crate::core::parse::parse_rustyle;
15use lazy_static::lazy_static;
16use proc_macro::{Span, TokenStream};
17use quote::quote;
18use std::error::Error;
19use std::sync::{Arc, Mutex};
20lazy_static! {
21  static ref CSS_ID: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
22  static ref OUTPUT: String = std::env::var("RUSTYLE_OUTPUT").unwrap_or(String::from("./rustyle"));
23}
24
25/// Create a style class which contains the rusty style code.
26/// Returns the name of the class.
27///
28/// ```
29/// let Global = css! {
30///   #![inject_global]
31///   body {
32///     padding: 0;
33///   }
34/// }
35///
36/// let Button = css! {
37///   border: 1px solid black;
38///   #[allow(vendor_prefix)]
39///   ::-webkit-scrollbar {
40///     width: 10px;
41///   }
42/// }
43///
44/// html! {
45///   ...
46///   <button class=(Button)>
47///     ...
48///   </button>
49///   ...
50/// }
51/// ```
52#[proc_macro]
53pub fn rustyle(input: TokenStream) -> TokenStream {
54  let mut id = CSS_ID.lock().unwrap();
55
56  let mut result = String::new();
57
58  let class_name = mangle(
59    &input
60      .clone()
61      .into_iter()
62      .map(|token| token.to_string())
63      .collect::<String>(),
64  );
65
66  for node in parse_rustyle(input) {
67    result.push_str(&node.generate_code(&class_name));
68  }
69
70  let file_name = format!("rustyle.{}.css", *id);
71
72  let string_path = format!("{}/{}", OUTPUT.as_str(), file_name);
73  let path = std::path::Path::new(&string_path);
74
75  if *id == 0 && std::fs::metadata(path.parent().unwrap()).is_ok() {
76    if let Err(err) = std::fs::remove_dir_all(path.parent().unwrap()) {
77      Span::call_site()
78        .warning(format!("couldn't empty the folder: {}", err))
79        .emit();
80    }
81  }
82
83  if let Err(err) = std::fs::create_dir_all(path.parent().unwrap()) {
84    Span::call_site()
85      .error(format!("couldn't create the folder: {}", err))
86      .emit();
87    return (quote! {}).into();
88  }
89
90  let mut file = match std::fs::File::create(path) {
91    Err(err) => {
92      Span::call_site()
93        .error(format!("couldn't create the file: {}", err))
94        .emit();
95      return (quote! {}).into();
96    }
97    Ok(file) => file,
98  };
99
100  match std::io::Write::write_all(&mut file, result.as_bytes()) {
101    Err(err) => {
102      Span::call_site()
103        .error(format!(
104          "couldn't write to {}: {}",
105          path.to_str().unwrap(),
106          err.description()
107        ))
108        .emit();
109    }
110    Ok(_) => {}
111  }
112
113  *id += 1;
114
115  let expanded = quote! { #class_name };
116
117  expanded.into()
118}
119
120/// Alias of [`rustyle!`] macro.
121///
122/// [`rustyle!`]: macro.rustyle.html
123///
124/// ```
125/// let Button = css! {
126///   border: 1px solid black;
127/// }
128///
129/// html! {
130///   ...
131///   <button class=(Button)>
132///     ...
133///   </button>
134///   ...
135/// }
136/// ```
137#[proc_macro]
138pub fn css(input: TokenStream) -> TokenStream {
139  rustyle(input)
140}
141
142/// Allows using an outer variable on [`rustyle!`] macro.
143/// Only constantly evaluable some expression allowed.
144///
145/// [`rustyle!`]: macro.rustyle.html
146///
147/// ```
148/// #[css_use]
149/// let Parent = css! {
150///   color: red;
151/// }
152///
153/// let Child = css! {
154///   ${Parent} > & {
155///     color: white;
156///   }
157/// }
158/// ```
159#[proc_macro_attribute]
160pub fn css_use(attr: TokenStream, item: TokenStream) -> TokenStream {
161  if !attr.is_empty() {
162    Span::call_site().error("Unexpected parameters").emit();
163    item
164  } else {
165    css_use_impl::css_use_impl(item)
166  }
167}