///|
/// A compiled template.
priv struct CompiledTemplate {
instructions : Instructions
blocks : Map[String, Instructions]
buffer_size_hint : Int
syntax_config : SyntaxConfig
initial_auto_escape : AutoEscape
}
///|
fn CompiledTemplate::new(
name : String,
source : String,
syntax_config : SyntaxConfig,
ws_config : WhitespaceConfig,
initial_auto_escape : AutoEscape,
) -> CompiledTemplate raise TemplateError {
let ast = parse(source, name, syntax_config, ws_config) catch {
err => raise attach_basic_debug_info(err, source)
}
let g = CodeGenerator::new(name, source)
g.compile_stmt(ast)
let buffer_size_hint = g.buffer_size_hint()
let (instructions, blocks) = g.finish()
{
instructions,
blocks,
buffer_size_hint,
syntax_config,
initial_auto_escape,
}
}
///|
/// Represents a handle to a template.
///
/// Templates are stored in the [`Environment`] as bytecode instructions.
/// With the [`Environment::get_template`] method that is looked up and
/// returned in form of this handle. Such a template can be rendered with
/// [`Template::render`].
pub struct Template {
priv env : Environment
priv compiled : CompiledTemplate
}
///|
/// Returns the name of the template.
pub fn Template::name(self : Template) -> String {
self.compiled.instructions.name
}
///|
/// Returns the source code of the template.
pub fn Template::source(self : Template) -> String {
self.compiled.instructions.source
}
///|
/// Renders the template into a string.
///
/// The provided value is used as the initial context for the template. It
/// can be any object that implements the object protocol, typically a map
/// created with [`Value::from_pairs`] or [`Value::from_map`].
pub fn Template::render(
self : Template,
ctx : Value,
) -> String raise TemplateError {
let buf = StringBuilder(size_hint=self.compiled.buffer_size_hint)
self.eval(ctx, Output::new(buf)) |> ignore
buf.to_string()
}
///|
/// Renders the template and returns the output together with the final
/// state. The state can be used to render blocks or call macros.
pub fn Template::render_captured(
self : Template,
ctx : Value,
) -> Captured raise TemplateError {
let buf = StringBuilder(size_hint=self.compiled.buffer_size_hint)
let (_, state) = self.eval(ctx, Output::new(buf))
{ output: buf.to_string(), state, }
}
///|
/// Renders the template, streaming the output into `write`.
///
/// Errors raised by `write` abort rendering with a `WriteFailure` error.
/// The final state is returned (like [`Template::render_captured`]).
pub fn Template::render_to(
self : Template,
ctx : Value,
write : (StringView) -> Unit raise,
) -> State raise TemplateError {
let (_, state) = self.eval(ctx, Output::with_sink(write))
state
}
///|
fn Template::eval(
self : Template,
root : Value,
out : Output,
) -> (Value?, State) raise TemplateError {
vm_eval(
self.env,
self.compiled.instructions,
root,
self.compiled.blocks,
out,
self.compiled.initial_auto_escape,
)
}
///|
/// Returns a set of all undeclared variables in the template.
///
/// This returns a set of all variables that might be looked up at runtime
/// by the template. With `nested` set to `true`, attribute lookups on
/// undeclared variables are tracked as dotted paths (`foo.bar`).
pub fn Template::undeclared_variables(
self : Template,
nested? : Bool = false,
) -> Array[String] {
let ast = parse(
self.compiled.instructions.source,
self.name(),
self.compiled.syntax_config,
WhitespaceConfig::default(),
) catch {
_ => return []
}
let rv = find_undeclared(ast, nested).to_array()
rv.sort_by((a, b) => compare_str(a, b))
rv
}
///|
/// Creates an empty [`State`] for this template.
pub fn Template::new_state(self : Template) -> State {
State::new(
Context::new(self.env),
self.compiled.initial_auto_escape,
self.compiled.instructions,
prepare_blocks(self.compiled.blocks),
)
}
///|
/// Captured render output together with the state.
pub struct Captured {
/// The rendered output.
output : String
/// The state after rendering.
state : State
}
///|
/// A handle to a compiled expression.
///
/// An expression is created via the [`Environment::compile_expression`]
/// method. It provides a method to evaluate the expression and return the
/// result as value object.
pub struct Expression {
priv env : Environment
priv instructions : Instructions
}
///|
/// Evaluates the expression with some context.
///
/// ```mbt check
/// test {
/// let env = @minijinja.Environment::new()
/// let expr = env.compile_expression("number < 42")
/// let ctx = @minijinja.Value::from_pairs([
/// ("number", @minijinja.Value::from_int(23)),
/// ])
/// inspect(expr.eval(ctx).to_string(), content="True")
/// }
/// ```
pub fn Expression::eval(
self : Expression,
ctx : Value,
) -> Value raise TemplateError {
let (rv, _) = vm_eval(
self.env,
self.instructions,
ctx,
Map([]),
Output::null(),
NoEscape,
)
match rv {
Some(v) => v
None => abort("expression evaluation did not leave value on stack")
}
}
///|
/// Returns a set of all undeclared variables in the expression.
pub fn Expression::undeclared_variables(
self : Expression,
nested? : Bool = false,
) -> Array[String] {
let expr = parse_expr(self.instructions.source) catch { _ => return [] }
let rv = find_undeclared(EmitExpr({ expr, span: Span::default(), }), nested).to_array()
rv.sort_by((a, b) => compare_str(a, b))
rv
}