use parse::compile_check_html;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[macro_use]
extern crate html5ever;
mod parse;
mod util;
use util::*;
#[proc_macro]
pub fn rhtml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand(input.into()).into()
}
fn expand(input: TokenStream) -> TokenStream {
let input_string = match validate_input(input) {
Ok(string) => string,
Err(err) => {
return err;
}
};
let (html_parts, rust_evaluators) = match parse_rhtml(&input_string) {
Ok(result) => result,
Err(err) => return err,
};
let html_for_validate = trim_whitespace_per_line(&html_parts.join(""));
if let Err(error) = compile_check_html(&html_for_validate) {
return error;
}
let template_parts_ident = format_ident!("template_parts");
let mut html_literals: Vec<_> = html_parts
.iter()
.map(|part| string_to_literal(part))
.collect();
let Some(template_end_literal) = html_literals.pop() else {
return compile_error("internal");
};
if html_literals.len() != rust_evaluators.len() {
return compile_error(
"unexpected number of parameters, this might be an internal rust_html error",
);
}
quote! {
{
let #template_parts_ident: Vec<(&'static str, rust_html::Template)> = vec![#(
(
#html_literals,
rust_html::Render::render(&#rust_evaluators)
)
),*];
rust_html::Template::build_internal(
#template_parts_ident,
#template_end_literal
)
}
}
}
fn parse_rhtml(input: &str) -> Result<(Vec<String>, Vec<TokenStream>), TokenStream> {
let mut skip_next = false;
let mut depth = 0;
let mut html_buffer: Vec<char> = vec![];
let mut rust_buffer: Vec<char> = vec![];
let mut html_parts: Vec<_> = vec![];
let mut rust_evaluators: Vec<_> = vec![];
let chars: Vec<_> = input.chars().collect();
for (i, token) in chars.iter().enumerate() {
if skip_next {
skip_next = false;
continue;
};
let peek = chars.get(i + 1);
let rust_mode = depth > 0;
match token {
'{' => {
if !rust_mode && peek == Some(&'{') {
html_buffer.push('{');
skip_next = true;
continue;
}
depth += 1;
if depth > 1 {
rust_buffer.push('{');
}
}
'}' => {
if !rust_mode && peek == Some(&'}') {
html_buffer.push('}');
skip_next = true;
continue;
}
depth -= 1;
if depth > 0 {
rust_buffer.push('}');
}
if depth < 0 {
return Err(compile_error(
"Unexpected close bracket '}', need an open bracket first (or '}}' to escape)"
));
}
}
token => {
if rust_mode {
rust_buffer.push(*token);
} else {
html_buffer.push(*token);
}
}
}
let change_to_rust = !rust_mode && depth > 0;
let change_to_html = rust_mode && depth == 0;
if change_to_rust {
let html_string: String = html_buffer.iter().collect();
html_parts.push(html_string);
html_buffer.clear();
}
if change_to_html {
let rust_string: String = rust_buffer.iter().collect();
let rust_evaluator = match inner_rust_to_tokens(&rust_string) {
Ok(rust_evaluator) => {
let html_context: String = html_buffer.iter().collect();
let valid_rust =
validate_inner_rust(&rust_evaluator, &rust_string, &html_context);
match valid_rust {
Ok(_) => rust_evaluator,
Err(inner_err) => return Err(inner_err),
}
}
Err(err) => return Err(err),
};
rust_evaluators.push(rust_evaluator);
rust_buffer.clear();
}
}
if depth > 0 {
return Err(compile_error("Missing close bracket '}'"));
}
let last_part = html_buffer.iter().collect();
html_parts.push(last_part);
Ok((html_parts, rust_evaluators))
}