Skip to content

Instantly share code, notes, and snippets.

@jsimmons
Created December 4, 2010 23:25
Show Gist options
  • Select an option

  • Save jsimmons/728594 to your computer and use it in GitHub Desktop.

Select an option

Save jsimmons/728594 to your computer and use it in GitHub Desktop.
Simple templating system in Lua using Lua as the templating language.
local concat, coroutine, insert, loadstring, open, pcall, setfenv, setmetatable =
table.concat, coroutine, table.insert, loadstring, io.open, pcall, setfenv, setmetatable
module 'spin.template'
function parse_file(path, env)
local file, err = open(path, 'r')
if not file then return nil, err end
local source = file:read('*a')
if not source then return nil, 'could not read from file' end
file:close();
return parse(source)
end
function parse(source)
local code, index = {}, 1
while true do
-- Find code blocks delimited by {{ and }}.
local start, stop, block = source:find('({%b{}})', index)
-- Insert the block of plaintext prior to the code block as an echo.
insert(code, ('echo [[%s]]'):format(source:sub(index, (start or 0) - 1)))
-- If the pattern was not found we're at the end of the source so no need to continue.
if not start then break end
-- In this block determine if the result should be treated as an echo,
-- an include or simply a code block. Also extracts the meat.
local echo, include, block = block:match('{{(=?)(@?)(.+)}}')
if echo:len() == 1 then
block = ('echo(%s)'):format(block)
elseif include:len() == 1 then
-- Yields for more data (contents of the include)
block = coroutine.yield(block)
end
insert(code, block)
index = stop + 1
end
return concat(code, '\n')
end
function compile(code, name, env)
local chunk, err = loadstring(code, name)
if not chunk then return nil, err, code end
return function(data)
local output = {}
local data = data or {}
function data.echo(...)
insert(output, concat {...})
end
-- Instead of joining the two tables manually, we make lookups which fail in the data table fall back to the environment passed when we compiled.
-- This means that views are unable to inadvertently mess with the global state, and with a restricted environment, are completely unable to.
data = setmetatable(data, {__index=env, __metatable='metatable is locked'})
setfenv(chunk, data)
local success, err = pcall(chunk)
if not success then return nil, err, code end
return concat(output)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment