use crate::ast::Expr;
use crate::eval::{EvaluationOptions, Evaluator};
use crate::parser::parse_expression;
use crate::value::string_for_render;
use anyhow::{Result, bail};
pub fn render_template(
template: &str,
options: &EvaluationOptions,
functions: &mut Vec<String>,
references: &mut Vec<String>,
) -> Result<String> {
let mut out = String::new();
let mut pos = 0usize;
while let Some(start) = find_open(template, pos) {
out.push_str(&template[pos..start]);
let expr_start = start + 3;
let close = find_close(template, expr_start)?;
let raw_expr = template[expr_start..close].trim();
let expr = parse_expression(raw_expr)?;
expr.collect_functions(functions);
expr.collect_roots(references);
let mut evaluator = Evaluator::new(options);
let value = evaluator.eval_for_template(&expr)?;
out.push_str(&string_for_render(&value.json));
pos = close + 2;
}
out.push_str(&template[pos..]);
Ok(out)
}
fn find_open(template: &str, start: usize) -> Option<usize> {
template[start..].find("${{").map(|offset| start + offset)
}
fn find_close(template: &str, start: usize) -> Result<usize> {
let chars = template.char_indices().collect::<Vec<_>>();
let mut i = chars.partition_point(|(offset, _)| *offset < start);
let mut in_string = false;
while i < chars.len() {
let (offset, ch) = chars[i];
if ch == '\'' {
if in_string && chars.get(i + 1).map(|(_, ch)| *ch) == Some('\'') {
i += 2;
continue;
}
in_string = !in_string;
}
if !in_string && ch == '}' && chars.get(i + 1).map(|(_, ch)| *ch) == Some('}') {
return Ok(offset);
}
i += 1;
}
bail!("template expression is missing closing `}}`")
}
#[allow(dead_code)]
fn _assert_expr_send_sync(_: &Expr) {}