hypertext-macros 0.12.1

A blazing fast type-checked HTML macro crate.
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote, quote_spanned};
use syn::{
    Ident, LitBool, LitChar, LitFloat, LitInt, LitStr, Token,
    parse::{Parse, ParseStream},
    spanned::Spanned,
    token::{Brace, Paren},
};

use super::{ElementBody, Generate, Generator, Literal, ParenExpr, Syntax};
use crate::{AttributeValueNode, Context};

pub struct Component<S: Syntax> {
    pub name: Ident,
    pub attrs: Vec<ComponentAttribute>,
    pub dotdot: Option<Token![..]>,
    pub body: ElementBody<S>,
}

impl<S: Syntax> Generate for Component<S> {
    const CONTEXT: Context = Context::Node;

    fn generate(&self, g: &mut Generator) {
        let fields = self.attrs.iter().map(|attr| {
            let name = &attr.name;
            let value = &attr.value_expr();

            quote!(#name: #value,)
        });

        let children = match &self.body {
            ElementBody::Normal { children, .. } => {
                let buffer_ident = Generator::buffer_ident();

                let block = g.block_with(Brace::default(), |g| {
                    g.push(children);
                });

                let lazy = quote! {
                    ::hypertext::Lazy::dangerously_create(
                        |#buffer_ident: &mut ::hypertext::Buffer|
                            #block
                    )
                };

                let children_ident = Ident::new("children", self.name.span());

                quote!(
                    #children_ident: #lazy,
                )
            }
            ElementBody::Void => quote!(),
        };

        let name = &self.name;

        let default = self
            .dotdot
            .as_ref()
            .map(|dotdot| quote_spanned!(dotdot.span()=> ..::core::default::Default::default()))
            .unwrap_or_default();

        let init = quote! {
            #name {
                #(#fields)*
                #children
                #default
            }
        };

        g.push_expr(Paren::default(), Self::CONTEXT, &init);
    }
}

pub struct ComponentAttribute {
    name: Ident,
    value: ComponentAttributeValue,
}

impl ComponentAttribute {
    fn value_expr(&self) -> TokenStream {
        match &self.value {
            ComponentAttributeValue::Literal(lit) => lit.to_token_stream(),
            ComponentAttributeValue::Ident(ident) => ident.to_token_stream(),
            ComponentAttributeValue::Expr(expr) => {
                let mut tokens = TokenStream::new();

                expr.paren_token.surround(&mut tokens, |tokens| {
                    expr.expr.to_tokens(tokens);
                });

                tokens
            }
        }
    }
}

impl Parse for ComponentAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            name: input.parse()?,
            value: {
                input.parse::<Token![=]>()?;

                input.parse()?
            },
        })
    }
}

pub enum ComponentAttributeValue {
    Literal(Literal),
    Ident(Ident),
    Expr(ParenExpr<AttributeValueNode>),
}

impl Parse for ComponentAttributeValue {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        if lookahead.peek(LitStr)
            || lookahead.peek(LitInt)
            || lookahead.peek(LitBool)
            || lookahead.peek(LitFloat)
            || lookahead.peek(LitChar)
        {
            input.call(Literal::parse_any).map(Self::Literal)
        } else if lookahead.peek(Ident) {
            input.parse().map(Self::Ident)
        } else if lookahead.peek(Paren) {
            input.parse().map(Self::Expr)
        } else {
            Err(lookahead.error())
        }
    }
}