vessel/doc/temple.md
Arija A. e3538e725d
doc(temple): Fix wording
Signed-off-by: Arija A. <ari@ari.lt>
2025-06-26 15:18:53 +03:00

46 KiB
Raw Permalink Blame History

Temple Templating Language Specification

This document defines the syntax and semantics of the Temple templating language - a modern, embeddable template language (inspired by engines like Jinja) designed for high-performance use in C-based web applications.

1. Introduction

Temple allows template authors to intermix static text with dynamic logic. Output expressions are enclosed in {{ ... }}, and control statements in {% ... %}. Comments use {# ... #}. Blocks of logic (loops, conditionals, macros) begin with {% ... %} and must be terminated with an {% end %} tag. (Notably, a block opened with {%- block NAME -%} must be closed with {%- end -%} to correctly match whitespace-trimming syntax.)

Temple is strictly typed and case-sensitive. Identifiers must begin with a lowercase letter or underscore (identifiers starting with an uppercase letter are not permitted to avoid conflicts). The language distinguishes value types (strings, numbers, booleans, null, infinity, arrays, ranges) from reference types (arrays, hashmaps, objects provided by C). By default, output is not automatically escaped; template authors are strongly encouraged to use the escape function or raw output blocks to sanitise content, or set up their Temple engine to auto-escape output expressions.

Key concepts of Temple include variable scopes (global, local), data types (with literal syntax), control flow (if, for, while, break, continue, finish), macros, blocks and template inheritance, imports and namespacing, function calls, and expression evaluation and operators. Examples are provided for each construct.

Its file extension .lpt comes from the transformation chain of: temple -> tpl (taken) -> lpt (reversed).

2. Key Features

  • Embedded Logic: Intermix static text with dynamic expressions ({{ ... }}) and statements ({% ... %}).
  • Data Types: Built-in support for strings, integers, floats, booleans, null, infinity, arrays, and ranges.
  • String Formatting: Format f-strings with embedded expressions, e.g. f"Hello, {name}!".
  • Variables and Scoping: Clear scoping rules (global, local). The set statement can assign to variables, object members, array indices, or hashmap entries. The unset statement can remove a variable.
  • Control Flow: Conditionals (if/elif/else), loops (for, while), loop-control (break, continue), and a finish statement to end rendering.
  • Macros: Reusable template components (functions) defined with {% macro ... %}...{% end %} and invoked via normal expressions {{ name(...) }}. Macros support positional arguments, default values, and variadic parameters.
  • Blocks and Inheritance: Named blocks ({% block NAME %}...{% end %}) that child templates can override; inheritance allows creating base templates with overridable sections.
  • Imports and Namespaces: Import other templates at runtime via {% import EXPRESSION : namespace %}. Imported templates can export macros or variables into the given namespace. Support for explicit {% namespace %} statements as well.
  • Function Calls: Call built-in or C-defined functions with normal syntax (func(arg1, arg2, ...)).
  • Operators and Expressions: C-style operators (+, -, *, /, %, comparisons, logic, bitwise, ternary ? :, etc.) with well-defined precedence. Assignment (= and +=, etc.) has the lowest precedence.
  • Whitespace Control: Automatically trim whitespace around tags using {%- ... -%}.
  • Extensibility: Add custom functions in C; C data (arrays, hashmaps, objects) can be passed into templates.
  • Error Handling: Syntax errors are detected at compile time; runtime errors (e.g. invalid lookup, undefined variable) raise exceptions or yield null as appropriate.

Each of the above features is detailed in the following sections.

3. Strings

Temple supports several string literal forms:

  • Normal string: Delimited by double quotes with C-style escapes. Example: "Hello\nWorld!".
  • Raw string: Prefixed with r, disables escape processing. Example: r"C:\path\file.txt".
  • Format (f-)string: Prefixed with f, allows embedded expressions in { }. Example: f"Hello, {user.name}!".

Format strings evaluate each embedded expression and replace it with its string value. For instance, if user.name is "Alice", then f"Hi, {user.name}" outputs Hi, Alice.

4. Numbers

Temple has three numeric types:

  • Integers (64-bit signed): Decimal (42 or 0d42), hexadecimal (0x2A), octal (0o52), or binary (0b101010).
  • Floats: IEEE-754 double precision, e.g. 3.14, 6.02e23.
  • Infinity: A value representing an infinitely large (or -inf = infinitely small) value, with syntax of inf or -inf. Infinity is always truthy.

Numeric literals can appear in expressions and support standard arithmetic. Example: {{ 2 + 2 }} outputs 4.

5. Booleans and Null

Temple has boolean and null literals:

  • Boolean: true or false.
  • Null: null, representing the absence of a value. Null is a falsy value.

Example: {{ 3 > 2 }} yields true. A conditional like {% if user == null %}...{% end %} checks for a null reference, similarly to C's NULL.

6. Arrays

Arrays are ordered, zero-indexed collections (heterogeneous allowed). Literal arrays use square bracket syntax:

[1, "two", false, null]

Elements are evaluated in order and stored in memory. You can index into an array with [index]. For example, if items = ["a","b","c"], then items[1] yields "b".

7. Ranges

Ranges represent sequences of integers, useful for loops. A range literal has the form from:to/step, with from, to, and step all optional.

Valid forms of ranges are:

  • :N means 0 to N (inclusive)
  • M: means M to end (or infinity)
  • M:N means M to N inclusive (step 1).
  • M:N/S means from M to N inclusive in steps of S.

Example: {{ 1:5 }} produces the range 1-5, which iterates as 1,2,3,4,5. You can loop over a range in a for-loop, or access its individual elements using the lookup operator. For example:

{% for i : 1:5 %}
    {{ i }}
{% end %}

This prints 1 2 3 4 5. Similarly:

{{ (1:5)[1] }}

This prints 2.

8. Hashmaps and Objects

Temple can work with hashmaps (associative arrays) and objects provided by the C environment (in case of hashmap - also a literal). There is no literal syntax for objects; they must be supplied by the host program. You access a hashmap value by key:

{{ config["site_name"] }}

or an object's property by member lookup:

{{ user.name }}

If config["site_name"] is "TempleSite", the first example outputs TempleSite.

Variables, arrays, hashmaps, and objects are distinct types: integers, floats, booleans, strings, and null are value types (copied on assignment), whereas arrays, hashmaps, and objects are reference types. Assigning a reference type (array/object) to a new variable does not copy the contents, but gives another reference to the same underlying data. Modifying it through one variable affects all references to it. Temple does not perform automatic deep copy of arrays or objects.

9. Keywords

Temple reserves a set of keywords (all lowercase) for its syntax. These cannot be used as identifiers. The main keywords include:

  • Control flow: if, elif, else, for, while, break, continue, finish.
  • Macros: macro, unmacro.
  • Variables: set, unset.
  • Import: import.
  • Namespacing: namespace.
  • Blocks: block, parent, end.
  • Literals: true, false, null, inf.

A full table of keywords is provided below (literal usage shown):

Keyword Meaning
if Start an if-statement
elif Else-if in an if-statement
else Else clause in an if-statement
for Start a for-loop
while Start a while-loop
break Break out of nearest loop
continue Continue to next loop iteration
finish Stop rendering entire template
set Assign value (set x = ...)
unset Remove a variable (unset x)
macro Define a macro (reusable component)
unmacro Undefine a macro
import Import another template file
namespace Create a new namespace
block Define a named block for inheritance
end End the last opened block/statement
true/false Boolean literals
null Null literal
inf Infinitely large number
parent Parent of an inherited block

10. Identifiers and Names

Identifiers name variables, macro parameters, functions, and alike. They are composed of letters, digits, or underscores. Identifiers are case-sensitive (e.g. Count and count are different). As a convention, Temple treats UPPER_SNAKE_CASE variables as constants, which means they can be set once, but not changed in the future.

Examples of valid identifiers: user_name, _count, value1. Invalid: 1var (starts with digit), @ar (starts with an invalid symbol).

11. Variables and Scoping

Temple has global (template-level) and local (within a block or macro) variable scopes. Variables hold any value type or references. Assigning to a variable that did not previously exist creates it in the current scope; if used at top level it is global.

  • Variable Declaration/Assignment: Use {% set NAME = expression %} to assign. This can create or update a variable. Compound assignments (+=, -=, etc.) are also supported. For example, {% set count = 0 %} and later {% set count += 1 %}.
  • Macro Variables: Inside a macro, parameters and any variables set are local to that macro.
  • Scope Rules: By default, a {% set %} affects the nearest (innermost) scope. A set at template top-level sets a global; inside a macro it sets a local (macro) variable; inside a block or loop it sets a variable local to that block.
  • Unsetting: {% unset NAME %} removes a variable from the current scope (making it undefined). Only variables in the current or global scope can be unset; you cannot unset object members or macro parameters. Example: {% unset user %}.

Example:

{% set x = 10 %}
Value of x is {{ x }}.  {# outputs 10 #}

{% set x += 5 %}
Now x is {{ x }}.       {# outputs 15 #}

12. Assignment (set)

The set statement assigns or updates a value. Syntax:

{% set target = expr %}

target can be:

  • A variable or parameter name (e.g. x)
  • An object member (e.g. user.name)
  • An array or hashmap lookup (e.g. arr[2], dict["key"])

All forms above may appear on the left side. For example, if user.name exists, {% set user.name = "Alice" %} modifies that property. Similarly {% set config["mode"] = "debug" %} sets a hashmap entry.

Compound assignment is also allowed:

{% set count += 1 %}

which is equivalent to count = count + 1. All standard assignment operators (+=, -=, *=, /=, %=, bitwise &=, |=, etc.) are supported (see operators section).

13. Unassignment (unset)

The {% unset NAME %} statement removes a variable from scope. After unsetting, the variable name is not longer defined. For example:

{% set temp = 5 %}
{% unset temp %}
Value is {{ temp }}.  {# prints nothing or null #}

Unsetting does not apply to object members or array lookups (hashmap lookups are allowed) - those cannot be removed this way.

14. Macros (Definition)

A macro is a reusable template fragment (like a function). Macros can be defined only at the root level (not nested inside other blocks). They use {% macro %} and {% end %} or, similarly, its whitespace-trimming counterpart {%- macro -%} and {%- end -%}:

{% macro greet(name, greeting = "Hello") %}
    {{ greeting }}, {{ name }}!
{% end %}
  • Name: The macro's name (greet above).
  • Parameters: A comma-separated list in parentheses (optional). Parameters may have default values (see below). Variadic parameters are supported by prefixing with * (spread), packing them into an array, similarly, keyword arguments are supported using ** (kspread), packing them into a hashmap.
  • Body: The content inside macro/end, which can include output, logic, and variables.

Once defined, a macro's body is not executed until it is expanded (called). Macros cannot be redefined in the same template (a second definition raises an error). You may conditionally remove a macro with unmacro (see next section).

Default Parameters: Macro parameters may have default values. In the above example, greeting = "Hello" means if no argument is passed for greeting, it defaults to "Hello". You can define multiple defaults, e.g. foo(bar=1, baz=2).

Variadic Parameters: Using a leading * marks a parameter as variadic (it collects all extra positional arguments into a list), similarly ** marks the parameter as keyword-variadic (it collects all extra keyword arguments into a hashmap). For example:

{% macro list_items(*items, **kwds) %}
    <ul>
        {% for item : items %}
            <li>{{ item }}</li>
        {% end %}
    </ul>
    <ul>
        {% for kv : kwds %}
            <li>{{ kv.key }}: {{ kv.value }}</li>
        {% end %}
    </ul>
{% end %}

Calling list_items("a","b","c") passes all arguments in the array items. Similarly, Calling list_items(a="a",b="b",c="c") passes all arguments (and their names) in the hashmap kwds.

15. Macros (Invocation and unmacro)

To invoke (expand) a macro, use the simple {{ ... }} expressions. You can call with or without arguments:

{{ greet("Temple", greeting="Hi") }}

This calls the greet macro defined earlier, passing "Temple" for the first parameter and overriding greeting to "Hi". Arguments are passed positionally and can also be specified by name. All parameters are required.

Within the macro body, parameters are accessed by their name. For example, in greet(name), use {{ name }} inside the macro.

Undefining a Macro: Use {% unmacro name %} to remove a previously defined macro. After unmacro, calling that macro will be an error.

16. Blocks

A block is a named placeholder that can be overridden in child templates. Blocks are defined in a base template and optionally redefined in child templates:

{% block title %}
    Default Title
{% end %}

This defines a block named title. In an extending template, one can write:

{% block title %}
    My Custom Title
{% end %}

to override it. When rendered, the child's block content replaces the base content. Blocks can also be nested in loops or conditionals if needed.

Important: If you use the whitespace-trimming syntax {%- block name -%}, then you must close it with {%- end -%} (not just {% end %}) to properly match the trimmed block.

Example:

<div>
    {%- block header -%}
        <h1>Welcome</h1>
    {%- end -%}
</div>

17. Template Inheritance

Temple supports a simple inheritance mechanism using blocks. A child template can “inherit” from a base by including that template and overriding blocks. One typically renders the base template and it will include blocks from child templates based on naming convention or import.

For example, if base.lpt contains:

<html>
  <body>
    {% block content %}
      <p>Default content.</p>
    {% end %}
  </body>
</html>

A child template might do:

{% import "base.lpt" %}
{% block content %}
  <p>Overridden content!</p>
{% end %}

In effect, the imported base template's content block is replaced by the child's definition. Exact inheritance semantics depend on how templates are assembled at render time in the C host.

Blocks from multiple levels can override each other. Use the special parent statement to access the parent block content from an overridden block:

{% block content %}
  <p>Child content</p>
  {% parent %}
{% end %}

18. Imports and Namespaces

Temple can also import other template files at runtime with {% import %}. The syntax is:

{% import "file.lpt" : namespace %}

Temple uses a colon to assign a namespace. Here, namespace is an identifier under which the imported template's macros and variables are grouped. The expression before the colon can be any string expression or template expression that evaluates to a filename. For example:

{% import "forms.lpt" : forms %}

This loads forms.lpt. To call a macro field defined in forms.lpt, use forms.field(...). Namespace imports help avoid naming conflicts.

By default, imported templates are evaluated in global context if there is no specified namespace, otherwise a new scope is created.

Example:

{% import "utils.lpt" : util %}
{{ util.format_name(user.name) }}

This imports utils.lpt under util, then calls its format_name function.

Or, without a namespace:

{% import "utils.lpt" %}
{{ format_name(user.name) }}

19. Expressions

An expression computes a value. Temple expressions can include:

  • Literals: strings, numbers, booleans, null, arrays, hashmaps, ranges (see earlier sections). Example: "hello", 42, [1,2,3], {a: b}.
  • Variables and lookups: identifiers, array indexing, object member access. E.g. user, items[0], config["key"], user.name.
  • Function calls: calling built-in functions or macros (see sections below). Syntax: f(arg1, arg2, ...).
  • Operators: arithmetic (+, -, *, /, %), comparison (==, !=, <, >, <=, >=), logical (&&, ||, !), bitwise (&, |, ^, ~, <<, >>), and the conditional ? : operator.
  • Range: Range values.

Expressions follow a specific precedence (highest to lowest):

  1. Primary: Literals, variable lookup, parenthesised subexpressions, function calls.
  2. Unary: +x, -x, !x, ~x (bitwise NOT).
  3. Multiplicative: *, /, %.
  4. Additive: +, -.
  5. Shifts: <<, >>.
  6. Relational: <, >, <=, >=, @ (x [included] in y).
  7. Equality: ==, !=.
  8. Bitwise AND, XOR, OR: &, ^, |.
  9. Logical AND, OR: &&, ||.
  10. Ternary: cond ? a : b.
  11. Range: after conditional, the range operator (:) is applied (turning the expression into a range if used).
  12. Assignment: =, +=, etc. (lowest precedence). Used with the set keyword.

All binary operators are left-associative. The ternary ? : is right-associative.

Example of expression evaluation:

{{ 1 + 2 * 3 == 7 && !false ? 10 : 0 }}

Here 2*3 is evaluated first (6), then 1+6 = 7, then 7==7 is true, !false is true, so true && true is true. The ternary yields 10. So the output is 10:

{{ ((1 + (2 * 3)) == 7) && (!false) ? 10 : 0 }}

Note regarding '@' operator: the @ operator is similar to Python's in, that is 10 @ some_set = 10 in some_set.

20. Operators

Temple supports the following operators:

  • Arithmetic: + (add), - (subtract), * (multiply), / (divide), % (modulus).
  • Comparison: ==, !=, <, >, <=, >=.
  • Logical: && (and), || (or), ! (not).
  • Bitwise: & (AND), | (OR), ^ (XOR), ~ (NOT), << (shift left), >> (shift right).
  • Assignment: = (simple assignment), and combined forms (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=).
  • Ternary: cond ? a : b, where a is evaluated if cond is true, else b.
  • Spread: unary * can be used in certain contexts (e.g. marking an argument as variadic, or unpacking a value in an expand).
  • KSpread: unary ** can be used in certain contexts (e.g. marking an argument as keyword-variadic, or unpacking a hashmap in an expand).

The precedence of = and other assignment operators is the lowest of all operators. For example, in a = b + c * d, multiplication and addition occur before the assignment.

The operator symbols are used both in expressions and in statements. For example, a for-loop header uses : (distinguished from the range operator).

Operator examples:

{{ -x }}                        {# unary negation #}
{{ 2 + 3 * 4 }}                 {# multiplication before addition #}
{{ (a < b) && (c != d) }}       {# logical and of two comparisons #}
{{ x == y ? "yes" : "no" }}     {# ternary conditional #}

A full table of operators with meaning is given above in the Expressions section.

21. Conditionals (if / elif / else)

Temple's if statement selects code based on a condition. Syntax:

{% if CONDITION %}
    ...content...
{% elif OTHER_CONDITION %}
    ...content...
...repeated elifs...
{% else %}
    ...content...
{% end %}
  • The {% if %} block evaluates CONDITION (an expression).
  • If true (non-zero, non-null, non-false), the block is executed; otherwise the next elif or else is tried.
  • Multiple {% elif %} clauses can follow.
  • An optional {% else %} is executed if all prior conditions are false.
  • Every if must be closed with {% end %}.

Example:

{% if user.is_admin %}
    Admin view
{% elif user.is_logged_in %}
    Welcome, {{ user.name }}!
{% else %}
    Please log in.
{% end %}

Temple also supports the ternary operator as an expression: condition ? a : b. Example:

{{ user.is_admin ? "Admin" : "User" }}

This yields "Admin" if user.is_admin is true, else "User".

22. For Loops

The for loop iterates over arrays, ranges, strings, or anything iterable. Syntax:

{% for VAR : ITERABLE %}
    ...body...
{% end %}
  • VAR becomes the loop variable.
  • ITERABLE is an array, range, string, or hashmap. If it's a hashmap or object, the loop iterates over keys or entries (depending on implementation).
  • On each iteration, VAR is set to the current item (for arrays/ranges/strings).

Example iterating a list:

{% for item : items %}
    {{ item }},
{% end %}

If items = ["a","b","c"], this outputs a, b, c,.

Iterating a range:

{% for i : 1:3 %}
    {{ i }}
{% end %}

This outputs 1 2 3.

Iterating characters in a string:

{% for ch : "hi" %}
    {{ ch }}
{% end %}

Produces h i.

23. While Loops

A while loop repeats as long as a condition holds. Syntax:

{% while CONDITION %}
    ...body...
{% end %}
  • CONDITION is an expression evaluated before each iteration.
  • If true, the body executes and then the condition is checked again; if false, the loop exits.

Example:

{% set idx = 0 %}
{% while idx < 5 %}
    {{ idx }},
    {% set idx += 1 %}
{% end %}

This prints 0,1,2,3,4, then stops when idx reaches 5.

24. Loop Control (break, continue)

Inside loops, you can use {% break %} or {% continue %}:

  • break: Immediately exit the innermost loop.
  • continue: Skip the rest of the current iteration and continue with the next.

Example:

{% for x : [1,2,3,4] %}
    {% if x == 3 %}
        {% break %}
    {% end %}
    {{ x }}
{% end %}

This outputs 1 2 and then breaks out when x == 3.

And:

{% for x : [1,2,3,4] %}
    {% if x == 2 %}
        {% continue %}
    {% end %}
    {{ x }}
{% end %}

This outputs 1 3 4 (skipping 2).

Neither break nor continue can appear outside of a loop (doing so is an error). These statements must be closed with {% end %} if trimmed, but trim is not allowed on break/continue (as it affects parsing).

25. Finish Statement

The {% finish %} statement immediately stops rendering the entire template. Any content after a finish in the execution flow is ignored. It can be useful to conditionally abort rendering. Example:

{% if user == null %}
    {% finish %} {# abort if no user #}
{% end %}
<p>Welcome, {{ user.name }}!</p>

If user is null, the finish triggers and the greeting is never printed.

26. Function Calls

Temple supports calling built-in or custom functions from expressions. The syntax is the same as macros:

{{ func_name(arg1, arg2, ...) }}

Arguments can be positional and keyword. For example, if there is a C-defined function format_date(date, format), you might write format_date(today, "%Y-%m-%d").

Built-in functions include common utilities. Example from built-ins: escape("<b>bold</b>") yields &lt;b&gt;bold&lt;/b&gt;. You can also call them in statements, e.g. {% set s = capitalize(name) %} to uppercase the first character of name.

Macros are also invoked via the expand statement as described earlier (they are not called directly in expressions).

27. String Formatting (f-strings)

Temple's format strings (f"...") allow embedding expressions inside a string. Inside an f"..." literal, any substring of the form {expression} is evaluated and interpolated into the result. For example:

{% set name = "Alice" %}
{{ f"Hello, {name}!" }}

This outputs Hello, Alice!. Expressions inside {} follow all normal expression rules (they can call functions, use operators, etc.). You can have multiple {} parts:

{{ f"{user.first_name} {user.last_name} ({user.age} years old)" }}

28. Output Expressions and Escaping

An output expression is written as {{ expression }}. The expression is evaluated and its result is converted to a string and written into the output. For example, {{ user.name }} inserts the name variable's string value.

By default, Temple does not automatically escape HTML or other content in output expressions. For security (e.g. preventing XSS), use the escape function or set up your Temple engine to auto-escape output expressions using a specified function.

Whitespace around {{ and }} is not significant, but you may use the trimming syntax {%- or -%} to remove surrounding whitespace in the rendered output.

29. Built-in Functions

Temple includes many built-in functions for common tasks.

Function Description Example
abs(value) Returns the absolute value of a number. -5 => 5
batch(sequence, size, fillvalue=null) Breaks a list into chunks of a given size. [1, 2, 3, 4, 5], 2 => [[1, 2], [3, 4], [5]]
capitalize(string) Capitalizes the first character of a string and lowercases the rest. "hello" => "Hello"
center(string, width, fillchar=" ") Centers a string within a given width, padded by a character. "hello", 10 => " hello "
choice(sequence) Returns a random item from a sequence. [1, 2, 3, 4] => 3 (random)
count(sequence, value) Counts the number of occurrences of a value in a sequence. [1, 2, 3, 3, 4], 3 => 2
escape(string) Escapes HTML characters in a string. "<div>" => "&lt;div&gt;"
filesizeformat(value) Formats a number of bytes into a human-readable size. 1048576 => "1.0 MB"
first(sequence) Returns the first item in a sequence. [1, 2, 3, 4] => 1
float(value) Converts a value to a floating point number. "3.14" => 3.14
format(string, *args) Formats a string using the provided arguments and keyword arguments. "Hello %s!", "World" => "Hello World!"
join(sequence, delimiter) Joins the elements of a sequence with a delimiter. ['a', 'b', 'c'], ',' => 'a,b,c'
len(sequence) Returns the length of a sequence. [1, 2, 3] => 3
array(value) Converts a value to an array. "hello" => ['h', 'e', 'l', 'l', 'o']
lower(string) Converts a string to lowercase. "HELLO" => "hello"
max(sequence) Returns the maximum item in a sequence. [1, 2, 3] => 3
min(sequence) Returns the minimum item in a sequence. [1, 2, 3] => 1
random(sequence) Returns a random item from a sequence. [1, 2, 3, 4] => 2 (random)
replace(value, old, new) Replaces occurrences of a substring in a string. "Hello World", "World", "Temple" => "Hello Temple"
reverse(sequence) Reverses the order of items in a sequence. [1, 2, 3] => [3, 2, 1]
round(value, precision=0) Rounds a number to the specified precision. 3.14159, 2 => 3.14
slice(sequence, start=0, end=null) Returns a slice of a sequence. [1, 2, 3, 4], 1, 3 => [2, 3]
sort(sequence, reverse=false) Sorts a sequence. [3, 1, 2], False => [1, 2, 3]
stringify(value) Converts an object to a string (similar to str). 123 => "123"
striptags(value) Strips HTML tags from a string. "<p>Hello</p>" => "Hello"
sum(sequence) Returns the sum of all items in a sequence. [1, 2, 3, 4] => 10
title(string) Converts a string to title case (capitalizing each word). "hello world" => "Hello World"
trim(string) Removes leading and trailing spaces from a string. " hello " => "hello"
unique(sequence) Removes duplicate values from a sequence. [1, 2, 2, 3] => [1, 2, 3]
upper(string) Converts a string to uppercase. "hello" => "HELLO"
urlencode(value) URL-encodes a string or dictionary. "Hello World!" => "Hello%20World%21"
urlize(string) Converts URLs in a string into anchor links. "Visit http://example.com" => "Visit <a href="http://example.com">http://example.com</a>"
wordcount(string) Returns the word count of a string. "Hello world" => 2
now() Get current UTC timestamp. 1750715557.0
date(format, timestamp) Convert a timestamp to a human-readable format. "%Y-%m-%d %H:%M:%S", 1750715557.0 => 2025--06-23 21:52:37
typeof(value) Return type of value. "hello" => "string"
isnull(value) Check if value is NULL. null => true
isiter(value) Check if value is iterable. [1, 2, 3] => true
wordwrap(text, width, break_long_words=false) Wrap text to a given line width, optionally breaking long words. wordwrap("Hello World!", 5) =>
"Hello\nWorld!"
slugify(text) Turn a string into a URL and filesystemsafe “slug.” slugify("Hello World!") => "hello-world"
truncate(text, length, suffix="...") Truncate to a max length, adding a suffix if truncated. truncate("abcdef", 4) => "abcd..."
indent(text, spaces=4) Indent every line of a multi-line string by the given spaces. indent("a\nb", 2) =>
" a\n b"
wrap(text, prefix, suffix) Surround a string with prefix/suffix. wrap("name","<b>","</b>") => "<b>name</b>"
pow(base, exp) Exponentiation. pow(2, 3) => 8
sqrt(x) Square root. sqrt(9) => 3
log(n, base=e) Logarithm with configurable base. log(8, 2) => 3
clamp(value, min, max) Clamp a number into a given range. clamp(10, 0, 5) => 5
factorial(n) Factorial of an integer. factorial(5) => 120
gcd(a, b) Greatest common divisor. gcd(8, 12) => 4
localtime(offset) Convert a UTC timestamp to local time (offset in minutes). localtime(120) => "2025-06-24T16:00:00+02:00"
urljoin(base, path) Safely join URL segments. urljoin("https://ex.com/a","b/c") => "https://ex.com/a/b/c"
pathjoin(*parts) Join filesystem path segments. pathjoin("usr","local","bin") => "usr/local/bin"
basename(path) Return filename portion of a path. basename("/tmp/file.txt") => "file.txt"
dirname(path) Return directory portion of a path. dirname("/tmp/file.txt") => "/tmp"
queryencode(params) Turn a dict into a URL query string. queryencode({"a":1,"b":2}) => "a=1&b=2"
all(seq) Return true if all elements are truthy. all([1,2,0]) => false
any(seq) Return true if any element is truthy. any([0,0,3]) => true
zip(*seqs) Iterate over multiple sequences in parallel. zip([1,2],[3,4]) => pairs (1,3),(2,4)
enumerate(seq, start=0) Pair each element with its index. enumerate(["a","b"],1) => (1,"a"),(2,"b")
blake2s(data, key=None) Compute a BLAKE2s hash. blake2s("msg") => "abc.."
csrf_token() Generate or retrieve a per-session CSRF token. csrf_token() => "abc..."
l10n_get(key) Lookup a localized string by key. l10n_get("welcome") => "Sveiki"
pluralize(count, singular, plural=null) Choose correct plural form based on count. pluralize(2,"item") => "items"
debug(message, level="info") Emit a log (debug) message from a template. debug("User saved", level="debug")
abort(message) Halt rendering and raise an error with a custom message. abort("Invalid input") => error
block_super() Within an overridden block, render the parent block's content. {% block content %}New{{ block_super() }}Old{% endblock %}
url_for(name) Get an absolute URL to a given resource. "index" => "/"

Additional C functions can be exposed as built-ins. For example, one can write and register a C function read_filesystem to be called as read_filesystem(path) in templates.

Any function can also accept keyword and positional arguments if it is designed to do so.

30. Expression Syntax Summary

Expressions support the following patterns:

  • Parentheses: Use ( expr ) to group or control evaluation order.
  • Variable: identifier
  • Lookup: arr[expr] or obj.name.
  • Function Call: f(expr1, expr2, ...).
  • Ternary: cond ? exprTrue : exprFalse.
  • Ranges: After any expression, a colon may turn it into a range (handled in precedence).

Examples:

{{ (a + b) * c }}
{{ user.address.street }}
{{ config["timeout"] }}
{{ func(x, y, z) }}
{{ condition ? "yes" : "no" }}

The parser evaluates based on operator precedence as described earlier. For example, in 1 + 2 * 3, multiplication is done before addition.

31. Value vs Reference Types

Temple distinguishes value types from reference types. Value types (numbers, infinity, booleans, null, strings) behave like copies: assigning them or passing them copies the value. Reference types (arrays, hashmaps, objects from C) are pointers to shared data. Assigning or passing a reference type creates an alias to the same underlying data. Mutating an array or object through one reference will be visible through all references. This is important for understanding side effects.

For instance:

{% set arr = [1,2,3] %}
{% set arr2 = arr %}
{% set arr2[0] = 99 %}
{{ arr[0] }}

This prints 99 because arr2 and arr refer to the same array.

32. Whitespace Control and Comments

  • Whitespace Trimming: By default, Temple preserves normal whitespace. Use {%- and -%} on block delimiters to trim whitespace around that tag. For example, {%- for item : items -%} will remove the whitespace immediately before and after the tag. As noted, if a block is opened with {%- block %} or another trimmed tag, it must be closed with {%- end -%}.
  • Comments: {# comment text #} denotes a comment. Comments are ignored in output, but can be used to insert engine-specific metadata in the AST.

Sections and text outside any {% %} or {{ }} are passed through verbatim into the output. For clean HTML output, use whitespace trimming judiciously to avoid unwanted newlines.

33. Namespacing

To create a namespace, you can simply use the {% namespace %} block statement:

{% namespace test %}
    {% macro greet(name) %}
        Hello, {{ title(name) }}!
    {% and %}
{% end %}

To call the greet macro now, you would do:

{{ test.greet("Arija") }}

Implicit namespacing is also allowed in imports:

{% import "base.lpt" : base %}

Namespaces cannot nest, and their names are identifiers, not expressions. Namespaces cannot be redefined.

34. Lookup order

Lookups of variables, macros, members, etc. must be done in this order:

  1. Literals and Keywords
  2. Namespace (e.g., hello.world, hello could be a namespace)
  3. Functions (e.g., func() is first validated to be a function call, not a macro)
  4. Macros (e.g., hello is fist looked up as a macro, not a variable)
  5. Global variables/constants
  6. Local variables/constants

35. Authors

  • Arija A. <ari@ari.lt> - Author of the Temple templating language.

This format is licensed under AGPL-3.0-only and is part of the Vessel project which you can find at https://git.ari.lt/ari/vessel.

More information about the license can be found at https://git.ari.lt/ari/vessel/raw/branch/main/LICENSE.

36. Appendix: Table of All Literals

Type Syntax Example Description
String "hello", r"raw\n", f"hi {x}" UTF-8 text. r disables escapes; f enables interpolation.
Integer 42, 0x2a, 0o52, 0b101010 64-bit signed. Supports decimal, hex, octal, binary.
Float 3.14, 6.02e23 IEEE-754 double precision.
Boolean true, false Logical values.
Null null Absence of value.
Array [1, "two", false] Ordered, zero-indexed, heterogeneous elements.
Range from:to/step, from: Inclusive ranges. All from, to, and step are optional.
Hashmap {a: b} Key-value store, accessed via lookups (e.g., config["key"]).
Object N/A (no literals) Structured data from C, accessed via member lookup (e.g., user.name).