use crate::*;
pub fn parse_class(input: TokenStream) -> TokenStream {
let tokens: proc_macro2::TokenStream = match parse::<ClassInput>(input) {
Ok(class_input) => class_input.into_token_stream(),
Err(error) => return error.to_compile_error().into(),
};
TokenStream::from(tokens)
}
pub(crate) fn parse_class_prop_key(input: ParseStream) -> syn::Result<ClassPropKey> {
if input.peek(Brace) {
let content: ParseBuffer<'_>;
braced!(content in input);
let expr: Expr = content.parse()?;
Ok(ClassPropKey::Dynamic(expr.to_token_stream()))
} else {
let key: String = parse_kebab_name(input)?;
Ok(ClassPropKey::Static(key))
}
}
pub(crate) fn class_prop_key_to_tokens(key: &ClassPropKey) -> proc_macro2::TokenStream {
match key {
ClassPropKey::Static(s) => {
quote! { #s.to_string() }
}
ClassPropKey::Dynamic(expr) => {
quote! { (#expr).to_string() }
}
}
}
pub(crate) fn expand_var_macros(expr: &Expr) -> proc_macro2::TokenStream {
match expr {
Expr::Macro(expr_macro) => {
if expr_macro.mac.path.is_ident(VAR) {
let body_tokens: &proc_macro2::TokenStream = &expr_macro.mac.tokens;
let body_str: String = reconstruct_kebab_from_tokens(body_tokens);
let css_name: String = format!("{CSS_VAR_PREFIX}{body_str}{CSS_VAR_SUFFIX}");
quote! { #css_name }
} else if expr_macro.mac.path.is_ident(FORMAT_MACRO) {
let mac_tokens: &proc_macro2::TokenStream = &expr_macro.mac.tokens;
let expanded: proc_macro2::TokenStream = expand_var_macros_in_tokens(mac_tokens);
let path: &Path = &expr_macro.mac.path;
quote! { #path!(#expanded) }
} else {
expr.into_token_stream()
}
}
_ => expr.into_token_stream(),
}
}
pub(crate) fn expand_var_macros_in_tokens(
tokens: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let mut result: Vec<proc_macro2::TokenTree> = Vec::new();
let mut iter: Peekable<proc_macro2::token_stream::IntoIter> =
tokens.clone().into_iter().peekable();
while let Some(token) = iter.next() {
match &token {
proc_macro2::TokenTree::Ident(ident)
if *ident == VAR
&& iter.peek().is_some_and(
|token: &proc_macro2::TokenTree| matches!(token, proc_macro2::TokenTree::Punct(punct) if punct.as_char() == '!'),
) =>
{
iter.next();
if iter
.peek()
.is_some_and(|token: &proc_macro2::TokenTree| matches!(token, proc_macro2::TokenTree::Group(_)))
{
if let Some(proc_macro2::TokenTree::Group(group)) = iter.next() {
let inner: proc_macro2::TokenStream = group.stream();
let var_name: String = reconstruct_kebab_from_tokens(&inner);
let css_name: String = format!("{CSS_VAR_PREFIX}{var_name}{CSS_VAR_SUFFIX}");
let expanded: proc_macro2::TokenStream = quote! { #css_name };
result.extend(expanded);
}
} else {
result.push(proc_macro2::TokenTree::Ident(ident.clone()));
result.push(proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
'!',
proc_macro2::Spacing::Alone,
)));
}
}
proc_macro2::TokenTree::Group(group) => {
let expanded_inner: proc_macro2::TokenStream =
expand_var_macros_in_tokens(&group.stream());
let new_group: proc_macro2::Group =
proc_macro2::Group::new(group.delimiter(), expanded_inner);
result.push(proc_macro2::TokenTree::Group(new_group));
}
_ => {
result.push(token);
}
}
}
result.into_iter().collect()
}
pub(crate) fn is_static_string_expr(tokens: &proc_macro2::TokenStream) -> bool {
for token in tokens.clone() {
match token {
proc_macro2::TokenTree::Literal(_) => continue,
_ => return false,
}
}
true
}
pub(crate) fn expr_to_string(tokens: &proc_macro2::TokenStream) -> String {
let mut result: String = String::new();
for token in tokens.clone() {
if let proc_macro2::TokenTree::Literal(lit) = token {
let literal_token_stream: proc_macro2::TokenStream =
proc_macro2::TokenTree::Literal(lit).into();
if let Ok(literal_string) = parse2::<LitStr>(literal_token_stream) {
result.push_str(&literal_string.value());
}
}
}
result
}
pub(crate) fn pseudo_blocks_to_tokens(
pseudo_blocks: &[PseudoBlock],
) -> Option<proc_macro2::TokenStream> {
if pseudo_blocks.is_empty() {
return None;
}
let parts: Vec<proc_macro2::TokenStream> = pseudo_blocks
.iter()
.map(|block: &PseudoBlock| {
let selector: &str = block.get_selector();
let style_parts: Vec<proc_macro2::TokenStream> = block
.get_properties()
.iter()
.map(|(key, value): &(ClassPropKey, ClassPropValue)| match value {
ClassPropValue::Expr(expr) => {
let key_token: proc_macro2::TokenStream = class_prop_key_to_tokens(key);
quote! { #key_token + #CSS_PROP_SEPARATOR + &(#expr).to_string() + #CSS_DECL_TERMINATOR }
}
})
.collect();
quote! {
::euv::PseudoRule::new(
#selector.to_string(),
[#(#style_parts), *].concat()
)
}
})
.collect();
Some(quote! { vec![#(#parts), *] })
}
pub(crate) fn media_blocks_to_tokens(
media_blocks: &[MediaBlock],
) -> Option<proc_macro2::TokenStream> {
if media_blocks.is_empty() {
return None;
}
let parts: Vec<proc_macro2::TokenStream> = media_blocks
.iter()
.map(|block: &MediaBlock| {
let query: &str = block.get_query();
let style_parts: Vec<proc_macro2::TokenStream> = block
.get_properties()
.iter()
.map(|(key, value): &(ClassPropKey, ClassPropValue)| match value {
ClassPropValue::Expr(expr) => {
let key_token: proc_macro2::TokenStream = class_prop_key_to_tokens(key);
quote! { #key_token + #CSS_PROP_SEPARATOR + &(#expr).to_string() + #CSS_DECL_TERMINATOR }
}
})
.collect();
quote! {
::euv::MediaRule::new(
#query.to_string(),
[#(#style_parts), *].concat()
)
}
})
.collect();
Some(quote! { vec![#(#parts), *] })
}
pub(crate) fn pseudo_blocks_to_static_string(pseudo_blocks: &[PseudoBlock]) -> String {
let mut result: String = String::new();
for block in pseudo_blocks {
result.push_str(block.get_selector());
result.push_str(CSS_RULE_OPEN);
for (key, value) in block.get_properties() {
let ClassPropValue::Expr(expr) = value;
let ClassPropKey::Static(key_str) = key else {
continue;
};
result.push_str(key_str);
result.push_str(CSS_PROP_SEPARATOR);
result.push_str(&expr_to_string(expr));
result.push_str(CSS_DECL_TERMINATOR);
}
result.push('}');
}
result
}
pub(crate) fn media_blocks_to_static_string(media_blocks: &[MediaBlock]) -> String {
let mut result: String = String::new();
for block in media_blocks {
result.push_str(CSS_MEDIA_PREFIX);
result.push_str(block.get_query());
result.push_str(CSS_RULE_OPEN);
for (key, value) in block.get_properties() {
let ClassPropValue::Expr(expr) = value;
let ClassPropKey::Static(key_str) = key else {
continue;
};
result.push_str(key_str);
result.push_str(CSS_PROP_SEPARATOR);
result.push_str(&expr_to_string(expr));
result.push_str(CSS_DECL_TERMINATOR);
}
result.push('}');
}
result
}
pub(crate) fn emit_once_lock_fn(tokens: &mut proc_macro2::TokenStream, p: OnceLockParams<'_>) {
let OnceLockParams {
vis,
fn_name_token,
const_name_token,
class_name_str,
style_expr,
pseudo_expr,
media_expr,
} = p;
tokens.extend(quote! {
#vis fn #fn_name_token() -> &'static ::euv::Css {
static #const_name_token: ::std::sync::OnceLock<::euv::Css> = ::std::sync::OnceLock::new();
#const_name_token.get_or_init(|| {
let css: ::euv::Css = ::euv::Css::new(#class_name_str.to_string(), #style_expr, #pseudo_expr, #media_expr);
css.inject_style();
css
})
}
});
}
pub(crate) fn lookup_pseudo_selector(keyword: &str) -> Option<&'static str> {
match keyword {
KEYWORD_HOVER => Some(PSEUDO_HOVER),
KEYWORD_FOCUS => Some(PSEUDO_FOCUS),
KEYWORD_FOCUS_WITHIN => Some(PSEUDO_FOCUS_WITHIN),
KEYWORD_FOCUS_VISIBLE => Some(PSEUDO_FOCUS_VISIBLE),
KEYWORD_ACTIVE => Some(PSEUDO_ACTIVE),
KEYWORD_VISITED => Some(PSEUDO_VISITED),
KEYWORD_DISABLED => Some(PSEUDO_DISABLED),
KEYWORD_ENABLED => Some(PSEUDO_ENABLED),
KEYWORD_CHECKED => Some(PSEUDO_CHECKED),
KEYWORD_READONLY => Some(PSEUDO_READONLY),
KEYWORD_READWRITE => Some(PSEUDO_READWRITE),
KEYWORD_REQUIRED => Some(PSEUDO_REQUIRED),
KEYWORD_OPTIONAL => Some(PSEUDO_OPTIONAL),
KEYWORD_VALID => Some(PSEUDO_VALID),
KEYWORD_INVALID => Some(PSEUDO_INVALID),
KEYWORD_IN_RANGE => Some(PSEUDO_IN_RANGE),
KEYWORD_OUT_OF_RANGE => Some(PSEUDO_OUT_OF_RANGE),
KEYWORD_PLACEHOLDER_SHOWN => Some(PSEUDO_PLACEHOLDER_SHOWN),
KEYWORD_FIRST_CHILD => Some(PSEUDO_FIRST_CHILD),
KEYWORD_LAST_CHILD => Some(PSEUDO_LAST_CHILD),
KEYWORD_ONLY_CHILD => Some(PSEUDO_ONLY_CHILD),
KEYWORD_FIRST_OF_TYPE => Some(PSEUDO_FIRST_OF_TYPE),
KEYWORD_LAST_OF_TYPE => Some(PSEUDO_LAST_OF_TYPE),
KEYWORD_ONLY_OF_TYPE => Some(PSEUDO_ONLY_OF_TYPE),
KEYWORD_ROOT => Some(PSEUDO_ROOT),
KEYWORD_EMPTY => Some(PSEUDO_EMPTY),
KEYWORD_TARGET => Some(PSEUDO_TARGET),
KEYWORD_LINK => Some(PSEUDO_LINK),
KEYWORD_ANY_LINK => Some(PSEUDO_ANY_LINK),
KEYWORD_BEFORE => Some(PSEUDO_BEFORE),
KEYWORD_AFTER => Some(PSEUDO_AFTER),
KEYWORD_FIRST_LINE => Some(PSEUDO_FIRST_LINE),
KEYWORD_FIRST_LETTER => Some(PSEUDO_FIRST_LETTER),
KEYWORD_SELECTION => Some(PSEUDO_SELECTION),
KEYWORD_PLACEHOLDER => Some(PSEUDO_PLACEHOLDER),
KEYWORD_BACKDROP => Some(PSEUDO_BACKDROP),
KEYWORD_MARKER => Some(PSEUDO_MARKER),
KEYWORD_SPELLING_ERROR => Some(PSEUDO_SPELLING_ERROR),
KEYWORD_GRAMMAR_ERROR => Some(PSEUDO_GRAMMAR_ERROR),
_ => None,
}
}