workers-rsx-impl 0.1.0

Proc macros for workers-rsx
Documentation
use crate::child::Child;
use crate::children::Children;
use crate::element_attribute::ElementAttribute;
use crate::element_attributes::ElementAttributes;
use crate::tags::{ClosingTag, OpenTag};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};

pub struct Element {
    name: syn::Path,
    attributes: ElementAttributes,
    children: Children,
}

impl Parse for Element {
    fn parse(input: ParseStream) -> Result<Self> {
        let open_tag = input.parse::<OpenTag>()?;

        let children = if open_tag.self_closing {
            Children::default()
        } else {
            let children = input.parse::<Children>()?;
            let closing_tag = input.parse::<ClosingTag>()?;
            closing_tag.validate(&open_tag);
            children
        };

        Ok(Element {
            name: open_tag.name,
            attributes: open_tag.attributes,
            children,
        })
    }
}

impl Element {
    pub fn is_custom_element(&self) -> bool {
        match self.name.get_ident() {
            None => true,
            Some(ident) => {
                let name = ident.to_string();
                let first_letter = name.get(0..1).unwrap();
                first_letter.to_uppercase() == first_letter
            }
        }
    }

    /// Collect all static class names from this element and its descendants.
    pub fn collect_class_names(&self) -> Vec<String> {
        let mut classes = Vec::new();

        // Check attributes for "class" with a string literal value
        for attr in &self.attributes.attributes {
            if let ElementAttribute::WithValue(key, block) = attr {
                let key_str = key
                    .iter()
                    .map(|i| i.to_string())
                    .collect::<Vec<_>>()
                    .join("-");
                if key_str == "class" {
                    if block.stmts.len() == 1 {
                        if let syn::Stmt::Expr(syn::Expr::Lit(syn::ExprLit {
                            lit: syn::Lit::Str(s),
                            ..
                        })) = &block.stmts[0]
                        {
                            for class_name in s.value().split_whitespace() {
                                classes.push(class_name.to_string());
                            }
                        }
                    }
                }
            }
        }

        // Recurse into children
        for child in &self.children.nodes {
            Child::collect_class_names_from(child, &mut classes);
        }

        classes
    }

    /// Inject a `TailwindStyle` child into the `<head>` element.
    /// Walks the tree recursively to find `<head>` among children.
    /// Returns `true` if injection succeeded.
    pub fn inject_tailwind_style(&mut self, class_names: Vec<String>) -> bool {
        if let Some(ident) = self.name.get_ident() {
            if ident.to_string() == "head" {
                self.children
                    .nodes
                    .push(Child::TailwindStyle(class_names));
                return true;
            }
        }
        // Recurse into child elements
        for child in &mut self.children.nodes {
            if let Child::Element(el) = child {
                if el.inject_tailwind_style(class_names.clone()) {
                    return true;
                }
            }
        }
        false
    }

    /// Prepend a child node at the beginning of this element's children.
    pub fn prepend_child(&mut self, child: Child) {
        self.children.nodes.insert(0, child);
    }
}

impl ToTokens for Element {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let name = &self.name;

        let declaration = if self.is_custom_element() {
            let attrs = self.attributes.for_custom_element(&self.children);
            quote! { #name #attrs }
        } else {
            let attrs = self.attributes.for_simple_element();
            let children_tuple = self.children.as_option_of_tuples_tokens();
            quote! {
                workers_rsx::SimpleElement {
                    tag_name: stringify!(#name),
                    attributes: #attrs,
                    contents: #children_tuple,
                }
            }
        };

        declaration.to_tokens(tokens);
    }
}