///|
fn Run::run_nodes(
self : Run,
nodes : Array[@syntax.Node],
) -> Unit raise @spec.Refused {
for node in nodes {
match node {
Line(n) => self.line = n
_ => {
let template = self.template
let line = self.line
self.run_node(node) catch {
e => raise e.at(template, line)
}
if self.braking || self.skipping {
return
}
}
}
}
}
///|
fn Run::run_node(self : Run, node : @syntax.Node) -> Unit raise @spec.Refused {
match node {
Line(n) => self.line = n
Text(t) => if !self.extended { self.out.write_string(t) }
Out(e) => {
let v = self.eval(e)
if !self.extended {
self.emit(v)
}
}
Raw(e) => {
let v = self.eval(e)
if !self.extended {
self.out.write_string(
match v {
Markup(s) => s
other => self.call.text(other)
},
)
}
}
For(target~, iter~, cond~, recursive~, each~, body~, otherwise~) =>
self.run_for(
target,
self.eval(iter),
cond,
recursive,
each,
body,
otherwise,
0,
)
If(branches, otherwise) => {
for branch in branches {
if self.call.truthy(self.eval(branch.0)) {
self.run_nodes(branch.1)
return
}
}
self.run_nodes(otherwise)
}
Set(target, e) => self.assign(target, self.eval(e))
SetBlock(target, filter, body) => {
let captured = self.capture(() => self.run_nodes(body))
let mut value = self.rendered(captured)
match filter {
Some(chain) => value = self.apply_chain(chain, value)
None => ()
}
self.assign(target, value)
}
MacroDef(m) => self.bind(m.name, self.make_macro(m))
CallBlock(params, call, body) =>
match call {
Call(target, args) => {
let callee = self.eval(target)
let (pos, named) = self.eval_args(args)
let caller = Macro::{
run: self,
name: "caller",
params,
body,
home: self.scopes.copy(),
template: self.template,
pug: false,
caller: None,
varargs: true,
kwargs: true,
}
named["caller"] = Object(caller)
let result = self.invoke(callee, pos, named)
if !self.extended {
self.emit(result)
}
}
_ => raise @spec.invalid("a call block ends in a call")
}
FilterBlock(chain, body) => {
let captured = self.capture(() => self.run_nodes(body))
let value = self.apply_chain(chain, self.rendered(captured))
if !self.extended {
self.emit(value)
}
}
Extends(e) => self.set_parent(self.eval(e))
BlockDef(b) =>
if !self.extended {
self.render_block(b.name, scoped=b.scoped)
}
Include(e, missing~, context~, raw~, inline~, block~) =>
self.include_template(
self.eval(e),
missing~,
context~,
raw~,
inline~,
block~,
)
Yield => ()
Import(e, name, context~) =>
self.bind(name, self.import_module(self.eval(e), context))
FromImport(e, names, context~) => {
let imported = self.import_module(self.eval(e), context)
for pair in names {
let (name, renamed) = pair
match imported {
Object(obj) =>
match obj.attr(name) {
Some(v) => self.bind(renamed.unwrap_or(name), v)
None =>
raise @spec.invalid(
"the template does not export the requested name \{name}",
)
}
_ => ()
}
}
}
With(pairs, body) => {
let values = pairs.map(p => (p.0, self.eval(p.1)))
self.scoped(Map([]), () => {
for pair in values {
self.assign(pair.0, pair.1)
}
self.run_nodes(body)
})
}
Autoescape(e, body) => {
let before = self.autoescape()
self.set_autoescape(self.call.truthy(self.eval(e)))
defer self.set_autoescape(before)
self.scoped(Map([]), () => self.run_nodes(body))
}
Do(e) => ignore(self.eval(e))
Debug => {
let context = self.visible()
let filters = self.env.filters.keys().collect()
filters.sort_by((a, b) => @spec.code_point_compare(a, b))
let tests = self.env.tests.keys().collect()
tests.sort_by((a, b) => @spec.code_point_compare(a, b))
let report = @spec.Value::Dict(
Map([
("context", Dict(context)),
("filters", Seq(filters.map(f => @spec.Value::Text(f)))),
("tests", Seq(tests.map(t => @spec.Value::Text(t)))),
]),
)
self.emit(self.call.filter("pprint", report, [], Map([])))
}
Show(e, body) => {
let captured = self.capture(() => self.run_nodes(body))
if self.call.truthy(self.eval(e)) {
self.out.write_string(captured)
} else {
self.out.write_string("")
self.out.write_string(captured)
self.out.write_string("
")
}
}
Once(key, body) => {
let slot = match key {
Some(e) => self.call.text(self.eval(e))
None => "\{self.template}:\{self.line}"
}
match self.once.get(slot) {
Some(text) => self.out.write_string(text)
None => {
let render = () => {
let text = self.capture(() => self.run_nodes(body))
self.once[slot] = text
text
}
let text = match self.env.cache {
Some(store) => store(slot, render)
None => render()
}
self.once[slot] = text
self.out.write_string(text)
}
}
}
TransOut(t) => self.trans(t)
Break => self.braking = true
Continue => self.skipping = true
Element(el) => self.element(el)
Doctype(name) => {
let text = doctype(name)
let lower = text.to_lower()
self.terse = lower == ""
self.xml = lower.has_prefix(" {
self.out.write_string("")
}
Case(subject, arms, otherwise) => {
let value = self.eval(subject)
for arm in arms {
for candidate in arm.0 {
if self.eval(candidate) == value {
self.run_nodes(arm.1)
return
}
}
}
self.run_nodes(otherwise)
}
}
}
///|
/// Apply a filter chain whose innermost input is missing to `value`.
fn Run::apply_chain(
self : Run,
chain : @syntax.Expr,
value : @spec.Value,
) -> @spec.Value raise @spec.Refused {
match chain {
Filter(None, name, args) => {
let (pos, named) = self.eval_args(args)
self.call.filter(name, value, pos, named)
}
Filter(Some(inner), name, args) => {
let v = self.apply_chain(inner, value)
let (pos, named) = self.eval_args(args)
self.call.filter(name, v, pos, named)
}
_ => raise @spec.invalid("expected a filter")
}
}
///|
/// Bind a value to a target: a name, a tuple unpacking an iterable, or an
/// attribute of a namespace.
fn Run::assign(
self : Run,
target : @syntax.Target,
value : @spec.Value,
) -> Unit raise @spec.Refused {
match target {
Name(n) => self.bind(n, value)
Tuple(targets) => {
let items = self.call.items(value)
if items.length() != targets.length() {
raise @spec.invalid(
if items.length() > targets.length() {
"too many values to unpack (expected \{targets.length()})"
} else {
"not enough values to unpack (expected \{targets.length()}, got \{items.length()})"
},
)
}
for i, t in targets {
self.assign(t, items[i])
}
}
Field(name, attr) =>
match self.lookup(name) {
Object(obj) => obj.assign(attr, value)
Undefined(n) => raise undefined_use(n)
_ =>
raise @spec.invalid(
"cannot assign attribute on a non-namespace object",
)
}
}
}
///|
/// A Jinja doctype from Pug's shorthand, or the name as written.
fn doctype(name : String) -> String {
match name.to_lower() {
"" | "html" | "5" => ""
"xml" => ""
"transitional" =>
""
"strict" =>
""
"frameset" =>
""
"1.1" =>
""
"basic" =>
""
"mobile" =>
""
"plist" =>
""
_ => ""
}
}