ogrim-macros 0.0.3

Helper crate for `ogrim`. Please see the docs of `ogrim` for more information. Do not use this crate directly, API stability is not guaranteed!
Documentation
use proc_macro2::{TokenStream, Span};
use quote::{quote, quote_spanned};

use crate::{ast, err::{err, Error}};


pub(crate) fn emit(input: ast::Input) -> Result<TokenStream, Error> {
    let buf_init = if let Some(expr) = &input.buffer {
        quote! { let mut buf = &mut *#expr; }
    } else {
        let prolog = input.prolog
            .ok_or(err!("you have to specify either a buffer to write into or an XML prolo"))?;

        let version = match prolog.version.as_str() {
            "1.0" => quote! { ogrim::Version::V1_0 },
            "1.1" => quote! { ogrim::Version::V1_1 },
            other => return Err(err!("invalid version '{other}'")),
        };
        let standalone = match prolog.standalone {
            None => quote! { None },
            Some(v) => quote! { Some(#v) },
        };
        let format = input.format.unwrap_or(quote! { ogrim::Format::Terse });


        quote! {
            let mut buf = ogrim::Document::new(#version, #standalone, #format);
        }
    };
    let ret = if input.buffer.is_some() { quote!{} } else { quote! { buf } };

    let root = emit_element(&input.root);


    Ok(quote! {
        {
            #buf_init
            #root
            #ret
        }
    })
}


fn emit_element(elem: &ast::Element) -> TokenStream {
    let name = &elem.name;
    let mut out = quote! {
        buf.open_tag(#name);
    };


    for attr in &elem.attrs {
        match attr {
            ast::Attr::Single(name, value) => {
                let (span, v) = match value {
                    ast::AttrValue::Literal(s) => (Span::call_site(), quote! { #s }),
                    ast::AttrValue::Expr(e) => (
                        span_of_tokenstream(&e),
                        quote! { (#e) },
                    ),
                };
                out.extend(quote_spanned!{span=>
                    buf.attr(#name, &#v);
                });
            }
            ast::Attr::Fill(expr) => {
                out.extend(quote! {
                    buf.attrs(#expr);
                });
            }
        }
    }

    if elem.empty {
        out.extend(quote! {
            buf.close_empty_elem_tag();
        });
    } else {
        let children = elem.children.iter().map(|child| {
            match child {
                ast::Child::Text(s) => quote! { buf.text(&#s); },
                ast::Child::TextExpr(e) => {
                    let span = span_of_tokenstream(&e);
                    quote_spanned! {span=> buf.text(&#e); }
                },
                ast::Child::Closure { arg, body } => quote! {
                    {
                        let #arg = &mut buf;
                        #body
                    }
                },
                ast::Child::Element(elem) => emit_element(elem),
            }
        });

        out.extend(quote! {
            buf.close_start_tag();
            #(#children)*
            buf.end_tag(#name);
        });
    }

    out
}

impl quote::ToTokens for ast::Name {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = &self.0;
        tokens.extend(quote! { #s });
    }
}

fn span_of_tokenstream(tokens: &TokenStream) -> Span {
    tokens.clone().into_iter().next().map(|tt| tt.span()).unwrap_or(Span::call_site())
}