use crate::*;
impl Parse for ClassInput {
fn parse(input: ParseStream) -> SynResult<Self> {
let mut classes: Vec<ClassDef> = Vec::new();
while !input.is_empty() {
let visibility: syn::Visibility = input.parse()?;
let name: Ident = input.parse()?;
let params: Option<Vec<ClassParam>> = if input.peek(syn::token::Paren) {
let param_content;
syn::parenthesized!(param_content in input);
let mut param_list: Vec<ClassParam> = Vec::new();
while !param_content.is_empty() {
let param_name: Ident = param_content.parse()?;
param_content.parse::<Token![:]>()?;
let ty: syn::Type = param_content.parse()?;
param_list.push(ClassParam {
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 properties: Vec<(Ident, ClassPropValue)> = Vec::new();
while !content.is_empty() {
let prop_key: Ident = content.parse()?;
content.parse::<Token![:]>()?;
let prop_value: ClassPropValue = if content.peek(syn::token::Brace) {
let expr_content;
braced!(expr_content in content);
let expr: Expr = expr_content.parse()?;
ClassPropValue::Expr(expr.into_token_stream())
} else {
let lit: LitStr = content.parse()?;
ClassPropValue::Literal(lit.value())
};
properties.push((prop_key, prop_value));
if content.peek(Semi) {
content.parse::<Semi>()?;
}
}
classes.push(ClassDef {
visibility,
name,
params,
properties,
});
}
Ok(ClassInput { classes })
}
}
impl ToTokens for ClassDef {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let vis: &syn::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<TokenStream2> = params
.iter()
.map(|param| {
let param_name: &Ident = ¶m.name;
let ty: &syn::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<TokenStream2> = self
.properties
.iter()
.map(|(key, value)| {
let css_key: String = key.to_string().replace('_', "-");
match value {
ClassPropValue::Literal(lit) => {
let css_val: String = lit.clone();
quote! { #css_key.to_string() + ": " + #css_val + "; " }
}
ClassPropValue::Expr(expr) => {
quote! { #css_key.to_string() + ": " + &(#expr) + "; " }
}
}
})
.collect();
tokens.extend(quote! {
#[allow(non_snake_case)]
#vis fn #name(#(#param_defs),*) -> euv::vdom::CssClass {
let __css_string: String = [#(#css_string_parts),*].concat();
let __unique_name: String = format!("{}-{}", #class_name_str, [#(format!("{:?}", #param_names)),*].join("-"));
euv::vdom::CssClass::new(__unique_name, __css_string)
}
});
}
None => {
let css_string: String = self
.properties
.iter()
.map(|(key, value)| {
let css_key: String = key.to_string().replace('_', "-");
match value {
ClassPropValue::Literal(lit) => {
format!("{}: {};", css_key, lit)
}
ClassPropValue::Expr(_) => {
unreachable!(
"non-parameterized class should not have expression values"
)
}
}
})
.collect::<Vec<String>>()
.join(" ");
let const_name: Ident = Ident::new(&class_name_str.to_uppercase(), name.span());
let fn_name: Ident = name.clone();
tokens.extend(quote! {
#[allow(non_snake_case)]
#vis fn #fn_name() -> &'static euv::vdom::CssClass {
static #const_name: std::sync::OnceLock<euv::vdom::CssClass> = std::sync::OnceLock::new();
#const_name.get_or_init(|| euv::vdom::CssClass::new(#class_name_str.to_string(), #css_string.to_string()))
}
});
}
}
}
}
impl ToTokens for ClassInput {
fn to_tokens(&self, tokens: &mut TokenStream2) {
for class_def in &self.classes {
class_def.to_tokens(tokens);
}
}
}