moonjinja

A Jinja-style runtime template engine for MoonBit

template
jinja
html
rendering
moon add ZSeanYves/moonjinja@0.2.1
Download zip
Author
Version
0.2.1
License
Apache-2.0
Last updated
25 days ago
Downloads
24
README

#MoonJinja

MoonJinja is a runtime Jinja-style template engine for MoonBit. Version 0.2 uses an Environment registry, accepts any ToJson value, caches compiled templates, and runs on wasm, wasm-gc, JavaScript, and native targets.

#Install

moon add ZSeanYves/moonjinja@0.2.1

Add the package to moon.pkg:

import {
"ZSeanYves/moonjinja",
}

#Quick Start

fn render_page() -> String raise Error {
let environment = @moonjinja.Environment::new()
environment.set_options(
@moonjinja.RenderOptions::default().with_autoescape(true),
)
environment.add_template(
"hello.html",
"Hello {{ user.name | upper }}!",
)
environment.render("hello.html", {
"user": { "name": "MoonBit" },
})
}

The complete executable example is in src/examples/basic:

moon run src/examples/basic

#Loading Templates

Templates can be registered directly or resolved by an application-owned loader. The core package does not access the filesystem.

let environment = @moonjinja.Environment::new()
environment.set_loader(name => {
match name {
"layout.html" => Ok("<{% block body %}{% endblock %}>")
"page.html" => Ok(
"{% extends \"layout.html\" %}" +
"{% block body %}{{ message }}{% endblock %}",
)
_ => Err("template not found: " + name)
}
})
let output = environment.render("page.html", { "message": "Ready" })

Loader results and parsed templates are cached. Replacing the loader invalidates loader-owned entries while preserving templates added with add_template. reload_template refreshes one loader entry, clear_cache reparses all registered sources, and a cache capacity of zero disables loader source snapshots as well as parsed-template caching.

For development reloads, set_versioned_loader returns (source, version); the environment recompiles only when the version token changes.

#Extensions

Built-ins and user extensions use the same registry contracts:

environment.add_filter("surround", (value, args) => {
let marker = match args {
[first, ..] => first.to_display_string()
[] => "*"
}
Ok(@moonjinja.Value::StrValue(
marker + value.to_display_string() + marker,
))
})
environment.add_function("answer", _ =>
Ok(@moonjinja.Value::IntValue(42)))
environment.add_test("positive", (value, _) =>
match value {
@moonjinja.Value::IntValue(number) => Ok(number > 0)
_ => Err("positive expects an integer")
})

Context-aware variants accept keyword arguments and receive the logical template name, autoescape state, and sandbox state through ExtensionContext. Values are deep-copied at the callback boundary.

#Supported Syntax

  • Variables, dotted lookup, indexing, list/map literals, arithmetic, **, ~, chained comparisons, value-preserving and/or, inline conditionals, in, and is tests.
  • if/elif/else, for/else, loop metadata, break, and continue.
  • set, scoped with, include with or without context, and raw blocks.
  • macro, default parameters, call/caller, import, and from import.
  • Multi-level extends, block, super(), and self.block().
  • Explicit - whitespace markers plus environment-level trim_blocks and lstrip_blocks.

Built-in filters include upper, lower, trim, split, safe, escape (e), length, default, join, replace, first, last, reverse, abs, string, capitalize, title, wordcount, sum, min, max, unique, sort, keys, values, and items. Built-in functions include range, list, dict, and namespace.

#Safety And Limits

Enable autoescape for HTML output. escape always encodes unsafe text and returns a safe string; safe must only be used with trusted HTML.

RenderOptions configures strict undefined values, whitespace behavior, fuel, parser/renderer/include depth, UTF-8 output and source byte limits, cache capacity, and maximum range allocation. Template names reject absolute paths, drive-letter paths, and .. segments. Include and inheritance cycles are rejected.

with_sandbox(true) forces autoescape, rejects safe, and denies custom extensions unless explicitly admitted with allow_extension_in_sandbox. Host callbacks and loaders remain trusted application code and need an outer process/container boundary when templates are fully untrusted.

CompiledTemplate::render_to streams output chunks to a callback without building the complete result string.

#Validation

