Skip to content

Instantly share code, notes, and snippets.

@Anaminus
Created December 31, 2013 00:19
Show Gist options
  • Save Anaminus/8190460 to your computer and use it in GitHub Desktop.
Save Anaminus/8190460 to your computer and use it in GitHub Desktop.

Lua Templates

Enables the parsing of basic templates.

Also check out slt2, which is about a million times better!

API

  • Template.Parse ( template, data )

    Parses a template string. template is the template string. data is a table containing the data to apply to the template. Returns the resulting output string.

    A table is also returned. If any template tags throw an error, the error string will be added to this table.

  • Template.ParseFile ( file, data )

    Parses a template from the contents of a file. file is the name of the file to get the template from. data is a table containing the data to apply to the template. Returns the resulting output string.

    A table is also returned. If any template tags throw an error, the error string will be added to this table.

Template Format

When a template is parsed, the parser looks for tags. Tags are portions of the template contained within double-curly brackets ({{ }}).

The character (\) can be used to escape characters with special meaning, such as opening or closing tag brackets, or escape characters.

Other than these, portions of a template are interpreted literally.

Tag Content

The content of a tag is interpreted as a Lua function. The values returned by the function construct the tag's output string. Values are converted to strings, then concatenated together. The exception is tables, which are instead recursed.

Tags have customized environments. Most global variables are removed for safety reasons. The following variables are either added back in, or reimplemented:

  • print ( ... )

    Arguments are converted to strings and appended to the tag's output. No separators are added between arguments. Works as an alternative to returning values. Tables are recursed in the same way.

Values in the data table (the one passed to the Parse function) are also added to the environment, so that they may be accessed directly. This is where the real magic of templates happens. For example, the data table,

{ fruit = "apple" }

along with the tag,

{{return fruit}}

will output the string "apple".

If the tag throws an error, and a default entry is specified in the data table, then the value of that entry will be outputted instead of an empty string.

Examples

template = [[Hello, {{return user}}! How are you?]]
data = { user = "Alice" }
print( Template.Parse(template, data) )
--> "Hello, Alice! How are you?"


template = [[Escape characters with the "\\" character!]]
print( Template.Parse(template) )
--> "Escape characters with the "\" character!"


template = [[Replace content using the \{{tag}} format!]]
print( Template.Parse(template) )
--> "Replace content using the {{tag}} format!"


template = [[Replace content using the {{return "{{tag\}}"}} format!]]
print( Template.Parse(template) )
--> "Replace content using the {{tag}} format!"
--[==[
# Template
Enables the parsing of basic templates.
## API
- `Template.Parse ( template, data )`
Parses a template string. `template` is the template string. `data` is a
table containing the data to apply to the template. Returns the resulting
output string.
A table is also returned. If any template tags throw an error, the error
string will be added to this table.
- `Template.ParseFile ( file, data )`
Parses a template from the contents of a file. `file` is the name of the
file to get the template from. `data` is a table containing the data to
apply to the template. Returns the resulting output string.
A table is also returned. If any template tags throw an error, the error
string will be added to this table.
]==]
local function recurse(t,o)
for i = 1,#t do
if type(t[i]) == 'table' then
recurse(t[i])
else
o[#o+1] = tostring(t[i])
end
end
end
local function getDefault(data)
if not data.default then
return ''
end
if type(data.default) == 'table' then
local o = {}
recurse(data.default,o)
return table.concat(o)
else
return tostring(data.default)
end
end
local function parseTag(str,data,tag)
data = data or {}
local output = {}
local func,err = loadstring(str,'tag #' .. tag)
if not func then
return getDefault(data),err
end
local env = {}
function env.print(...)
recurse({...},output)
end
for k,v in pairs(data) do
env[k] = v
end
setfenv(func,env)
local results = {pcall(func)}
if not table.remove(results,1) then
return getDefault(data),results[1]
end
recurse(results,output)
return table.concat(output)
end
local function doTemplate(template,data)
local lit = template[1]
local tag = template[2]
local errors = {}
local output = ''
for i = 1,#lit do
output = output .. lit[i]
if tag[i] then
local result,err = parseTag(tag[i],data,i)
if err then
errors[#errors+1] = err
end
output = output .. result
end
end
return output,errors
end
local function parseTemplate(source)
local template = {{},{}}
local i,s = 1,1
local n = #source
local t = 1
local intag = false
local lit = ''
local tag = ''
while i <= n do
if source:sub(i,i) == '\\' then
if intag then
tag = tag .. source:sub(s,i-1) .. source:sub(i+1,i+1)
else
lit = lit .. source:sub(s,i-1) .. source:sub(i+1,i+1)
end
i = i + 1
s = i + 1
elseif not intag and source:sub(i,i+1) == '{{' then
lit = lit .. source:sub(s,i-1)
intag = true
i = i + 1
s = i + 1
elseif intag and source:sub(i,i+1) == '}}' then
tag = tag .. source:sub(s,i-1)
template[1][t] = lit
template[2][t] = tag
lit = ''
tag = ''
t = t + 1
intag = false
i = i + 1
s = i + 1
end
i = i + 1
end
template[1][t] = lit .. source:sub(s,n)
return template
end
local sourceCache = {}
local fileCache = {}
return {
Parse = function(source,data)
local template
if sourceCache[source] then
template = sourceCache[source]
else
template = parseTemplate(source)
sourceCache[source] = template
end
return doTemplate(template,data)
end;
ParseFile = function(file,data)
local template
if fileCache[file] then
template = fileCache[file]
else
local f,err = io.open(file)
if not f then
return nil,err
end
template = parseTemplate(f:read('*a'))
fileCache[file] = template
f:close()
end
return doTemplate(template,data)
end;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment