Skip to content

gilzoide/molde

Repository files navigation

Molde

Build Status

Zero dependency, single file template engine for Lua 5.1+ with builtin sandbox support.

It compiles a template string to a function that generates the final string by substituting values by the ones in a sandboxed environment.

Templates

There are 3 constructs templates recognize:

  • Literals: Content that will be copied unmodified to the final string. Read the note on long strings
  • Value: A value processed by Lua and appended to the final string, stringified by tostring
  • Statement: A Lua code block to be copied unmodified to the generated code, used for variable assignments, repetitions, conditions, etc. It doesn't directly generate contents for the final string

Values are delimited by matching {{ and }}, statements by {% and %}, and everything else is considered literal. Delimiter characters {, } and % can be escaped using a leading backslash. If you want literal }} or %} in your template, they must be escaped, or molde will return error.

Example:

NOTE: This is not a valid molde template for educational purposes.

By default, everything is copied unmodified to the final string.

Values are just Lua expressions:
- Hello {{ "world" }}
  "Hello world"
- {{ 5 + 3 * 4 }}
  "17"
- {{ nil or "default" }}
  "default"
- You are using {{ _VERSION }}
  "You are using Lua 5.3" (You may use Lua 5.1 and 5.2 as well)
- Line 1{{ "\n" }}Line 2
  "Line 1
  Line 2"
- Escaping \{{ Hi! \}} (Note that you MUST escape the closing '}}')
  "Escaping {{ Hi! }}"
- Escaping is characterwise, so \{{ is as valid as {\{
  "Escaping is characterwise, so {{ is as valid as {{"
- table.insert is used in values {{ so they must be a valid expression! }}
  Error: ')' expected near 'they'

Statements are Lua statements:
- {% for i = 1, 5 do %}{{ i }} {% end %}
  "1 2 3 4 5 "
- {% -- this is just a comment, y'know %}
  ""
- {{ unbound_variable }}{% unbound_variable = "Hi!" %} {{ unbound_variable }}
  "nil Hi!"
- {% if false then %}This will never be printed{% else %}Conditionals!{% end %}
  "Conditionals!"
- \{% Escaping works \%} {\% here as well %\} (You MUST escape closing '%}' too)
  "{% Escaping works %} {% here as well %}"
- {% if without_then %}Statements must form valid Lua code!{% end %}
  Error: 'then' expected near 'table'

Note on long strings

The lua reference manual says:

For convenience, when the opening long bracket is immediately
followed by a newline, the newline is not included in the string.

The code generated by molde to insert literals uses long strings, so newlines that come immediately after a closing value or statement will not be considered in the final string.

Installing

Using LuaRocks:

# luarocks install molde

Or you may copy the only source file molde.lua to your Lua path

Using

local molde = require 'molde'

-- molde.load and molde.loadfile return a function that receives a table
-- with the values to substitute, and the optional environment (default: _G)
hello_template = molde.load([[Hello {{ name or "world" }}]])
print(hello_template()) -- "Hello world"
print(hello_template{name = "gilzoide"}) -- "Hello gilzoide"
name = "gilzoide"
print(hello_template({}, _ENV or getfenv())) -- "Hello gilzoide"

-- load the template from a file (same template)
hello_template = molde.loadfile("hello_template")
name = nil
print(hello_template()) -- "Hello world"

Testing

Run automated tests using busted:

$ busted

Documentation

The API is documented using LDoc and is available at github pages.

To generate:

$ ldoc . -d docs

Change log

  • 2.0.0 - Removed dependency on LPegLabel in favor of a pure streaming parser, added molde.tokenize, changed molde.parse function to be an iterator instead of returning table, move doc comments to source file, changed string_bracket_level to be a function argument instead of module-wide configuration, change molde.load to return nil + error instead of raising.
  • 1.0.1 - Fix error handling for matching on LpegLabel v1.5
  • 1.0.0 - Updated to use LpegLabel version 1.5+
  • 0.1.6 - Support for Lua 5.1