Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rendering text templates

Use std.template when the layout of a page or message is clearer as text than as a sequence of string concatenations. Register a layout once and pass values for each render. Templates cannot call your program’s functions or read files.

import std.template
from std.template import TemplateError
from std.json import Json, Member

def main() -> Result[(), TemplateError | AllocError | IoError]:
    bounds = std.template.limits()
    mut pages = std.template.engine(&bounds)?
    pages.add("layout", "<h1>{% block title %}Report{% endblock %}</h1>{% block body %}{% endblock %}")?
    pages.add("report", "{% extends 'layout' %}{% block title %}{{ title }}{% endblock %}{% block body %}<ul>{% for name in names %}<li>{{ name }}</li>{% else %}<li>No entries</li>{% endfor %}</ul>{% endblock %}")?
    values = Json.Object([
        Member("title", Json.Text("Team & friends")),
        Member("names", Json.Array([Json.Text("Ada"), Json.Text("Grace")]?)),
    ]?)
    print(pages.render("report", &values)?)?
    Ok(())
<h1>Team &amp; friends</h1><ul><li>Ada</li><li>Grace</li></ul>

The context uses the JSON value model explicitly; no class-to-template conversion happens automatically. Fields such as {{ person.name }} look up object members. A missing field returns an error rather than silently producing an empty string. An empty array takes the loop’s else branch, which is useful for empty reports.

Interpolated text is HTML-escaped by default. Keep user text in HTML text or quoted ordinary attributes. HTML escaping does not validate links or JavaScript; do not place user values in scripts, styles or tag names. For a plain-text message, call pages.render("report", &values, html=False) with a plain-text template. The safe filter deliberately disables escaping for trusted markup.

Set limits for your application before constructing the engine: for example, lower max_output for a short notification. Registration and rendering are fallible, and the example propagates those failures to the caller. Keep the engine and render it again with different values to reuse its parsed templates.

For a small fragment assembled in your program, use std.template.escape rather than a chain of string replacements. Choose HtmlContext.Text for text children or the quote-specific attribute mode for each attribute value. For example, quotes should stay literal in text but must be escaped inside a double-quoted attribute:

from std.template import escape, HtmlContext, TemplateError

def main() -> Result[(), TemplateError | AllocError | IoError]:
    label = "Say \"hello\" & smile"
    attribute = escape(&label, HtmlContext.DoubleQuotedAttribute, max_bytes=256)?
    child = escape(&label, HtmlContext.Text, max_bytes=256)?
    print(f"<button title=\"{attribute}\">{child}</button>"?)?
    Ok(())
<button title="Say &quot;hello&quot; &amp; smile">Say "hello" &amp; smile</button>

max_bytes includes the expanded entities. The helper returns ordinary text: passing its output into a template would escape it again. Keep raw values in Json.Text when using Engine.render, and use these helpers once per original value when assembling HTML directly. The context is your choice; the library does not detect HTML positions or validate URLs, scripts, styles or names.

See the std.template reference for the supported syntax, filters, inheritance rules and precise limits.