moon fmt --check moon check --target all --deny-warn moon test --target all --deny-warn moon bench --target native --release --deny-warn

See compatibility, 0.2 migration, and performance for detailed behavior.

#License

Apache-2.0.

#
JinjaError

pub(all) suberror JinjaError {
LexerError(String)
ParseError(String)
RenderError(String)
}

#
JinjaError::diagnostic

fn JinjaError::diagnostic(self : JinjaError) -> ErrorDiagnostic

#
JinjaError::message

fn JinjaError::message(self : JinjaError) -> String

#
JinjaError::stage

fn JinjaError::stage(self : JinjaError) -> ErrorStage

#
CompiledTemplate

pub struct CompiledTemplate {
// private fields
}

#
CompiledTemplate::render

fn[T : ToJson] CompiledTemplate::render(self : CompiledTemplate, value : T) -> String raise JinjaError

#
CompiledTemplate::render_json

fn CompiledTemplate::render_json(self : CompiledTemplate, value : Json) -> String raise JinjaError

#
CompiledTemplate::render_json_to

fn CompiledTemplate::render_json_to(self : CompiledTemplate, value : Json, write : (String) -> Unit) -> Unit raise JinjaError

Render JSON data incrementally into a caller-provided sink.

#
CompiledTemplate::render_to

fn[T : ToJson] CompiledTemplate::render_to(self : CompiledTemplate, value : T, write : (String) -> Unit) -> Unit raise JinjaError

Convert a MoonBit value with ToJson and render it incrementally.

#
Environment

pub struct Environment {
// private fields
}

#
Environment::add_context_filter

fn Environment::add_context_filter(self : Environment, name : String, filter : (ExtensionContext, Value, Array[Value], Map[String, Value]) -> Result[Value, String]) -> Unit

#
Environment::add_context_function

fn Environment::add_context_function(self : Environment, name : String, function : (ExtensionContext, Array[Value], Map[String, Value]) -> Result[Value, String]) -> Unit

#
Environment::add_context_test

fn Environment::add_context_test(self : Environment, name : String, test_function : (ExtensionContext, Value, Array[Value], Map[String, Value]) -> Result[Bool, String]) -> Unit

#
Environment::add_filter

fn Environment::add_filter(self : Environment, name : String, filter : (Value, Array[Value]) -> Result[Value, String]) -> Unit

#
Environment::add_function

fn Environment::add_function(self : Environment, name : String, function : (Array[Value]) -> Result[Value, String]) -> Unit

#
Environment::add_template

fn Environment::add_template(self : Environment, name : String, source : String) -> Unit raise JinjaError

#
Environment::add_test

fn Environment::add_test(self : Environment, name : String, test_function : (Value, Array[Value]) -> Result[Bool, String]) -> Unit

#
Environment::allow_extension_in_sandbox

fn Environment::allow_extension_in_sandbox(self : Environment, name : String) -> Unit

Allow a registered extension to execute while sandbox mode is enabled.

#
Environment::clear_autoescape_callback

fn Environment::clear_autoescape_callback(self : Environment) -> Unit

#
Environment::clear_cache

fn Environment::clear_cache(self : Environment) -> Unit

Clear parsed templates while preserving registered source strings.

#
Environment::get_template

fn Environment::get_template(self : Environment, name : String) -> CompiledTemplate raise JinjaError

#
Environment::new

#
Environment::reload_template

fn Environment::reload_template(self : Environment, name : String) -> Unit

Invalidate one parsed template and any loader-owned source snapshot.

#
Environment::remove_filter

fn Environment::remove_filter(self : Environment, name : String) -> Unit

#
Environment::remove_function

fn Environment::remove_function(self : Environment, name : String) -> Unit

#
Environment::remove_template

fn Environment::remove_template(self : Environment, name : String) -> Unit

#
Environment::remove_test

fn Environment::remove_test(self : Environment, name : String) -> Unit

#
Environment::render

fn[T : ToJson] Environment::render(self : Environment, name : String, value : T) -> String raise JinjaError

#
Environment::set_autoescape_callback

fn Environment::set_autoescape_callback(self : Environment, select : (String) -> Bool) -> Unit

Select autoescape per logical template name (for example by suffix).

#
Environment::set_loader

