rshtml_core 0.6.1

RsHtml: A Template Engine for Seamless HTML and Rust Integration.
Documentation
#![cfg_attr(not(debug_assertions), allow(unused))]

use std::{mem, ops::Range};
use winnow::{
    LocatingSlice, Parser,
    ascii::multispace0,
    combinator::{alt, peek, repeat},
    token::{none_of, take_till},
};

#[derive(Debug, Clone)]
pub enum Snippet {
    ExprSimple(Range<usize>, String),
    ExprParen(Range<usize>, String),
    Block(Range<usize>, String),
    Stmt(Range<usize>, String),
    Component(Range<usize>, String),
    ChildContent(Range<usize>, String),
    Continue(Range<usize>, String),
    Break(Range<usize>, String),
    None,
}

impl Snippet {
    pub fn start(&self) -> usize {
        match self {
            Snippet::ExprSimple(r, _)
            | Snippet::ExprParen(r, _)
            | Snippet::Block(r, _)
            | Snippet::Stmt(r, _)
            | Snippet::Component(r, _)
            | Snippet::ChildContent(r, _)
            | Snippet::Continue(r, _)
            | Snippet::Break(r, _) => r.start,

            Snippet::None => 0,
        }
    }
}

#[derive(Debug, Default, Clone)]
pub struct Debug {
    pub snippets: Vec<Snippet>,
    pub source: Vec<u8>,
}

impl Debug {
    pub fn new(source: Vec<u8>) -> Self {
        Self {
            source,
            snippets: Vec::new(),
        }
    }

    pub fn extract(&mut self) {
        self.snippets
            .sort_unstable_by_key(|b| std::cmp::Reverse(b.start()));

        let snippets = mem::take(&mut self.snippets);

        for snippet in snippets {
            if let Snippet::ExprSimple(range, expr) = snippet {
                self.source_replace_insert(expr.as_str(), range.start - 1);
            }
        }
    }

    pub fn push(&mut self, snippet: Snippet) {
        match snippet {
            Snippet::ExprSimple(range, expr) => {
                let expr = expr.strip_prefix('#').unwrap_or(&expr);
                let expr = format!("&{};", expr);
                self.snippets.push(Snippet::ExprSimple(range, expr));
            }
            Snippet::ExprParen(range, expr) => {
                let mut expr = expr.trim();
                expr = expr.strip_prefix('#').unwrap_or(expr);
                expr = expr.strip_prefix('(').unwrap_or(expr);
                expr = expr.strip_suffix(')').unwrap_or(expr);
                let expr = format!("&{};", expr);
                self.source_replace(&expr, range.start - 1);
            }
            Snippet::Block(range, mut block) => {
                block.replace_range(0..1, " ");
                let len = block.len();
                block.replace_range(len - 1..len, " ");
                self.source_replace(&block, range.start);
            }
            Snippet::Stmt(range, stmt) => {
                let ranges =
                    Self::stmt(&mut LocatingSlice::new(stmt.as_str())).unwrap_or(Vec::new());

                let mut stmt_buf = stmt.as_bytes().to_vec();

                for range in ranges {
                    let limit = range.end.min(stmt_buf.len());

                    for item in stmt_buf.iter_mut().take(limit).skip(range.start) {
                        if *item != b'\n' {
                            *item = b' ';
                        }
                    }
                }

                let cleaned_stmt = String::from_utf8(stmt_buf).unwrap();

                self.source_replace(&cleaned_stmt, range.start);
            }
            Snippet::Component(_range, _component) => {}
            Snippet::ChildContent(range, child_content) => {
                let child_content = format!("{};", child_content.trim());
                self.source_replace(&child_content, range.start - 1);
            }
            Snippet::Continue(range, continue_) => {
                let continue_ = format!("{};", continue_.trim());
                self.source_replace(&continue_, range.start - 1);
            }
            Snippet::Break(range, break_) => {
                let break_ = format!("{};", break_.trim());
                self.source_replace(&break_, range.start - 1);
            }
            Snippet::None => {}
        }
    }

    fn stmt(input: &mut LocatingSlice<&str>) -> winnow::Result<Vec<Range<usize>>> {
        take_till(1.., '{').parse_next(input)?;
        let range = Self::skip_braces.parse_next(input)?;

        let mut ranges = repeat(
            0..,
            (
                peek((multispace0, "else")).void(),
                take_till(1.., '{').void(),
                Self::skip_braces,
            )
                .map(|(_, _, r)| r),
        )
        .fold(Vec::new, |mut acc, item| {
            acc.push(item);
            acc
        })
        .parse_next(input)?;

        ranges.insert(0, range);

        Ok(ranges)
    }

    fn skip_braces(input: &mut LocatingSlice<&str>) -> winnow::Result<Range<usize>> {
        (
            "{".void(),
            repeat(
                0..,
                alt((Self::skip_braces.void(), none_of(['{', '}']).void())),
            )
            .fold(|| (), |_, _| ())
            .span(),
            "}".void(),
        )
            .map(|(_, range, _)| range)
            .parse_next(input)
    }

    pub fn source_replace(&mut self, content: &str, start: usize) {
        let content_bytes = content.as_bytes();
        let mut buf_idx = start;

        for &byte in content_bytes {
            if buf_idx < self.source.len() {
                self.source[buf_idx] = byte;
                buf_idx += 1;
            }
        }
    }

    pub fn source_replace_insert(&mut self, content: &str, start: usize) {
        let content_bytes = content.as_bytes();
        let mut buf_idx = start;

        for &byte in content_bytes {
            if self.source[buf_idx] == b'\n' {
                self.source.insert(buf_idx, byte);
                buf_idx += 1;
            } else {
                self.source[buf_idx] = byte;
                buf_idx += 1;
            }
        }
    }
}