rshtml_core 0.6.1

RsHtml: A Template Engine for Seamless HTML and Rust Integration.
Documentation
use super::{Input, template::inner_template_content};
use crate::extensions::ParserDiagnostic;
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_str;
use winnow::{
    ModalResult, Parser,
    ascii::{multispace0, multispace1},
    combinator::{alt, cut_err, opt, peek, repeat},
    error::{AddContext, ContextError, ErrMode, StrContext, StrContextValue},
    stream::Stream,
    token::none_of,
};

pub fn rust_stmt<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<TokenStream> {
    alt((
        (
            if_stmt,
            repeat(0.., (multispace0, "else", multispace1, if_stmt)).fold(
                TokenStream::new,
                |mut acc, (_, _, _, if_stmt_)| {
                    acc.extend(quote! { else });
                    acc.extend(if_stmt_);
                    acc
                },
            ),
            opt((
                multispace0,
                "else",
                multispace0,
                must_inner_template_content,
            )
                .map(|(_, _, _, content)| {
                    quote! {else #content}
                })),
        )
            .map(|(if_, if_else_chain_, else_opt)| {
                let mut ts = TokenStream::new();
                ts.extend(if_);
                ts.extend(if_else_chain_);
                ts.extend(else_opt);

                ts
            }),
        for_stmt,
        while_stmt,
    ))
    .parse_next(input)
}

fn if_stmt<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<TokenStream> {
    let checkpoint = input.checkpoint();

    let (head, body) = (multispace0, "if", stmt_head, must_inner_template_content)
        .map(|(_, if_, head, content)| (format!("{if_} {head}"), content))
        .parse_next(input)?;

    if let Err(e) = parse_str::<syn::Expr>(&format!("{} {{}}", head)) {
        let error_msg = Box::leak(e.to_string().into_boxed_str());
        input.reset(&checkpoint);
        return Err(ErrMode::Cut(ContextError::new().add_context(
            input,
            &checkpoint,
            StrContext::Expected(StrContextValue::Description(error_msg)),
        )));
    };

    let head_ts: TokenStream = head.parse().unwrap();

    let mut ts = TokenStream::new();
    ts.extend(head_ts);
    ts.extend(body);

    Ok(ts)
}

fn for_stmt<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<TokenStream> {
    let checkpoint = input.checkpoint();

    let (head, body) = (multispace0, "for", stmt_head, must_inner_template_content)
        .map(|(_, for_, head, content)| (format!("{for_} {head}"), content))
        .parse_next(input)?;

    if let Err(e) = parse_str::<syn::Expr>(&format!("{} {{}}", head)) {
        let error_msg = Box::leak(e.to_string().into_boxed_str());
        input.reset(&checkpoint);
        return Err(ErrMode::Cut(ContextError::new().add_context(
            input,
            &checkpoint,
            StrContext::Expected(StrContextValue::Description(error_msg)),
        )));
    };

    let head_ts: TokenStream = head.parse().unwrap();

    let mut ts = TokenStream::new();
    ts.extend(head_ts);
    ts.extend(body);

    Ok(ts)
}

fn while_stmt<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<TokenStream> {
    let checkpoint = input.checkpoint();

    let (head, body) = (multispace0, "while", stmt_head, must_inner_template_content)
        .map(|(_, while_, head, content)| (format!("{while_} {head}"), content))
        .parse_next(input)?;

    if let Err(e) = parse_str::<syn::Expr>(&format!("{} {{}}", head)) {
        let error_msg = Box::leak(e.to_string().into_boxed_str());
        input.reset(&checkpoint);
        return Err(ErrMode::Cut(ContextError::new().add_context(
            input,
            &checkpoint,
            StrContext::Expected(StrContextValue::Description(error_msg)),
        )));
    };

    let head_ts: TokenStream = head.parse().unwrap();

    let mut ts = TokenStream::new();
    ts.extend(head_ts);
    ts.extend(body);

    Ok(ts)
}

fn stmt_head<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<&'a str> {
    let start = input.input;

    (
        multispace1,
        repeat(1.., none_of(['{', '@', '}'])).fold(|| (), |_, _| ()),
        multispace0,
    )
        .void()
        .parse_next(input)?;

    let len = start.len() - input.input.len();
    let head_str = &start[..len];

    Ok(head_str)
}

fn must_inner_template_content<'a, 'ctx>(input: &mut Input<'a, 'ctx>) -> ModalResult<TokenStream> {
    (cut_err(peek('{')).expected("{"), inner_template_content)
        .map(|(_, content)| content)
        .parse_next(input)
}