fn Environment::set_loader(self : Environment, loader : (String) -> Result[String, String]) -> Unit

#
Environment::set_options

fn Environment::set_options(self : Environment, options : RenderOptions) -> Unit

#
Environment::set_versioned_loader

fn Environment::set_versioned_loader(self : Environment, loader : (String) -> Result[(String, String), String]) -> Unit

Install a loader that returns (source, version) and automatically recompiles an entry when its version changes.

#
ErrorDiagnostic

pub struct ErrorDiagnostic {
// private fields
}

Structured view over a lexer, parser, or render failure.

#
ErrorDiagnostic::message

fn ErrorDiagnostic::message(self : ErrorDiagnostic) -> String

#
ErrorDiagnostic::span

#
ErrorDiagnostic::stage

#
ErrorDiagnostic::template_name

fn ErrorDiagnostic::template_name(self : ErrorDiagnostic) -> String?

#
ErrorDiagnostic::template_trace

fn ErrorDiagnostic::template_trace(self : ErrorDiagnostic) -> Array[String]

#
ErrorStage

pub(all) enum ErrorStage {
Lexer
Parser
Render
} derive(Eq,
Debug
)

#
ExtensionContext

pub struct ExtensionContext {
// private fields
}

Read-only metadata passed to context-aware extensions.

#
ExtensionContext::autoescape

fn ExtensionContext::autoescape(self : ExtensionContext) -> Bool

#
ExtensionContext::sandboxed

fn ExtensionContext::sandboxed(self : ExtensionContext) -> Bool

#
ExtensionContext::template_name

fn ExtensionContext::template_name(self : ExtensionContext) -> String

#
RenderOptions

pub struct RenderOptions {
// private fields
}

#
RenderOptions::default

fn RenderOptions::default() -> RenderOptions

#
RenderOptions::with_autoescape

fn RenderOptions::with_autoescape(self : RenderOptions, enabled : Bool) -> RenderOptions

#
RenderOptions::with_cache_capacity

fn RenderOptions::with_cache_capacity(self : RenderOptions, max_cache_size : Int) -> RenderOptions

Set the parsed-template cache capacity. Zero disables the cache.

#
RenderOptions::with_compile_limits

fn RenderOptions::with_compile_limits(self : RenderOptions, max_template_size : Int, max_range_size : Int) -> RenderOptions

#
RenderOptions::with_features

fn RenderOptions::with_features(self : RenderOptions, macros : Bool, multi_template : Bool, loop_controls : Bool) -> RenderOptions

Enable or disable optional language feature groups at compile time.

#
RenderOptions::with_limits

fn RenderOptions::with_limits(self : RenderOptions, fuel : Int, max_recursion : Int, max_output_size : Int) -> RenderOptions

#
RenderOptions::with_sandbox

fn RenderOptions::with_sandbox(self : RenderOptions, enabled : Bool) -> RenderOptions

Enable the restricted execution policy and force HTML autoescape.

#
RenderOptions::with_strict_undefined

fn RenderOptions::with_strict_undefined(self : RenderOptions, enabled : Bool) -> RenderOptions

#
RenderOptions::with_structural_limits

fn RenderOptions::with_structural_limits(self : RenderOptions, max_parse_depth : Int, max_include_depth : Int) -> RenderOptions

#
RenderOptions::with_whitespace_control

fn RenderOptions::with_whitespace_control(self : RenderOptions, trim_blocks : Bool, lstrip_blocks : Bool) -> RenderOptions

#
SourceSpan

pub(all) struct SourceSpan {
start : Int
end : Int
line : Int
column : Int
end_line : Int
end_column : Int
} derive(Eq,
Debug
)

UTF-8 byte and human-readable source range.

#
Value

pub(all) enum Value {
Undefined
Null
IntValue(Int)
DoubleValue(Double)
BoolValue(Bool)
StrValue(String)
SafeStr(String)
ListValue(Array[Value])
MapValue(Map[String, Value])
}

Dynamic values exposed to custom functions and filters.

#
Value::as_int

fn Value::as_int(self : Value) -> Int?

#
Value::as_string

fn Value::as_string(self : Value) -> String?

#
Value::to_display_string

fn Value::to_display_string(self : Value) -> String