Template Syntax

A trimmer template is a simple text file, similarly to many template engines out there. There are few kinds of delimiters supported in template:

  • {{ a.b }} prints expression (usually just variable substitution) to the template output
  • ## something statements, they usually they last to the end of line


Simple expression substitutes a variable passed to template context of a renderer:

{{ var }}

You can use attribute access via dot a.b and item access via brackets a["b"]. It depends on the type of variable (i.e. the implementor of Variable trait) whether this means the same or a different thing. For most types these kind of accesses are equivalent.

There are also whitespace control flags on an expression. If there is a dash - char on either end of the expression, then all of the adjacent whitespace is stripped, for example, the following examples will render the same:

{{ x }}{{ y }}
{{ x -}}     {{ y }}
{{ x }}   {{- y }}
{{ x -}}    {{- y }}

This works even if expressions are divided by newlines, comments, loops and conditional staments.

Another mode of whitespace control is using plus + character instead, this strips all the whitespace and puts exactly one space between variables. These examples are all equivalent:

{{ x }} {{ y }}
{{ x +}}{{+ y }}
{{ x +}}    {{ y }}
{{ x }}    {{+ y }}

In case of conflict (i.e. one operator on the left and other on the right):

  • Plus + operator overrides no operator at all
  • Dash - operator overrides both plus + and no operator

Additional notes:

  1. The plus operator doesn’t add space at the beginning and the end of the buffer
  2. We don’t strip spaces output by an expression, only template source

The rule (2) in particular means, you can protect spaces using expression, in particular these are equivalent:

{{ x }}   {{ y }}
{{ x +}}    {{ " " }}    {{+ y }}
{{ x -}}   {{ "   " }}   {{- y }}
{{ x -}}   {{ "" }}   {{ "" }}   {{- y }}

(yes even empty string is a delimiter that stops whitespace removal)

As you have already seen you can also put a bare string in expression, this is the easiest way to put expression or statement delimiter into the file:

{{ '{{' }}


Statements start with double-sharp and a keyword. Here are examples:

  • ## syntax: indent
  • ## if x > 0
  • ## for a in array

They must start at the start of the line, not counting the whitespace.

The syntax and validate statements must also be at the start of the file and in the first column of the row.

As a special case empty statement ## at the end of the line strips newline and leading spaces on the new line, effectively joining two lines:

function(  ##
  arg1,    ##

Would output function(arg1, arg2). This is useful if the whole output line doesn’t fit a template line. While you could use {{- '' +}} at the same place, the line joiner ## is simpler and more clear.

Syntax Statement

Syntax statement looks like this:

## syntax: indent

Syntax doesn’t influence parsing the template but the typechecking and the output of the template.

Syntax statement must come the first in the file

Here are the list of supported syntaxes:

## syntax: indent

Means the output is indentation-sensitive, and we strip the additional indentation created by the block statements (## if / ## endif and ## for / ## endfor), so the final output can easily be YAML or Python code.

## syntax: oneline

All subsequent whitespace (including newlines) is condensed and treated as a single space, effectively making template a oneline thing. This syntax is useful for templating log formats and command-lines.

Note: all whitespace printed by expressions is preserved, so you might escape whitespace and newlines using quoted literals ({{ "\n" }}), unless they are rejected by a validator.


Plain (no syntax statement) means the output of the template is rendered as is with all whitespace. Statements always occupy the whole line including indentation whitespace and trailing end of line.

Validate Statement

The validate statement is the core thing for producing valid template output. By default template output is not validated. But if you add the following to the beginning of the file:

## validate default: [a-z]+

The output of any variable can consist only of alphanumeric characters. Validator is a regular expression, the ^ and $ anchors are added automatically.

The default validator is used for every expression that doesn’t override the validator. You can add a validator with any other name to be used in code that possibly extends default syntax, for example:

## validate default: [a-zA-Z0-9]+
## validate quoted: [^']*
echo {{ arg1 }} '{{ arg2 | quoted }}'

Here we generate a shell script. To be careful, we assume that it’s only safe to put alphanumeric characters into the file. But in single-quoted strings its safe to put anything except a quote, so for all variables printed in quotes we can add a quoted validator. See front page for more practical example.

Filter Statement

Similarly to validate statements there is filter statement. This is useful if you produce HTML output and escape (qoute) some characters in it. This works similarly to how filters work in any other template engine, except it requires to declare an alias to validator, at the top of the template:

## filter h: builtin.html_entities
        <h1>{{ title | h }}</h1>

Of course, there is a default filter declared in the template too.

Validators and filters are in the same namespace so you can override filter by a validator:

## filter default: builtin.html_entities
## validate ne: .*
        <h1>{{ title }}</h1>  ### auto-escaped
        {{ body | ne }}       ### a piece of HTML validated in advance

Predefined validators


transforms <>"'`/ into HTML entities. It’s good idea to use it as a filter default: in any HTML template.


This is not a catch-all validator for HTML (like in any other template), you can’t put such variables in tag name, attribute name, script or style tag.

This work the same in amost any other HTML template engine, though. Consult OWASP for more info.


escapes variable value into an argument that’s safe to put onto shell command-line. It’s not safe to put argument in quotes.


## filter arg: builtin.shell_argument
some_cmd {{ value1 | arg }} {{ value2 | arg }}

If the value is a part of the argument you must stop quotes before the arg:

## filter arg: builtin.shell_argument
echo "Here's arg: "{{ value1 | arg }}". It's safe"

The quoting caveats and brittleness of shell-commands are the reason it’s not recommended to use it as a default argument for example:

## validate alnum: [a-z0-9]+
cp source/{{ name | alnum }} target/{{ name | alnum }}

The above, wouldn’t be safe with just shell escaping as one could use parent directory specifiers even if shell expansion would be escaped in name.

If Statement

Conditional statement looks like:

## if something
    output something
## endif

In any case lines containing ## if and ## endif do not put into output. In indent syntax the inner indentation of the block is also stripped.

For Statement

There are two forms of loop statements, for iterating over sequences:

## for var in value
    output something
## endfor

And for iterating over dictionaries:

## for key, value in var
    {{ key }} = {{ value }}
## endfor

In any case lines containing ## for and ## endfor do not put into output. In indent syntax the inner indentation of the block is also stripped.

For loop does not support imperative break and continue statements, but it allows filtering values by using ## skip if:

## for key, value in var
    ## skip if key == "bad_value"
    {{ key }} = {{ value }}
## endfor

This is works just like the following, but allows to keep indentation lower:

## for key, value in var
    ## if key != "bad_value"
        {{ key }} = {{ value }}
    ## endif
## endfor