templr_parser 0.2.1

Parser for templr templates
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt};
use syn::{
    parse::{Parse, ParseStream},
    Token,
};

use crate::{Attr, Name, Node};

#[derive(Debug, Clone)]
pub struct OpenTag {
    pub lt: Token![<],
    pub name: Name,
    pub attrs: Vec<Attr>,
    pub slash: Option<Token![/]>,
    pub gt: Token![>],
}

impl Parse for OpenTag {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            lt: input.parse()?,
            name: Name::parse_tag(input)?,
            attrs: {
                let mut attrs = vec![];
                while !input.peek(Token![/]) && !input.peek(Token![>]) {
                    attrs.push(Attr::parse(input)?);
                }
                attrs
            },
            slash: input.parse()?,
            gt: input.parse()?,
        })
    }
}

impl ToTokens for OpenTag {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.lt.to_tokens(tokens);
        self.name.to_tokens(tokens);
        tokens.append_all(&self.attrs);
        self.slash.to_tokens(tokens);
        self.gt.to_tokens(tokens);
    }
}

#[derive(Debug, Clone)]
pub struct CloseTag {
    pub lt: Token![<],
    pub slash: Token![/],
    pub name: Name,
    pub gt: Token![>],
}

impl Parse for CloseTag {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            lt: input.parse()?,
            slash: input.parse()?,
            name: Name::parse_tag(input)?,
            gt: input.parse()?,
        })
    }
}

impl ToTokens for CloseTag {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.lt.to_tokens(tokens);
        self.slash.to_tokens(tokens);
        self.name.to_tokens(tokens);
        self.gt.to_tokens(tokens);
    }
}

#[derive(Debug, Clone)]
pub struct Element {
    pub open: OpenTag,
    pub nodes: Vec<Node>,
    pub close: Option<CloseTag>,
}

impl Element {
    #[inline]
    pub const fn name(&self) -> &Name {
        &self.open.name
    }
}

impl Parse for Element {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let open = OpenTag::parse(input)?;
        let open_name = open.name.to_string();
        match open.slash {
            Some(_) => Ok(Self {
                open,
                nodes: vec![],
                close: None,
            }),
            None => Ok(Self {
                open,
                nodes: {
                    let mut nodes = vec![];
                    while !input.peek(Token![<]) || !input.peek2(Token![/]) {
                        if input.is_empty() {
                            return Err(
                                input.error(format!("expected closing tag: `</{open_name}>`"))
                            );
                        }
                        nodes.push(Node::parse(input)?);
                    }
                    nodes
                },
                close: {
                    let close: CloseTag = input.parse()?;
                    if close.name.to_string() != open_name {
                        return Err(syn::Error::new_spanned(
                            close.name,
                            format!("Mismatched closing tag, expected: `</{open_name}/>`"),
                        ));
                    }
                    Some(close)
                },
            }),
        }
    }
}

impl ToTokens for Element {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.open.to_tokens(tokens);
        tokens.append_all(&self.nodes);
        self.close.to_tokens(tokens);
    }
}