==================================
Twig Template Engine Specification
==================================
This specification specifies a simple cross-language template engine for at least
PHP, Python and Ruby.
Purpose
=======
A language independent and simple template engine is useful for applications that
use code which is written in more than one programming language. Good Examples
are portal systems which use a blog written in Ruby, a forum software written in
PHP and a planet system written in Python.
Inspiration
===========
Twig uses a syntax similar to the Genshi text templates which in turn were
inspired by django which also inspired Jinja (all three of them python template
engines) which inspired the Twig runtime environment.
Undefined Behavior
==================
To simplify porting the template language to different platforms in a couple of
situations the behavior is undefined. Template authors may never take advantage
of such a situation!
Syntax
======
I'm too lazy to write down the syntax as BNF diagram but the following snippet
should explain the syntax elements::
{# This is a comment #}
{% block title %}Page Title Goes Here{% endblock %}
{% if show_navigation %}
{% endif %}
{% block body %}{% endblock %}
Comments and Whitespace
-----------------------
Everything between ``{#`` and ``#}`` is ignored by the lexer. Inside blocks and
variable sections the Lexer has to remove whitespace too.
Output Expressions
------------------
To output expressions two syntaxes exist. Simple variable output or full
expression output::
$this.is.a.variable.output
${ expression | goes | here }
The former is what we call a variable expression, the second a full expression.
Variable expressions must not contain whitespace, whereas a full expression
must print the output of the full wrapped expression.
Expressions
-----------
Expressions allow basic string manipulation and arithmetic calculations. It is
an infix syntax with the following operators in this precedence:
=========== ==============================================================
Operator Description
=========== ==============================================================
``+`` Convert both arguments into a number and add them up.
``-`` Convert both arguments into a number and substract them.
``*`` Convert both arguments into a number and multiply them.
``/`` Convert both arguments into a number and divide them.
``%`` Convert both arguments into a number and calculate the rest
of the integer division.
``~`` Convert both arguments into a string and concatenate them.
``or`` True if the left or the right expression is true.
``and`` True if the left and the right expression is true.
``not`` negate the expression
=========== ==============================================================
All number conversions have an undefined precision but the implementations
should try to select the best possible type. For example, if the implementation
sees an integer and a float that looks like an integer it may convert the
latter into a long and add them.
Use parentheses to group expressions.
If an object cannot be compared the implementation might raise an error or fail
silently. Template authors may never apply mathematical operators to untrusted
data. This is especially true for the php implementation where the following
outputs ``42``::
${ "foo41" + 1 }
This is undefined behavior and will break on different implementations or
return ``0`` as ``"foo41"`` is not a valid number.
Types
~~~~~
The following types exist:
=========== =============== ==============================================
Type Literal Description
=========== =============== ==============================================
``integer`` `\d+` One of the two numeric types. Which of them
is used and when is up to the implementation.
``float`` `\d+\.\d+` Floating point values.
``string`` see below A unicode string. The PHP implementation has
to use bytestrings here and may use mb_string.
``bool`` `(true|false)` Represents boolean values.
``none`` `none` This type is returned on missing variables or
attributes.
=========== =============== ==============================================
String regex::
(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')(?sm)
Attribute Lookup
~~~~~~~~~~~~~~~~
There are two ways to look up attributes on objects. The dot and the
subscript syntax, both inspired by JavaScript. Basically the following
expressions do the very same::
foo.name.0
foo['name'][0]
This is useful to dynamically get attributes from objects::
foo[bar]
The underlaying implementation is free to specify on it's own what an attribute
lookup means. The PHP reference implementation for example performs these
actions on ``foo.bar``:
- try ``$foo['bar']``
- try ``$foo->bar()`` (if they're using ``foo.bar`` and not ``foo[bar]``)
- try ``$foo->bar``
- try ``$foo->getBar()`` (if they're using ``foo.bar`` and not ``foo[bar]``)
The first match returns the object, attribute access to nonexisting attributes
returns `none`.
Filtering
~~~~~~~~~
The template language does not specify function calls, but filters can be used
to further modify variables using functions the template engine provides.
The following snippet shows how filters are translated to function calls::
${ 42 | foo(1, 2) | bar | baz }
-> baz(bar(foo(42, 1, 2)))
The following filters must be provided by the implementation:
=================== ======================================================
Name Description
=================== ======================================================
`date` Format the timestamp using the PHP date formatting
rules. This may sound like a nonstandard way of
formatting dates but it's a way very popular among
template designers and also used by django.
`strftime` Format the timestamp using standard strftime rules.
`numberformat` Apply number formatting on the string. This may or
may not use local specific rules.
`moneyformat` Like `numberformat` but for money.
`filesizeformat` Takes a number of bytes and displays it as KB/MB/GB
`format` Applies `sprintf` formatting on the string::
${ "%s %2f" | format(string, float) }
`even` Is the number even?
`odd` Is the number odd?
`escape` Apply HTML escaping on a string. This also has to
convert `"` to `" but leave `'` unmodified.
`e` Alias for `escape`.
`urlencode` URL encode the string. If the second parameter is
true this function should encode for path sections,
otherwise for query strings.
`quotes` Escape quotes (\', \", etc.)
`title` Make the string lowercase and upper case the first
characters of all words.
`capitalize` Like `title` but capitalizes only the first char of
the whole string.
`upper` Convert the string to uppercase.
`lower` Convert the string to lowercase.
`strip` Trim leading and trailing whitespace.
`lstrip` Trim leading whitespace.
`rstrip` Trim trailing whitespace.
`translate` Translate the string using either the "theme" domain
or the "chyrp" domain if in Admin. (Chyrp-specific)
`translate_plural` Translate the (singular) string, or the plural string
if the number passed is not 1.
`normalize` Convert all excessive whitespace (including linebreaks)
into a single space.
`truncate` Truncate a string, providing ellipsis, if it is longer
than the passed length. Keeps words in tact by default,
but with a second boolean parameter will be strict.
`replace` Replaces the occurrence of the first argument with the
second argument in the string.
`linebreaks` Convert linebreaks to 's.
`camelize` Convert string to camelcase.
`strip_tags` Strip HTML from the string.
`pluralize` Return the pluralization of a string, or if a number
is passed and it is 1, don't pluralize.
`sanitize` Remove special characters from a string.
`join` Concatenate the array items and join them with the
string provided (or commas by default).
`split` Split a string into an array at the given breakpoints.
`first` First entry of an Array.
`offset` Entry at Array[offset].
`last` Last entry of an Array.
`reverse` Reverse the Array items.
`count` Count the number of items in an array or string
characters.
`length` Alias for `count`.
`default` If the value is `none` the first argument is returned
`keys` Keys of an Array.
`items` Items of an Array.
`inspect` Dumps the variable or value.
`fallback` If the value is empty or `none`, return this value.
`selected` If the first argument is the same as the value, output
`class="selected"`, or `selected` if the second
argument is `true`.
`option_selected` Same as `selected`, but for `selected="selected"`.
`checked` Same as `selected`, but for `checked="checked"`.
=================== ======================================================
Additionally, if a filter is missing (say, ${ foo | bar_filter }, in Chyrp it
checks for an associated Trigger filter by that filter's name.
For Loops
---------
Iteration works via for loops. Loops work a bit like their Python counterparts,
except that they don't support multilevel tuple unpacking and that they add a new
layer to the context. Thus at the end of the iteration all the modifications on
the context disappear. Additionally, inside loops you have access to a special
`loop` object which provides runtime information:
====================== ===================================================
Variable Description
====================== ===================================================
``loop.index`` The current iteration of the loop (1-indexed)
``loop.index0`` The current iteration of the loop (0-indexed)
``loop.revindex`` The number of iterations from the end of the
loop (1-indexed)
``loop.revindex0`` The number of iterations from the end of the
loop (0-indexed)
``loop.first`` True if this is the first time through the loop
``loop.last`` True if this is the last time through the loop
``loop.parent`` For nested loops, this is the loop "above" the
current one
====================== ===================================================
Additionally for loops can have an `else` section that is executed if no
iteration took place.
Example
~~~~~~~
::
Notes on Iteration
~~~~~~~~~~~~~~~~~~
Because we have to cope with PHP too, which has problematic arrays that are
neither hashmaps nor lists, we have no support for associative array iteration
at all. How do you iterate over associative arrays then? Using a filter::
{% for key, value in array | items %}
...
{% endfor %}
To iterate over the keys only::
{% for key in array | keys %}
...
{% endfor %}
If Conditions
-------------
If conditions work like like Ruby, PHP and Python, just that we use PHP
keywords. Also, use `elseif` and not `else if`::
{% if expr1 %}
...
{% elseif expr2 %}
...
{% else %}
...
{% endif %}
Inheritance
-----------
Template inheritance allows you to build a base "skeleton" template that
contains all the common elements of your site and defines **blocks** that
child templates can override.
Here a small template inheritance example::
{% block title %}My site{% endblock %}
If we call that template "base.html" a "index.html" template could override
it and fill in the blocks::
{% extends "base.html" %}
{% block title %}Foo — {% super %}{% endblock %}
{% block content %}
This is the content
{% endblock %}
By using `{% super %}` you can render the parent's block. The template
filenames must be constant strings (we don't support dynamic inheritance
for simplicity) and are relative to the loader folder, not the current
template.