use alloc::string::String;
use super::expr::eval_compiled_expr_into;
#[cfg(feature = "std")]
use super::{
control::{render_for_loop, render_if},
include::render_include,
matching::render_match,
};
#[cfg(not(feature = "std"))]
use super::{
control::{render_for_loop_no_std, render_if_no_std},
include::render_include_no_std,
matching::render_match_no_std,
};
use crate::{compiled::Segment, error::TemplateError, scope::Scope};
const ESTIMATED_EXPR_SIZE: usize = 32;
const ESTIMATED_LOOP_ITERATIONS: usize = 4;
#[must_use]
pub fn estimate_output_capacity(segments: &[Segment]) -> usize {
let mut size: usize = 0;
for seg in segments {
match seg {
Segment::Static(s) | Segment::Raw(s) => size += s.len(),
Segment::Expr { .. } | Segment::Include(_) | Segment::Panic(_) => {
size += ESTIMATED_EXPR_SIZE;
}
Segment::Comment(_) => {} Segment::ForLoop {
body, else_body, ..
} => {
size += estimate_output_capacity(body) * ESTIMATED_LOOP_ITERATIONS;
size += estimate_output_capacity(else_body);
}
Segment::If {
branches,
else_body,
} => {
let mut max_branch = estimate_output_capacity(else_body);
for (_, branch_body) in branches {
max_branch = max_branch.max(estimate_output_capacity(branch_body));
}
size += max_branch;
}
Segment::Match { arms, .. } => {
let mut max_arm = 0;
for arm in arms {
max_arm = max_arm.max(estimate_output_capacity(&arm.body));
}
size += max_arm;
}
}
}
size
}
#[cfg(feature = "std")]
pub(crate) fn render_segments(
segments: &[Segment],
scope: &mut Scope<'_>,
base_dir: Option<&std::path::Path>,
) -> Result<String, TemplateError> {
let mut output = String::with_capacity(estimate_output_capacity(segments));
render_segments_into(segments, scope, base_dir, &mut output)?;
Ok(output)
}
#[cfg(feature = "std")]
#[inline]
pub(crate) fn render_segments_into(
segments: &[Segment],
scope: &mut Scope<'_>,
base_dir: Option<&std::path::Path>,
output: &mut String,
) -> Result<(), TemplateError> {
for segment in segments {
match segment {
Segment::Static(text) | Segment::Raw(text) => output.push_str(text),
Segment::Expr { expr, filters } => {
eval_compiled_expr_into(expr, filters, scope, output)?;
}
Segment::ForLoop {
binding,
list_expr,
body,
else_body,
} => {
render_for_loop(
binding.as_ref(),
list_expr,
body,
else_body,
scope,
base_dir,
output,
)?;
}
Segment::If {
branches,
else_body,
} => {
render_if(branches, else_body, scope, base_dir, output)?;
}
Segment::Match {
expr,
arms,
is_option,
} => {
render_match(expr, arms, *is_option, scope, base_dir, output)?;
}
Segment::Include(inc) => {
render_include(inc, scope, base_dir, output)?;
}
Segment::Panic(segments) => {
let msg = render_segments(segments, scope, base_dir)?;
return Err(TemplateError::panic(msg));
}
Segment::Comment(_) => {
}
}
}
Ok(())
}
#[cfg(not(feature = "std"))]
pub(crate) fn render_segments_into_no_std(
segments: &[Segment],
scope: &mut Scope<'_>,
output: &mut String,
) -> Result<(), TemplateError> {
for segment in segments {
match segment {
Segment::Static(text) | Segment::Raw(text) => output.push_str(text),
Segment::Expr { expr, filters } => {
eval_compiled_expr_into(expr, filters, scope, output)?;
}
Segment::ForLoop {
binding,
list_expr,
body,
else_body,
} => {
render_for_loop_no_std(
binding.as_ref(),
list_expr,
body,
else_body,
scope,
output,
)?;
}
Segment::If {
branches,
else_body,
} => {
render_if_no_std(branches, else_body, scope, output)?;
}
Segment::Match {
expr,
arms,
is_option,
} => {
render_match_no_std(expr, arms, *is_option, scope, output)?;
}
Segment::Include(inc) => {
render_include_no_std(inc, scope, output)?;
}
Segment::Panic(segments) => {
let mut msg = String::new();
render_segments_into_no_std(segments, scope, &mut msg)?;
return Err(TemplateError::panic(msg));
}
Segment::Comment(_) => {
}
}
}
Ok(())
}
pub(crate) fn render_interpolated_str(
segments: &[Segment],
scope: &Scope<'_>,
) -> Result<String, TemplateError> {
let mut output = String::new();
for segment in segments {
match segment {
Segment::Static(text) | Segment::Raw(text) => output.push_str(text),
Segment::Expr { expr, filters } => {
eval_compiled_expr_into(expr, filters, scope, &mut output)?;
}
Segment::Comment(_) => {}
_ => {
return Err(TemplateError::syntax(
"control-flow tags are not allowed inside interpolated strings",
));
}
}
}
Ok(output)
}