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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use crate::*;
/// Implementation of `Parse` for `VarsInput`, parsing the `vars!` macro input.
impl Parse for VarsInput {
/// Parses the `vars!` macro input into a `VarsInput` AST.
///
/// # Arguments
///
/// - `ParseStream` - The syn parse stream to read from.
///
/// # Returns
///
/// - `syn::Result<Self>` - The parsed `VarsInput`, or a syntax error.
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut defs: Vec<VarsDef> = Vec::new();
while !input.is_empty() {
let visibility: Visibility = input.parse()?;
let name: Ident = input.parse()?;
let params: Option<Vec<VarsParam>> = if input.peek(Paren) {
let param_content: ParseBuffer<'_>;
syn::parenthesized!(param_content in input);
let mut param_list: Vec<VarsParam> = Vec::new();
while !param_content.is_empty() {
let param_name: Ident = param_content.parse()?;
param_content.parse::<Token![:]>()?;
let param_type: Type = param_content.parse()?;
param_list.push(VarsParam {
name: param_name,
param_type,
});
if param_content.peek(Token![,]) {
param_content.parse::<Token![,]>()?;
}
}
if param_list.is_empty() {
None
} else {
Some(param_list)
}
} else {
None
};
let content: ParseBuffer<'_>;
braced!(content in input);
let mut vars: Vec<(String, VarsValue)> = Vec::new();
while !content.is_empty() {
let var_name: String = parse_ident_name(&content)?;
let css_key: String = format!("{CSS_CUSTOM_PROPERTY_PREFIX}{var_name}");
content.parse::<Token![:]>()?;
let var_value: VarsValue = {
let expr: Expr = content.parse()?;
let expanded: proc_macro2::TokenStream = expand_var_macros(&expr);
VarsValue::Expr(expanded)
};
vars.push((css_key, var_value));
if content.peek(Semi) {
content.parse::<Semi>()?;
}
}
defs.push(VarsDef {
visibility,
name,
params,
vars,
});
}
Ok(Self { defs })
}
}
/// Implementation of `ToTokens` for `VarsDef`, converting a vars block into `Css` function tokens.
///
/// Each CSS variable block becomes a `Css` function that, when called, injects
/// the CSS custom properties into the DOM and returns a reference to the class.
/// The CSS key names are prefixed with `--`.
impl ToTokens for VarsDef {
/// Converts this CSS variable definition into token stream constructing a `Css`.
///
/// # Arguments
///
/// - `&mut proc_macro2::TokenStream` - The target token stream to append to.
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let visibility: &Visibility = self.get_visibility();
let name: &Ident = self.get_name();
let class_name_str: String = name.to_string();
match self.try_get_params() {
Some(params) => {
let param_defs: Vec<proc_macro2::TokenStream> = params
.iter()
.map(|param: &VarsParam| {
let param_name: &Ident = param.get_name();
let param_type: &Type = param.get_param_type();
quote! { #param_name: #param_type }
})
.collect();
let param_names: Vec<&Ident> = params
.iter()
.map(|param: &VarsParam| param.get_name())
.collect();
let css_string_parts: Vec<proc_macro2::TokenStream> = self
.get_vars()
.iter()
.map(|(key, value): &(String, VarsValue)| match value {
VarsValue::Expr(expr) => {
if is_static_string_expr(expr) {
let value_str: String = expr_to_string(expr);
let prop_str: String =
format!("{key}{CSS_PROP_SEPARATOR}{value_str}{CSS_DECL_TERMINATOR}");
quote! { #prop_str.to_string() }
} else {
let key_sep: String = format!("{key}{CSS_PROP_SEPARATOR}");
quote! { #key_sep.to_string() + &(#expr).to_string() + #CSS_DECL_TERMINATOR }
}
}
})
.collect();
let name_format: String = format!("{{}}{STR_HYPHEN}{{}}");
tokens.extend(quote! {
#visibility fn #name(#(#param_defs), *) -> ::euv::Css {
let css: ::euv::Css = ::euv::Css::new(format!(#name_format, #class_name_str, [#(format!("{:?}", #param_names)), *].join(#STR_HYPHEN)), [#(#css_string_parts), *].concat(), Vec::new(), Vec::new());
css.inject_style();
css
}
});
}
None => {
let name_span: Span = name.span();
let const_name: Ident = Ident::new(&class_name_str.to_uppercase(), name.span());
let const_name_token: proc_macro2::TokenStream =
quote_spanned!(name_span=> #const_name);
let fn_name_token: proc_macro2::TokenStream = quote_spanned!(name_span=> #name);
let all_static: bool =
self.get_vars()
.iter()
.all(|(_, value): &(String, VarsValue)| {
let VarsValue::Expr(expr) = value;
is_static_string_expr(expr)
});
let style_expr: proc_macro2::TokenStream = if all_static {
let mut css_string: String = String::new();
for (key, value) in self.get_vars() {
let VarsValue::Expr(expr) = value;
css_string.push_str(key);
css_string.push_str(CSS_PROP_SEPARATOR);
css_string.push_str(&expr_to_string(expr));
css_string.push_str(CSS_DECL_TERMINATOR);
}
quote! { #css_string.to_string() }
} else {
let css_string_parts: Vec<proc_macro2::TokenStream> = self
.get_vars()
.iter()
.map(|(key, value): &(String, VarsValue)| match value {
VarsValue::Expr(expr) => {
if is_static_string_expr(expr) {
let value_str: String = expr_to_string(expr);
let prop_str: String = format!("{key}{CSS_PROP_SEPARATOR}{value_str}{CSS_DECL_TERMINATOR}");
quote! { #prop_str.to_string() }
} else {
let key_sep: String = format!("{key}{CSS_PROP_SEPARATOR}");
quote! { #key_sep.to_string() + &(#expr).to_string() + #CSS_DECL_TERMINATOR }
}
}
})
.collect();
quote! { [#(#css_string_parts), *].concat() }
};
emit_once_lock_fn(
tokens,
OnceLockParams {
visibility,
fn_name_token: &fn_name_token,
const_name_token: &const_name_token,
class_name_str: &class_name_str,
style_expr: &style_expr,
selector_expr: "e! { Vec::new() },
at_rule_expr: "e! { Vec::new() },
},
);
}
}
}
}
/// Implementation of `ToTokens` for `VarsInput`, converting vars definitions into token streams.
impl ToTokens for VarsInput {
/// Converts all vars definitions into token streams.
///
/// # Arguments
///
/// - `&mut proc_macro2::TokenStream` - The target token stream to append to.
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.get_defs()
.iter()
.for_each(|vars_def: &VarsDef| vars_def.to_tokens(tokens));
}
}