Awesome
liluat
Liluat is a lightweight Lua based template engine. While simple to use it's still powerfull because you can embed arbitrary Lua code in templates. It doesn't need external dependencies other than Lua itself.
Liluat is a fork of version 1.0 of slt2 by henix. Although the core concept was taken from slt2, the code has been almost completely rewritten.
Table of contents
- OS support
- Lua support
- Installation
- Basic Syntax
- Example
- API
- Trimming
- Options
- Command line utility
- Sandboxing
- Caveats
- License
- Contributing
OS support
Liluat is developed on GNU/Linux and automatically tested on GNU/Linux and Mac OS X. I have much confidence that it will also work on FreeBSD, other BSDs and on other POSIX compatible systems like e.g. Cygwin.
Windows was not tested, but it might work with some limitations:
- absolute paths in template includes won't be properly detected because they don't start with a
/
\
is not supported as path separator- template files with Windows style line endings (
"\r\n"
) aren't supported - the unit tests for the command line won't work because they rely on a POSIX shell being available
Lua support
Liluat is automatically tested on the following Lua implementations:
- Lua 5.1
- Lua 5.2
- Lua 5.3
- LuaJIT 2.0
- LuaJIT 2.1 (beta)
Installation
Lua is available via luarocks, the following command installs it via luarocks:
# luarocks install liluat
You might need to add --local
if you don't have admin (root) privileges.
Alternatively just copy the file liluat.lua
to your software (this won't install the command line interface though).
Basic syntax
There are three different types of template blocks:
Code
You can write arbitrary Lua code in the form:
{{ some code }}
This allows for writing simple loops and conditions or even more complex logic.
Expressions
You can write arbitrary Lua expression that can be converted into a string like this:
{{= expression}}
Includes
You can include other template files like this:
{{include: 'templates/file_name'}}
By default the include path is either relative to the directory where the template that does the include is in or it is an absolute path starting with a /
, e.g. '/tmp/template-dfjCm'
. You can change this behavior using the base_path
option, see Options.
Liluat is able to detect cyclic inclusion in most cases (eg. not if you used symlinks to create a cycle in the filesystem).
More
There is more to the syntax of liluat, but that will be explained later on in the section Trimming.
Example
Some basic template in action.
See example.lua
:
local liluat = require("liluat")
local template = [[
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{= title}}</title>
</head>
<body>
<h1>Vegetables</h1>
<ul>
{{ -- write regular lua code in the template}}
{{for _,vegetable in ipairs(vegetables) do}}
<li><b>{{= vegetable}}</b></li>
{{end}}
</ul>
</body>
</html>
]]
-- values to render the template with
local values = {
title = "A fine selection of vegetables.",
vegetables = {
"carrot",
"cucumber",
"broccoli",
"tomato"
}
}
-- compile the template into lua code
local compiled_template = liluat.compile(template)
local rendered_template = liluat.render(compiled_template, values)
io.write(rendered_template)
Output:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A fine selection of vegetables.</title>
</head>
<body>
<h1>Vegetables</h1>
<ul>
<li><b>carrot</b></li>
<li><b>cucumber</b></li>
<li><b>broccoli</b></li>
<li><b>tomato</b></li>
</ul>
</body>
</html>
API
liluat.compile(template, [options], [template_name], [start_path])
Compile the template into Lua code that can later be rendered. Returns a compiled template.
template
: The template to compileoptions
: A table containing different configuration options, see the Options section.template_name
: A name to identify the template with. This is especially useful to be able to find out where a Lua syntax or runtime error is coming from.start_path
: Path to start in as a working directory. If thebase_path
option is not set, this is the path to which the first inclusion is relative to.
liluat.compile_file(filename, [options])
Same as liluat.compile
but loads the template from a file. template_name
is set to the filename and start_path
is set to the path where the file is in. Returns a compiled template.
filename
: File to load the template from.options
: A table containing different configuration options, see the Options section.
liluat.render(compiled_template, [values], [options])
Render a compiled template into a string, using the given values. This runs the compiled template in a sandbox with values
added to it's environment.
compiled_template
: This is the output ofliluat.compile
. Essentially Lua code with some meta data.values
: A Lua table containing any kind of values. This can even be functions or custom data types from C. These values are accessible inside the template.options
: A table containing different configuration options, see the Options section. NOTE: Most of those options only change the behavior ofliluat.compile
.
liluat.render_coroutine(compiled_template, [values], [options])
Same as liluat.render
but returns a function that can be run in a coroutine and will return one chunk of data at a time (so you can kind of "stream" the template rendering).
liluat.inline(template, [options], [start_path])
Load a template and return a template string where all the included templates have been inlined.
template
: A template string to be inlined.options
: A table containing different configuration options, see the Options section.start_path
: Path to start in as a working directory. If thebase_path
option is not set, this is the path to which the first inclusion is relative to.
liluat.get_dependencies(template, [options], [start_path])
Get a table containing all of the files that a template includes (also recursively).
template
: The template to examine.options
: A table containing different configuration options, see the [Options] section.start_path
: Path to start in as a working directory. If thebase_path
option is not set, this is the path to which the first inclusion is relative to.
Trimming
An important feature not yet talked about is trimming. In order to be able to write templates that look nice and readable while still keeping the output nice, some kinds of whitespaces need to be trimmed in some cases.
There are two kinds of trimming that liluat supports:
Left trimming
In case a line contains only whitespaces in front of a template block, those are removed when left trimming is enabled.
Right trimming
Right trimming, if enabled, removes newline characters directly following a template block.
Settings
The trimming can be globally enabled and disabled via the trim_left
and trim_right
options. Possible values are:
"all"
: trim all template blocks"expression"
: trim only expression blocks"code"
: trim only code blocks, this is the defaultfalse
: disable trimming
Include blocks are not trimmed.
Local override
You can locally override left and right trimming via +
and -
, where +
means, no trimming, and -
means trimming. For example, the block {{+ code -}}
will be trimmed right, but not left, no matter what the global trimming settings are.
Example
In this example, trim_left
and trim_right
are set to "code"
, which is the default.
{{for i = 1, 4 do}}
{{= i}}
{{end}}
{{for i = 5, 8 do}}
{{-= i-}}
{{end}}
{{for i = 9, 12 do+}}
{{-= i}}
{{end}}
Output:
1
2
3
4
5678
9
10
11
12
Options
The following options can be passed via the options
table:
start_tag
: Start tag to be used instead of{{
end_tag
: End tag to be used instead of}}
trim_right
: one of"all"
,"code"
,"expression"
orfalse
to disable. Default is"code"
. See the section Trimming for more information.trim_left
: one of"all"
,"code"
,"expression"
orfalse
to disable. Default is"code"
. See the section Trimming for more information.base_path
: Path that is used as base path for includes. Ifnil
orfalse
, all include paths are interpreted relative to the files path itself. Not that this doesn't influence absolute paths.reference
: If set totrue
,liluat.render
will reference the environment in the sandbox instead of recursively copyiing it. This reduces part of the security of the sandbox, because values can now leak out of it. However, this option is useful if you pass in environments that use a lot of memory or contain reference cycles, see Caveats/Environment is copied.
Command line utility
Liluat comes with a command line interface:
$ runliluat --help
Usage: runliluat [options]
Options:
-h|--help
Show this message.
--values lua_table
Table containing values to use in the template.
--value-file file_name
Use a file to define the table of values to use in the template.
-t|--template-file file_name
File that contains the template
-n|--name template_name
Name to use for the template
-d|--dependencies
List the dependencies of the template (list of included files)
-i|--inline
Inline all the included files into one template.
-o|--output filename
File to write the output to (defaults to stdout)
--options lua_table
A table of options for liluat
--options-file file_name
Read the options from a file.
--stdin "template"
Get the template from stdin.
--stdin "values"
Get the table of values from stdin.
--stdin "options"
Get the options from stdin.
-v|--version
Print the current version.
--path path
Root path of the templates.
Sandboxing
All the code in the templates is run in a sandbox. To achieve this, the code is run with its own global environment, Lua bytecode is forbidden and only a subset of Lua's standard library functions is allowed via a whitelist. If you require additional standard library functions, you have to pass them in manually via the values
parameter.
The whitelist currently contains the following:
ipairs
next
pairs
rawequal
rawget
rawset
select
tonumber
tostring
type
unpack
string
table
math
os.date
os.difftime
os.time
coroutine
Caveats
This section documents known issues that can arise in certain usage scenarios.
Environment is copied
Due to the sandboxing, the entire environment passed into liluat.render
or liluat.render_coroutine
is recursively copied. This can have the following consequences (and probably more):
- High memory usage if the environment uses a large amount of memory. Because a copy is created, liluat needs the same amount once again for the copy. This can get even worse when you render multiple templates with a big environment, because Lua's incremental garbage collector might not be fast enough to clean it up right away.
- Environments that contain reference cycles will trigger an infinite loop that results in a stack overflow.
- All metatables are removed from the values in the sandbox. This also means that most object oriented modules will break if you add them to the environment.
All those above issues can be fixed by setting the reference
option to true
, see Options. Note though that this will decrease the security of the sandbox, because changes to the environment that happen in the sandbox will leave the sandbox.
License
Liluat is free software licensed under the MIT license:
liluat - Lightweight Lua Template engine
Project page: https://github.com/FSMaxB/liluat
liluat is based on slt2 by henix, see https://github.com/henix/slt2
Copyright © 2016 Max Bruckner Copyright © 2011-2016 henix
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Contributing
If you find a bug or have a suggestion on what could be improved, write an issue on GitHub or write me an email.
I will also gladly accept pull requests via GitHub or email if I think that it will benefit the library. Be sure to talk to me first to increase your success rate and prevent possible frustration/misunderstandings.
Coding style
- use tabs for indentation
- don't leave trailing spaces
Other than that: Take a look at what's already there and try to adapt.
Unit tests
Write unit tests for everything you do. I'm using the busted unit testing framework. Every commit needs to pass the tests on every supported Lua implementation. Note that pull requests get automatically tested on Travis-CI.