use crate::*;
impl Parse for CssVarInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut defs: Vec<CssVarDef> = Vec::new();
while !input.is_empty() {
let visibility: Visibility = input.parse()?;
let name: Ident = input.parse()?;
let params: Option<Vec<CssVarParam>> = if input.peek(Paren) {
let param_content;
syn::parenthesized!(param_content in input);
let mut param_list: Vec<CssVarParam> = Vec::new();
while !param_content.is_empty() {
let param_name: Ident = param_content.parse()?;
param_content.parse::<Token![:]>()?;
let ty: Type = param_content.parse()?;
param_list.push(CssVarParam {
name: param_name,
ty,
});
if param_content.peek(Token![,]) {
param_content.parse::<Token![,]>()?;
}
}
Some(param_list)
} else {
None
};
let content;
braced!(content in input);
let mut vars: Vec<(String, CssVarValue)> = Vec::new();
while !content.is_empty() {
let var_name: String = parse_kebab_name(&content)?;
let css_key: String = format!("--{}", var_name);
content.parse::<Token![:]>()?;
let var_value: CssVarValue = {
let expr: Expr = content.parse()?;
CssVarValue::Expr(expr.into_token_stream())
};
vars.push((css_key, var_value));
if content.peek(Semi) {
content.parse::<Semi>()?;
}
}
defs.push(CssVarDef {
visibility,
name,
params,
vars,
});
}
Ok(CssVarInput { defs })
}
}
impl ToTokens for CssVarDef {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let vis: &Visibility = &self.visibility;
let name: &Ident = &self.name;
let class_name_str: String = name.to_string();
match &self.params {
Some(params) => {
let param_defs: Vec<proc_macro2::TokenStream> = params
.iter()
.map(|param| {
let param_name: &Ident = ¶m.name;
let ty: &Type = ¶m.ty;
quote! { #param_name: #ty }
})
.collect();
let param_names: Vec<&Ident> = params.iter().map(|p| &p.name).collect();
let css_string_parts: Vec<proc_macro2::TokenStream> = self
.vars
.iter()
.map(|(key, value)| match value {
CssVarValue::Expr(expr) => {
quote! { #key.to_string() + ": " + &(#expr).to_string() + "; " }
}
})
.collect();
tokens.extend(quote! {
#vis fn #name(#(#param_defs),*) -> euv_core::CssClass {
let __css_string: String = [#(#css_string_parts),*].concat();
let __unique_name: String = format!("{}-{}", #class_name_str, [#(format!("{:?}", #param_names)),*].join("-"));
euv_core::CssClass::new(__unique_name, __css_string)
}
});
}
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.vars.iter().all(|(_, value)| {
let CssVarValue::Expr(expr) = value;
is_static_string_expr(expr)
});
if all_static {
let mut css_string: String = String::new();
for (key, value) in &self.vars {
let CssVarValue::Expr(expr) = value;
css_string.push_str(key);
css_string.push_str(": ");
css_string.push_str(&expr_to_string(expr));
css_string.push_str("; ");
}
tokens.extend(quote! {
#vis fn #fn_name_token() -> &'static euv_core::CssClass {
static #const_name_token: std::sync::OnceLock<euv_core::CssClass> = std::sync::OnceLock::new();
#const_name_token.get_or_init(|| {
euv_core::CssClass::new(#class_name_str.to_string(), #css_string.to_string())
})
}
});
} else {
let css_string_parts: Vec<proc_macro2::TokenStream> = self
.vars
.iter()
.map(|(key, value)| match value {
CssVarValue::Expr(expr) => {
quote! { #key.to_string() + ": " + &(#expr).to_string() + "; " }
}
})
.collect();
tokens.extend(quote! {
#vis fn #fn_name_token() -> &'static euv_core::CssClass {
static #const_name_token: std::sync::OnceLock<euv_core::CssClass> = std::sync::OnceLock::new();
#const_name_token.get_or_init(|| {
let __css_string: String = [#(#css_string_parts),*].concat();
euv_core::CssClass::new(#class_name_str.to_string(), __css_string)
})
}
});
}
}
}
}
}
impl ToTokens for CssVarInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
for css_var_def in &self.defs {
css_var_def.to_tokens(tokens);
}
}
}