Lupa is a Jinja2 template engine implementation written in Lua and supports Lua syntax within tags and variables.
Lupa was sponsored by the Library of the University of Antwerp.
Lupa has the following requirements:
Download Lupa from the project’s download page.
Unzip Lupa and place the “lupa.lua” file in your Lua installation’s
package.path
. This location depends on your version of Lua. Typical locations
are listed below.
You can also place the “lupa.lua” file wherever you’d like and add it to Lua’s
package.path
manually in your program. For example, if Lupa was placed in a
/home/user/lua/ directory, it can be used as follows:
package.path = package.path..';/home/user/lua/?.lua'
Lupa is simply a Lua library. Its lupa.expand()
and lupa.expand_file()
functions may called to process templates. For example:
lupa = require('lupa')
lupa.expand("hello {{ s }}!", {s = "world"}) --> "hello world!"
lupa.expand("{% for i in {1, 2, 3} %}{{ i }}{% endfor %}") --> 123
By default, Lupa loads templates relative to the current working directory. This can be changed by reconfiguring Lupa:
lupa.expand_file('name') --> expands template "./name"
lupa.configure{loader = lupa.loaders.filesystem('path/to/templates')}
lupa.expand_file('name') --> expands template "path/to/templates/name"
See Lupa’s API documentation for more information.
Please refer to Jinja2’s extensive template documentation. Any incompatibilities are listed in the sections below.
While Lua and Python (Jinja2’s implementation language) share some similarities, the languages themselves are fundamentally different. Nevertheless, a significant effort was made to support a vast majority of Jinja2’s Python-style syntax. As a result, Lupa passes Jinja2’s test suite with only a handful of modifications. The comprehensive list of differences between Lupa and Jinja2 is described in the following sections.
Expressions use Lua’s syntax instead of Python’s, so many of Python’s syntactic constructs are not valid. However, the following constructs are valid, despite being invalid in pure Lua:
Iterating over table literals or table variables directly in a “for” loop:
{% for i in {1, 2, 3} %}…{% endfor %}
Conditional loops via an “if” expression suffix:
{% for x in range(10) if is_odd(x) %}…{% endfor %}
Table unpacking for list elements when iterating through a list of lists:
{% for a, b, c in {{1, 2, 3}, {4, 5, 6}} %}…{% endfor %}
Default values for macro arguments:
{% macro m(a, b, c=‘c’, d=’d’) %}…{% endmacro %}
Strings do not have unicode escapes nor is unicode interpreted in any way.
{% for ... %}
loops, the loop.length
, loop.revindex
,
loop.revindex0
, and loop.last
variables only apply to sequences, where
Lua’s '#'
operator applies.{% continue %}
and {% break %}
loop controls are not supported due to
complexity.recursive
loop modifier is
not supported.is
operator is not supported by Lua, so tests of the form {{ x is y }}
should be written {{ is_y(x) }}
(e.g. {{ is_number(42) }}
).{{ "foo"|upper .. "bar"|upper }}
), but can only occur at the end of an
expression (e.g. {{ "foo".."bar"|upper }}
).scoped
block modifier
is not supported.{% block ... %}
tags within a “false” block (e.g. {% if a %}
where a
evaluates to false
) are never read and stored due to the parser
implementation.{% extends b if a else c %}
) are not
supported. Instead, use a Lua conditional expression
(e.g. {% extends a and b or c %}
).{% extends ... %}
tags within a sub-scope are not effective outside that
scope (e.g. {% if a %}{% extends a %}{% else %}{% extends b %}{% endif %}
).
Instead, use a Lua conditional expression (e.g. {% extends a or b %}
).kwargs
variable since Lua does not support
keyword arguments.{% from x import y %}
tags are not supported. Instead, you must use either
{% import x %}
, which imports all globals in x
into the current
environment, or use {% import x as z %}
, which imports all globals in x
into the variable z
.{% set ... %}
does not support multiple assignment. Use {% do ...%}
instead. The catch is that {% do ... %}
does not support filters.{% trans %}
and {% endtrans %}
tags, {% with %}
and {% endwith %}
tags, and {% autoescape %}
and {% endautoescape %}
tags are not supported
since they are outside the scope of this implementation.batch
, groupby
, and slice
filters return generators which
produce one item at a time when looping. All other filters that produce
iterable results generate all items at once.float
filter only works in Lua 5.3 since that version of Lua has a
distinction between floats and integers.safe
filter must appear at the end of a filter chain since its output
cannot be passed to any other filter.range(n)
function returns a sequence from 1 to n
, inclusive,
since lists start at 1 in Lua.lipsum()
, dict()
, or joiner()
functions for the sake of simplicity.Lupa has a much simpler API consisting of just four functions and three fields:
lupa.expand()
: Expands a string template subject to an environment.lupa.expand_file()
: Expands a file template subject to an environment.lupa.configure()
Configures delimiters and template options.lupa.reset()
: Resets delimiters and options to their defaults.lupa.env
: The default environment for templates.lupa.filters
: The set of available filters (escape
, join
, etc.).lupa.tests
: The set of available tests (is_odd
, is_defined
, etc.).There is no bytecode caching.
Lupa has no extension mechanism. Instead, modify lupa.env
, lupa.filters
,
and lupa.tests
directly. However, the parser cannot be extended.
Sandboxing is not supported, although lupa.env
is safe by default (io
,
os.execute
, os.remove
, etc. are not available).