A port of MiniJinja (a Jinja2 template engine) to MoonBit
Dependencies
///|
test "hello world" {
let env = @minijinja.Environment::new()
env.add_template("hello.txt", "Hello {{ name }}!")
let tmpl = env.get_template("hello.txt")
let ctx = @minijinja.Value::from_pairs([
("name", @minijinja.Value::from_string("John")),
])
inspect(tmpl.render(ctx), content="Hello John!")
}///|
test "inheritance" {
let env = @minijinja.Environment::new()
env.add_template(
"layout.html", "<title>{% block title %}{% endblock %}</title>{% block body %}{% endblock %}",
)
env.add_template(
"index.html",
(
#|{% extends "layout.html" %}
#|{% macro item(x) %}<li>{{ x }}</li>{% endmacro %}
#|{% block title %}Items{% endblock %}
#|{% block body %}<ul>{% for x in items %}{{ item(x) }}{% endfor %}</ul>{% endblock %}
),
)
let ctx = @minijinja.Value::from_pairs([
(
"items",
@minijinja.Value::from_array([
@minijinja.Value::from_string("<a>"),
@minijinja.Value::from_int(42),
]),
),
])
inspect(
env.get_template("index.html").render(ctx),
content="<title>Items</title><ul><li><a></li><li>42</li></ul>",
)
}///|
test "custom filter" {
let env = @minijinja.Environment::new()
env.add_filter("repeat", (state, args) => {
let a = @minijinja.Args::new(state, args)
let kwargs = a.kwargs()
let value = a.string()
let times = a.opt_usize().unwrap_or(2)
let sep = kwargs.get_str("sep").unwrap_or("")
a.finish()
kwargs.assert_all_used()
@minijinja.Value::from_string(Array::make(times, value).join(sep))
})
inspect(
env.render_str(
"{{ 'ab'|repeat }} {{ 'x'|repeat(3, sep='-') }}",
@minijinja.Value::none(),
),
content="abab x-x-x",
)
}///|
test "typed arguments" {
let env = @minijinja.Environment::new()
env.add_function("clamp", (state, args) => {
let a = @minijinja.Args::new(state, args)
let value : Int = a.next()
let lo : Int = a.next()
let hi : Int? = a.next()
a.finish()
let hi = hi.unwrap_or(100)
@minijinja.Value::from_int(
if value < lo {
lo
} else if value > hi {
hi
} else {
value
},
)
})
inspect(
env.render_str(
"{{ clamp(5, 10) }} {{ clamp(500, 1) }}",
@minijinja.Value::none(),
),
content="10 100",
)
}///|
test "loader and blocks" {
let env = @minijinja.Environment::new()
let sources = {
"macros.txt": "{% macro hello(name) %}Hello {{ name }}!{% endmacro %}",
"page.txt": "{% from 'macros.txt' import hello %}{% block title %}{{ hello(who) }}{% endblock %}",
}
env.set_loader(name => sources.get(name))
let tmpl = env.get_template("page.txt")
let ctx = @minijinja.Value::from_pairs([
("who", @minijinja.Value::from_string("World")),
])
let out = StringBuilder()
let state = tmpl.render_to(ctx, s => out.write_view(s))
inspect(out.to_string(), content="Hello World!")
inspect(state.render_block("title"), content="Hello World!")
inspect(
state.call_macro("hello", [@minijinja.Value::from_string("Moon")]),
content="Hello Moon!",
)
}///|
struct Point {
x : Int
y : Int
}
///|
impl @minijinja.Object for Point with fn get_value(self, key) {
match key.as_str() {
Some("x") => Some(@minijinja.Value::from_int(self.x))
Some("y") => Some(@minijinja.Value::from_int(self.y))
_ => None
}
}
///|
impl @minijinja.Object for Point with fn enumerate(_self) {
Str(["x", "y"])
}
///|
test "objects" {
let env = @minijinja.Environment::new()
let ctx = @minijinja.Value::from_pairs([
("p", @minijinja.Value::from_object(Point::{ x: 1, y: 2, })),
])
inspect(
env.render_str("{{ p.x }},{{ p.y }} {{ p|list }}", ctx),
content="1,2 ['x', 'y']",
)
}///|
test "expressions" {
let env = @minijinja.Environment::new()
let expr = env.compile_expression("number < 42 and name is defined")
let ctx = @minijinja.Value::from_pairs([
("number", @minijinja.Value::from_int(23)),
("name", @minijinja.Value::from_string("x")),
])
inspect(expr.eval(ctx), content="True")
}///|
test "errors" {
let env = @minijinja.Environment::new()
env.add_template("bad.txt", "{{ [1, 2] + 23 }}")
let tmpl = env.get_template("bad.txt")
try tmpl.render(@minijinja.Value::none()) catch {
err => {
inspect(err.kind() == InvalidOperation, content="true")
inspect(
err,
content="invalid operation: tried to use + operator on unsupported types sequence and number (in bad.txt:1)",
)
}
} noraise {
_ => fail("expected an error")
}
}moon run cmd/minijinja -- hello.j2 hello.json
moon run cmd/minijinja -- --template='Hello {{ name }}!' -Dname=World
moon run cmd/minijinja -- --expr '1 + 1'
moon run cmd/minijinja -- --helppub(open) trait Object {
fn repr(Self) -> ObjectRepr = _
fn get_value(Self, Value) -> Value? = _
fn get_value_by_str(Self, String) -> Value? = _
fn enumerate(Self) -> Enumerator = _
fn enumerator_len(Self) -> Int? = _
fn is_true(Self) -> Bool = _
fn call(Self, State, Array[Value]) -> Value raise TemplateError = _
fn call_method(Self, State, String, Array[Value]) -> Value raise TemplateError = _
fn render(Self) -> String? = _
fn custom_cmp(Self, Value) -> Int? = _
}impl Show for TemplateErrorimpl Debug for TemplateErrorpub struct Args {
// private fields
}test {
let env = @minijinja.Environment::new()
env.add_function("add", (state, args) => {
let a = @minijinja.Args::new(state, args)
let x : Int = a.next()
let y : Int? = a.next()
a.finish()
@minijinja.Value::from_int(x + y.unwrap_or(1))
})
inspect(
env.render_str("{{ add(1) }} {{ add(1, 2) }}", @minijinja.Value::none()),
content="2 3",
)
}pub struct DynObject {
// private fields
}pub struct Environment {
// private fields
}fn Environment::add_filter(self : Environment, name : String, f : (State, Array[Value]) -> Value raise TemplateError) -> Unitfn Environment::add_function(self : Environment, name : String, f : (State, Array[Value]) -> Value raise TemplateError) -> Unitfn Environment::add_template(self : Environment, name : String, source : String) -> Unit raise TemplateErrorfn Environment::add_test(self : Environment, name : String, f : (State, Array[Value]) -> Value raise TemplateError) -> Unitfn Environment::compile_expression(self : Environment, expr : String) -> Expression raise TemplateErrorfn Environment::render_named_str(self : Environment, name : String, source : String, ctx : Value) -> String raise TemplateErrorfn Environment::render_str(self : Environment, source : String, ctx : Value) -> String raise TemplateErrortest {
let env = @minijinja.Environment::new()
let ctx = @minijinja.Value::from_pairs([
("name", @minijinja.Value::from_string("World")),
])
inspect(env.render_str("Hello {{ name }}!", ctx), content="Hello World!")
}fn Environment::set_formatter(self : Environment, f : (Output, State, Value) -> Unit raise TemplateError) -> Unitfn Environment::set_unknown_method_callback(self : Environment, f : (State, Value, String, Array[Value]) -> Value raise TemplateError) -> Unitfn Environment::template_from_named_str(self : Environment, name : String, source : String) -> Template raise TemplateErrorfn Environment::template_from_str(self : Environment, source : String) -> Template raise TemplateErrorpub(all) enum ErrorKind {
NonPrimitive
NonKey
InvalidOperation
SyntaxError
TemplateNotFound
TooManyArguments
MissingArgument
UnknownFilter
UnknownTest
UnknownFunction
UnknownMethod
BadEscape
UndefinedError
BadSerialization
CannotDeserialize
BadInclude
EvalBlock
CannotUnpack
WriteFailure
OutOfFuel
InvalidDelimiter
UnknownBlock
} derive(Eq, Hash, Debug)pub struct Expression {
// private fields
}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 struct ExtensionKey[T] {
// private fields
}test {
let counter : @minijinja.ExtensionKey[Ref[Int]] = @minijinja.ExtensionKey::new()
let env = @minijinja.Environment::new()
env.add_function("count", (state, _args) => {
let c = state.get_or_insert_extension(counter, () => { val: 0, })
c.val 1
@minijinja.Value::from_int(c.val)
})
inspect(
env.render_str(
"{{ count() }}{{ count() }}{{ count() }}",
@minijinja.Value::none(),
),
content="123",
)
inspect(
env.render_str("{{ count() }}", @minijinja.Value::none()),
content="1",
)
}pub struct Kwargs {
// private fields
}pub struct Output {
// private fields
}pub(all) struct Span {
start_line : Int
start_col : Int
start_offset : Int
end_line : Int
end_col : Int
end_offset : Int
} derive(Eq)pub struct State {
// private fields
}fn State::call_macro(self : State, name : String, args : Array[Value]) -> String raise TemplateErrorfn State::perform_test(self : State, test_name : String, args : Array[Value]) -> Bool raise TemplateErrorfn State::render_block_to(self : State, block : String, write : (StringView) -> Unit raise) -> Unit raise TemplateErrorpub struct StringInput {
// private fields
}pub struct SyntaxConfig {
// private fields
}fn SyntaxConfig::new(block_delimiters? : (String, String), variable_delimiters? : (String, String), comment_delimiters? : (String, String), line_statement_prefix? : String, line_comment_prefix? : String) -> SyntaxConfig raise TemplateErrorpub struct Template {
// private fields
}fn Template::render_to(self : Template, ctx : Value, write : (StringView) -> Unit raise) -> State raise TemplateErrortype Valuetype WhitespaceConfigtest {
let env = @minijinja.Environment::empty()
let state = env.empty_state()
guard @minijinja.builtin_filter("upper") is Some(upper) else {
fail("missing")
}
inspect(
upper.call(state, [@minijinja.Value::from_string("hi")]),
content="HI",
)
}fn debug_tokens(source : String, trim_blocks? : Bool, lstrip_blocks? : Bool) -> Array[String] raise TemplateErrorfn format_string(style : FormatStyle, format_str : String, args : Array[Value]) -> String raise TemplateErrorInstall
Download zipA port of MiniJinja (a Jinja2 template engine) to MoonBit
Dependencies