1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# Lupa
## Introduction
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][].
[Jinja2]: http://jinja.pocoo.org
[Library of the University of Antwerp]: http://www.uantwerpen.be/
## Requirements
Lupa has the following requirements:
* [Lua][] 5.1, 5.2, or 5.3.
* The [LPeg][] library.
[Lua]: http://www.lua.org
[LPeg]: http://www.inf.puc-rio.br/~roberto/lpeg/
## Download
Download Lupa from the project’s [download page][].
[download page]: download
## Installation
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.
* Lua 5.1: */usr/local/share/lua/5.1/* or */usr/local/share/lua/5.1/*
* Lua 5.2: */usr/local/share/lua/5.2/* or */usr/local/share/lua/5.2/*
* Lua 5.3: */usr/local/share/lua/5.3/* or */usr/local/share/lua/5.3/*
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'
## Usage
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.
[API documentation]: api.html
## Syntax
Please refer to Jinja2's extensive [template documentation][]. Any
incompatibilities are listed in the sections below.
[template documentation]: http://jinja.pocoo.org/docs/dev/templates/
## Comparison with Jinja2
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.
### Fundamental Differences
* 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.
### Syntactic Differences
* Line statements are not supported due to parsing complexity.
* In `{% for ... %}` loops, the `loop.length`, `loop.revindex`,
`loop.revindex0`, and `loop.last` variables only apply to sequences, where
Lua's `'#'` operator applies.
* The `{% continue %}` and `{% break %}` loop controls are not supported due to
complexity.
* Loops may be used recursively by default, so the `recursive` loop modifier is
not supported.
* The `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) }}`).
* Filters cannot occur after tokens within an expression (e.g.
`{{ "foo"|upper .. "bar"|upper }}`), but can only occur at the end of an
expression (e.g. `{{ "foo".."bar"|upper }}`).
* Blocks always have access to scoped variables, so the `scoped` block modifier
is not supported.
* Named block end tags are not supported since the parser cannot easily keep
track of that state information.
* Any `{% 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.
* Inline "if" expressions (e.g. `{% extends b if a else c %}`) are not
supported. Instead, use a Lua conditional expression
(e.g. `{% extends a and b or c %}`).
* Any `{% 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 %}`).
* Macros are simply Lua functions and have no metadata attributes.
* Macros do not have access to a `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.
* The `{% 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.
### Filter Differences
* Only the `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.
* The `float` filter only works in Lua 5.3 since that version of Lua has a
distinction between floats and integers.
* The `safe` filter must appear at the end of a filter chain since its output
cannot be passed to any other filter.
### Function Differences
* The global `range(n)` function returns a sequence from 1 to `n`, inclusive,
since lists start at 1 in Lua.
* No `lipsum()`, `dict()`, or `joiner()` functions for the sake of simplicity.
### API Differences
* 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).
|