Created
December 4, 2010 23:25
-
-
Save jsimmons/728594 to your computer and use it in GitHub Desktop.
Simple templating system in Lua using Lua as the templating language.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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