weft_derive 0.1.2

HTML templating with composability in mind
Documentation
use crate::inline_parse::{parse_inline, Segment, Substitutable};
use crate::{TemplateDerivation, TemplateSource};
use anyhow::{anyhow, Context, Error};
use kuchiki::iter::Siblings;
use kuchiki::{ElementData, ExpandedName, NodeData, NodeRef};
use log::*;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use quote::TokenStreamExt;
use syn::{parse_quote, Pat, Token};

#[derive(Default, Debug)]
struct Walker;

#[derive(Default, Debug)]
struct Attribute {
    name: String,
    value: Substitutable,
}

#[derive(Default, Debug)]
struct Directives {
    replacement: Option<syn::Expr>,
    content: Option<syn::Expr>,
    conditional: Option<syn::Expr>,
    iterator: Option<IteratorDecl>,
    plain_attrs: Vec<Attribute>,
}

#[derive(Debug)]
struct IteratorDecl {
    pattern: syn::Pat,
    in_: Token![in],
    expr: syn::Expr,
}

fn render_to_fn(src: &TemplateDerivation, nodes: NodeRef) -> Result<TokenStream2, Error> {
    let walker = Walker;
    let impl_body = walker.children(nodes.children())?;
    let include_deps = if let TemplateSource::Path(path) = &src.template_source {
        std::fs::metadata(path).with_context(|| format!("Checking for file: {:?}", path))?;

        let path_str = path
            .to_str()
            .ok_or_else(|| anyhow!("Non representable path specified: {:?}", path))?;
        quote! {
            let _ = include_bytes!(#path_str);
        }
    } else {
        quote! {}
    };

    Ok(quote! {
            fn render_to(&self, mut __weft_target: &mut impl ::weft::RenderTarget) -> Result<(), ::std::io::Error> {
                use ::weft::prelude::*;
                #include_deps;
                #impl_body;
                Ok(())
            }
    })
}

pub(crate) fn derive_impl(
    src: &TemplateDerivation,
    nodes: NodeRef,
    mut item: syn::DeriveInput,
) -> Result<TokenStream2, Error> {
    info!("Deriving implementation for {}", item.ident);
    let render_to_fn_impl = render_to_fn(src, nodes)?;

    info!("Generics before: {:#?}", item.generics);
    let bounds = item
        .generics
        .type_params()
        .map(|p| {
            let name = &p.ident;
            parse_quote!(#name : ::weft::WeftRenderable)
        })
        .collect::<Vec<syn::WherePredicate>>();

    {
        let where_clause = item
            .generics
            .where_clause
            .get_or_insert(parse_quote!(where));
        for clause in bounds {
            where_clause.predicates.push(clause);
        }
    }
    let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();

    let ident = &item.ident;
    let res = quote! {
        impl #impl_generics ::weft::WeftRenderable for #ident #ty_generics #where_clause {
            #render_to_fn_impl
        }
    };
    debug!("Impl: {}", res);
    Ok(res)
}

impl Walker {
    fn dom(&self, node: NodeRef) -> Result<TokenStream2, Error> {
        match node.data() {
            NodeData::Document(_) => {
                let ts = self.children(node.children())?;
                trace!("Document => {}", ts);
                Ok(ts)
            }
            NodeData::Element(data) => {
                trace!("Element: {:?}", data);
                let ts = self.element(data, node.children())?;
                trace!("Element => {}", ts);
                Ok(ts)
            }
            NodeData::Text(ref contents) => {
                let ts = self.text(&contents.borrow())?;
                trace!("Text => {}", ts);
                Ok(ts)
            }
            NodeData::Doctype { .. } => {
                debug!("Ignoring doctype: children: {:?}", node.children().count());
                Ok(TokenStream2::default())
            }
            NodeData::Comment { .. } => {
                debug!("Ignoring comment: children: {:?}", node.children().count());
                Ok(TokenStream2::default())
            }
            NodeData::ProcessingInstruction { .. } => {
                debug!(
                    "Ignoring processing instruction: children: {:?}",
                    node.children().count()
                );
                Ok(TokenStream2::default())
            }
            NodeData::DocumentFragment => {
                debug!("Ignoring document fragment: {:?}", node.children().count());
                Ok(TokenStream2::default())
            }
        }
    }

    fn children(&self, nodes: Siblings) -> Result<TokenStream2, Error> {
        let mut res = TokenStream2::new();
        for child in nodes {
            res.extend(self.dom(child)?);
        }

        Ok(res)
    }

    fn element(&self, data: &ElementData, children: Siblings) -> Result<TokenStream2, Error> {
        let localname = data.name.local.to_string();
        trace!("Start Element {:?}", data);

        let directive = Directives::parse_from_attrs(&data.attributes.borrow())?;
        let res = if let Some(repl) = directive.replacement {
            quote!(#repl.render_to(&mut __weft_target)?;)
        } else if let Some(content) = directive.content {
            let content = quote!(#content.render_to(&mut __weft_target)?;);
            self.emit_element(&localname, &directive.plain_attrs, content)
        } else {
            let content = self.children(children)?;
            self.emit_element(&localname, &directive.plain_attrs, content)
        };

        let res = if let Some(iter) = directive.iterator {
            quote!(for #iter { #res }; )
        } else {
            res
        };

        let res = if let Some(test) = directive.conditional {
            quote!(if #test { #res }; )
        } else {
            res
        };
        trace!("End Element {:?}", data);

        Ok(res)
    }
    fn text(&self, contents: &str) -> Result<TokenStream2, Error> {
        let mut result = TokenStream2::new();
        let cdata = contents.to_string();
        trace!("Text {:?}", cdata);
        let parsed = parse_inline(&cdata)?;
        for segment in parsed.children() {
            match segment {
                Segment::Literal(cdata) => {
                    let chunk = quote!(__weft_target.text(#cdata)?;);
                    result.extend(chunk);
                }
                Segment::Expr(expr) => {
                    let chunk = quote!(#expr.render_to(&mut __weft_target)?;);
                    result.extend(chunk);
                }
            }
        }
        Ok(result)
    }

    fn emit_element(
        &self,
        localname: &str,
        attrs: &[Attribute],
        content: TokenStream2,
    ) -> proc_macro2::TokenStream {
        let attrs_q = quote!(&[#(&#attrs),*]);
        let mut statements = TokenStream2::new();
        statements.extend(quote!(
            __weft_target.start_element_attrs(#localname.into(), #attrs_q)?;
        ));

        statements.extend(content);

        statements.extend(quote!(
            __weft_target.end_element(#localname.into())?;
        ));
        statements
    }
}

impl Directives {
    fn parse_from_attrs(attrs: &kuchiki::Attributes) -> Result<Self, Error> {
        let mut it = Self::default();
        for (name, value) in attrs.map.iter() {
            match &*name.local {
                "weft-replace" => {
                    let replacement = syn::parse_str(&value.value)?;
                    it.replacement = Some(replacement)
                }
                "weft-content" => {
                    let content = syn::parse_str(&value.value)?;
                    it.content = Some(content)
                }
                "weft-if" => {
                    let test = syn::parse_str(&value.value)?;
                    it.conditional = Some(test)
                }
                "weft-for" => {
                    let iterator = syn::parse_str(&value.value)?;
                    it.iterator = Some(iterator)
                }
                _ => it.plain_attrs.push(Attribute::parse(name, value)?),
            }
        }

        Ok(it)
    }
}

impl Attribute {
    fn parse(name: &ExpandedName, value: &kuchiki::Attribute) -> Result<Self, Error> {
        let name: String = name.local.to_string();
        let value = parse_inline(&value.value)?;

        Ok(Attribute { name, value })
    }
}
impl quote::ToTokens for Attribute {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let key_name: String = self.name.to_string();

        let str_iter_q = self
            .value
            .children()
            .map(|segment| match segment {
                Segment::Literal(cdata) => quote!(#cdata),
                Segment::Expr(expr) => quote!(#expr.to_string()),
            }).fold(
                quote!(::std::iter::empty::<::std::borrow::Cow<str>>()),
                |prefix, it| quote!(#prefix.chain(::std::iter::once(::std::borrow::Cow::from(#it)))),
            );

        let value = quote!(#str_iter_q.collect::<String>());
        tokens.append_all(
            quote!(::weft::AttrPair::new(::weft::QName::from(#key_name), #value.into())),
        )
    }
}

impl quote::ToTokens for IteratorDecl {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        self.pattern.to_tokens(tokens);
        self.in_.to_tokens(tokens);
        self.expr.to_tokens(tokens);
    }
}

impl syn::parse::Parse for IteratorDecl {
    fn parse(buf: &syn::parse::ParseBuffer<'_>) -> Result<Self, syn::parse::Error> {
        let pattern = Pat::parse_single(buf)?;
        let in_ = buf.parse()?;
        let expr = buf.parse()?;
        Ok(IteratorDecl { pattern, in_, expr })
    }
}