xref: /src/tools/lua/template.lua (revision 6ef644f5889afbd0f681b08ed1a2f369524af83e)
194cba803SRyan Moeller-- From lua-resty-template (modified to remove external dependencies)
294cba803SRyan Moeller--[[
394cba803SRyan MoellerCopyright (c) 2014 - 2020 Aapo Talvensaari
494cba803SRyan MoellerAll rights reserved.
594cba803SRyan Moeller
694cba803SRyan MoellerRedistribution and use in source and binary forms, with or without modification,
794cba803SRyan Moellerare permitted provided that the following conditions are met:
894cba803SRyan Moeller
994cba803SRyan Moeller* Redistributions of source code must retain the above copyright notice, this
1094cba803SRyan Moeller  list of conditions and the following disclaimer.
1194cba803SRyan Moeller
1294cba803SRyan Moeller* Redistributions in binary form must reproduce the above copyright notice, this
1394cba803SRyan Moeller  list of conditions and the following disclaimer in the documentation and/or
1494cba803SRyan Moeller  other materials provided with the distribution.
1594cba803SRyan Moeller
1694cba803SRyan Moeller* Neither the name of the {organization} nor the names of its
1794cba803SRyan Moeller  contributors may be used to endorse or promote products derived from
1894cba803SRyan Moeller  this software without specific prior written permission.
1994cba803SRyan Moeller
2094cba803SRyan MoellerTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
2194cba803SRyan MoellerANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2294cba803SRyan MoellerWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2394cba803SRyan MoellerDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
2494cba803SRyan MoellerANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2594cba803SRyan Moeller(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2694cba803SRyan MoellerLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
2794cba803SRyan MoellerANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2894cba803SRyan Moeller(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2994cba803SRyan MoellerSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3094cba803SRyan Moeller]]--
3194cba803SRyan Moeller
3294cba803SRyan Moellerlocal setmetatable = setmetatable
3394cba803SRyan Moellerlocal loadstring = loadstring
3494cba803SRyan Moellerlocal tostring = tostring
3594cba803SRyan Moellerlocal setfenv = setfenv
3694cba803SRyan Moellerlocal require = require
3794cba803SRyan Moellerlocal concat = table.concat
3894cba803SRyan Moellerlocal assert = assert
3994cba803SRyan Moellerlocal write = io.write
4094cba803SRyan Moellerlocal pcall = pcall
4194cba803SRyan Moellerlocal phase
4294cba803SRyan Moellerlocal open = io.open
4394cba803SRyan Moellerlocal load = load
4494cba803SRyan Moellerlocal type = type
4594cba803SRyan Moellerlocal dump = string.dump
4694cba803SRyan Moellerlocal find = string.find
4794cba803SRyan Moellerlocal gsub = string.gsub
4894cba803SRyan Moellerlocal byte = string.byte
4994cba803SRyan Moellerlocal null
5094cba803SRyan Moellerlocal sub = string.sub
5194cba803SRyan Moellerlocal var
5294cba803SRyan Moeller
5394cba803SRyan Moellerlocal _VERSION = _VERSION
5494cba803SRyan Moellerlocal _ENV = _ENV -- luacheck: globals _ENV
5594cba803SRyan Moellerlocal _G = _G
5694cba803SRyan Moeller
5794cba803SRyan Moellerlocal HTML_ENTITIES = {
5894cba803SRyan Moeller    ["&"] = "&",
5994cba803SRyan Moeller    ["<"] = "&lt;",
6094cba803SRyan Moeller    [">"] = "&gt;",
6194cba803SRyan Moeller    ['"'] = "&quot;",
6294cba803SRyan Moeller    ["'"] = "&#39;",
6394cba803SRyan Moeller    ["/"] = "&#47;"
6494cba803SRyan Moeller}
6594cba803SRyan Moeller
6694cba803SRyan Moellerlocal CODE_ENTITIES = {
6794cba803SRyan Moeller    ["{"] = "&#123;",
6894cba803SRyan Moeller    ["}"] = "&#125;",
6994cba803SRyan Moeller    ["&"] = "&amp;",
7094cba803SRyan Moeller    ["<"] = "&lt;",
7194cba803SRyan Moeller    [">"] = "&gt;",
7294cba803SRyan Moeller    ['"'] = "&quot;",
7394cba803SRyan Moeller    ["'"] = "&#39;",
7494cba803SRyan Moeller    ["/"] = "&#47;"
7594cba803SRyan Moeller}
7694cba803SRyan Moeller
7794cba803SRyan Moellerlocal VAR_PHASES
7894cba803SRyan Moeller
7994cba803SRyan Moellerlocal ESC    = byte("\27")
8094cba803SRyan Moellerlocal NUL    = byte("\0")
8194cba803SRyan Moellerlocal HT     = byte("\t")
8294cba803SRyan Moellerlocal VT     = byte("\v")
8394cba803SRyan Moellerlocal LF     = byte("\n")
8494cba803SRyan Moellerlocal SOL    = byte("/")
8594cba803SRyan Moellerlocal BSOL   = byte("\\")
8694cba803SRyan Moellerlocal SP     = byte(" ")
8794cba803SRyan Moellerlocal AST    = byte("*")
8894cba803SRyan Moellerlocal NUM    = byte("#")
8994cba803SRyan Moellerlocal LPAR   = byte("(")
9094cba803SRyan Moellerlocal LSQB   = byte("[")
9194cba803SRyan Moellerlocal LCUB   = byte("{")
9294cba803SRyan Moellerlocal MINUS  = byte("-")
9394cba803SRyan Moellerlocal PERCNT = byte("%")
9494cba803SRyan Moeller
9594cba803SRyan Moellerlocal EMPTY  = ""
9694cba803SRyan Moeller
9794cba803SRyan Moellerlocal VIEW_ENV
9894cba803SRyan Moellerif _VERSION == "Lua 5.1" then
9994cba803SRyan Moeller    VIEW_ENV = { __index = function(t, k)
10094cba803SRyan Moeller        return t.context[k] or t.template[k] or _G[k]
10194cba803SRyan Moeller    end }
10294cba803SRyan Moellerelse
10394cba803SRyan Moeller    VIEW_ENV = { __index = function(t, k)
10494cba803SRyan Moeller        return t.context[k] or t.template[k] or _ENV[k]
10594cba803SRyan Moeller    end }
10694cba803SRyan Moellerend
10794cba803SRyan Moeller
10894cba803SRyan Moellerlocal newtab
10994cba803SRyan Moellerdo
11094cba803SRyan Moeller    local ok
11194cba803SRyan Moeller    ok, newtab = pcall(require, "table.new")
11294cba803SRyan Moeller    if not ok then newtab = function() return {} end end
11394cba803SRyan Moellerend
11494cba803SRyan Moeller
11594cba803SRyan Moellerlocal function enabled(val)
11694cba803SRyan Moeller    if val == nil then return true end
11794cba803SRyan Moeller    return val == true or (val == "1" or val == "true" or val == "on")
11894cba803SRyan Moellerend
11994cba803SRyan Moeller
12094cba803SRyan Moellerlocal function trim(s)
12194cba803SRyan Moeller    return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY)
12294cba803SRyan Moellerend
12394cba803SRyan Moeller
12494cba803SRyan Moellerlocal function rpos(view, s)
12594cba803SRyan Moeller    while s > 0 do
12694cba803SRyan Moeller        local c = byte(view, s, s)
12794cba803SRyan Moeller        if c == SP or c == HT or c == VT or c == NUL then
12894cba803SRyan Moeller            s = s - 1
12994cba803SRyan Moeller        else
13094cba803SRyan Moeller            break
13194cba803SRyan Moeller        end
13294cba803SRyan Moeller    end
13394cba803SRyan Moeller    return s
13494cba803SRyan Moellerend
13594cba803SRyan Moeller
13694cba803SRyan Moellerlocal function escaped(view, s)
13794cba803SRyan Moeller    if s > 1 and byte(view, s - 1, s - 1) == BSOL then
13894cba803SRyan Moeller        if s > 2 and byte(view, s - 2, s - 2) == BSOL then
13994cba803SRyan Moeller            return false, 1
14094cba803SRyan Moeller        else
14194cba803SRyan Moeller            return true, 1
14294cba803SRyan Moeller        end
14394cba803SRyan Moeller    end
14494cba803SRyan Moeller    return false, 0
14594cba803SRyan Moellerend
14694cba803SRyan Moeller
14794cba803SRyan Moellerlocal function read_file(path)
14894cba803SRyan Moeller    local file, err = open(path, "rb")
14994cba803SRyan Moeller    if not file then return nil, err end
15094cba803SRyan Moeller    local content
15194cba803SRyan Moeller    content, err = file:read "*a"
15294cba803SRyan Moeller    file:close()
15394cba803SRyan Moeller    return content, err
15494cba803SRyan Moellerend
15594cba803SRyan Moeller
15694cba803SRyan Moellerlocal function load_view(template)
15794cba803SRyan Moeller    return function(view, plain)
15894cba803SRyan Moeller	if plain == true then return view end
15994cba803SRyan Moeller	local path, root = view, template.root
16094cba803SRyan Moeller	if root and root ~= EMPTY then
16194cba803SRyan Moeller	    if byte(root, -1) == SOL then root = sub(root, 1, -2) end
16294cba803SRyan Moeller	    if byte(view,  1) == SOL then path = sub(view, 2) end
16394cba803SRyan Moeller	    path = root .. "/" .. path
16494cba803SRyan Moeller	end
16594cba803SRyan Moeller	return plain == false and assert(read_file(path)) or read_file(path) or view
16694cba803SRyan Moeller    end
16794cba803SRyan Moellerend
16894cba803SRyan Moeller
16994cba803SRyan Moellerlocal function load_file(func)
17094cba803SRyan Moeller    return function(view) return func(view, false) end
17194cba803SRyan Moellerend
17294cba803SRyan Moeller
17394cba803SRyan Moellerlocal function load_string(func)
17494cba803SRyan Moeller    return function(view) return func(view, true) end
17594cba803SRyan Moellerend
17694cba803SRyan Moeller
17794cba803SRyan Moellerlocal function loader(template)
17894cba803SRyan Moeller    return function(view)
17994cba803SRyan Moeller	return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV)))
18094cba803SRyan Moeller    end
18194cba803SRyan Moellerend
18294cba803SRyan Moeller
18394cba803SRyan Moellerlocal function visit(visitors, content, tag, name)
18494cba803SRyan Moeller    if not visitors then
18594cba803SRyan Moeller        return content
18694cba803SRyan Moeller    end
18794cba803SRyan Moeller
18894cba803SRyan Moeller    for i = 1, visitors.n do
18994cba803SRyan Moeller        content = visitors[i](content, tag, name)
19094cba803SRyan Moeller    end
19194cba803SRyan Moeller
19294cba803SRyan Moeller    return content
19394cba803SRyan Moellerend
19494cba803SRyan Moeller
19594cba803SRyan Moellerlocal function new(template, safe)
19694cba803SRyan Moeller    template = template or newtab(0, 26)
19794cba803SRyan Moeller
19894cba803SRyan Moeller    template._VERSION    = "2.0"
19994cba803SRyan Moeller    template.cache       = {}
20094cba803SRyan Moeller    template.load        = load_view(template)
20194cba803SRyan Moeller    template.load_file   = load_file(template.load)
20294cba803SRyan Moeller    template.load_string = load_string(template.load)
20394cba803SRyan Moeller    template.print       = write
20494cba803SRyan Moeller
20594cba803SRyan Moeller    local load_chunk = loader(template)
20694cba803SRyan Moeller
20794cba803SRyan Moeller    local caching
20894cba803SRyan Moeller    if VAR_PHASES and VAR_PHASES[phase()] then
20994cba803SRyan Moeller        caching = enabled(var.template_cache)
21094cba803SRyan Moeller    else
21194cba803SRyan Moeller        caching = true
21294cba803SRyan Moeller    end
21394cba803SRyan Moeller
21494cba803SRyan Moeller    local visitors
21594cba803SRyan Moeller    function template.visit(func)
21694cba803SRyan Moeller        if not visitors then
21794cba803SRyan Moeller            visitors = { func, n = 1 }
21894cba803SRyan Moeller            return
21994cba803SRyan Moeller        end
22094cba803SRyan Moeller        visitors.n = visitors.n + 1
22194cba803SRyan Moeller        visitors[visitors.n] = func
22294cba803SRyan Moeller    end
22394cba803SRyan Moeller
22494cba803SRyan Moeller    function template.caching(enable)
22594cba803SRyan Moeller        if enable ~= nil then caching = enable == true end
22694cba803SRyan Moeller        return caching
22794cba803SRyan Moeller    end
22894cba803SRyan Moeller
22994cba803SRyan Moeller    function template.output(s)
23094cba803SRyan Moeller        if s == nil or s == null then return EMPTY end
23194cba803SRyan Moeller        if type(s) == "function" then return template.output(s()) end
23294cba803SRyan Moeller        return tostring(s)
23394cba803SRyan Moeller    end
23494cba803SRyan Moeller
23594cba803SRyan Moeller    function template.escape(s, c)
23694cba803SRyan Moeller        if type(s) == "string" then
23794cba803SRyan Moeller            if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
23894cba803SRyan Moeller            return gsub(s, "[\">/<'&]", HTML_ENTITIES)
23994cba803SRyan Moeller        end
24094cba803SRyan Moeller        return template.output(s)
24194cba803SRyan Moeller    end
24294cba803SRyan Moeller
24394cba803SRyan Moeller    function template.new(view, layout)
24494cba803SRyan Moeller        local vt = type(view)
24594cba803SRyan Moeller
24694cba803SRyan Moeller        if vt == "boolean" then return new(nil,  view) end
24794cba803SRyan Moeller        if vt == "table"   then return new(view, safe) end
24894cba803SRyan Moeller        if vt == "nil"     then return new(nil,  safe) end
24994cba803SRyan Moeller
25094cba803SRyan Moeller        local render
25194cba803SRyan Moeller        local process
25294cba803SRyan Moeller        if layout then
25394cba803SRyan Moeller            if type(layout) == "table" then
25494cba803SRyan Moeller                render = function(self, context)
25594cba803SRyan Moeller                    context = context or self
25694cba803SRyan Moeller                    context.blocks = context.blocks or {}
25794cba803SRyan Moeller                    context.view = template.process(view, context)
25894cba803SRyan Moeller                    layout.blocks = context.blocks or {}
25994cba803SRyan Moeller                    layout.view = context.view or EMPTY
26094cba803SRyan Moeller                    layout:render()
26194cba803SRyan Moeller                end
26294cba803SRyan Moeller                process = function(self, context)
26394cba803SRyan Moeller                    context = context or self
26494cba803SRyan Moeller                    context.blocks = context.blocks or {}
26594cba803SRyan Moeller                    context.view = template.process(view, context)
26694cba803SRyan Moeller                    layout.blocks = context.blocks or {}
26794cba803SRyan Moeller                    layout.view = context.view
26894cba803SRyan Moeller                    return tostring(layout)
26994cba803SRyan Moeller                end
27094cba803SRyan Moeller            else
27194cba803SRyan Moeller                render = function(self, context)
27294cba803SRyan Moeller                    context = context or self
27394cba803SRyan Moeller                    context.blocks = context.blocks or {}
27494cba803SRyan Moeller                    context.view = template.process(view, context)
27594cba803SRyan Moeller                    template.render(layout, context)
27694cba803SRyan Moeller                end
27794cba803SRyan Moeller                process = function(self, context)
27894cba803SRyan Moeller                    context = context or self
27994cba803SRyan Moeller                    context.blocks = context.blocks or {}
28094cba803SRyan Moeller                    context.view = template.process(view, context)
28194cba803SRyan Moeller                    return template.process(layout, context)
28294cba803SRyan Moeller                end
28394cba803SRyan Moeller            end
28494cba803SRyan Moeller        else
28594cba803SRyan Moeller            render = function(self, context)
28694cba803SRyan Moeller                return template.render(view, context or self)
28794cba803SRyan Moeller            end
28894cba803SRyan Moeller            process = function(self, context)
28994cba803SRyan Moeller                return template.process(view, context or self)
29094cba803SRyan Moeller            end
29194cba803SRyan Moeller        end
29294cba803SRyan Moeller
29394cba803SRyan Moeller        if safe then
29494cba803SRyan Moeller            return setmetatable({
29594cba803SRyan Moeller                render = function(...)
29694cba803SRyan Moeller                    local ok, err = pcall(render, ...)
29794cba803SRyan Moeller                    if not ok then
29894cba803SRyan Moeller                        return nil, err
29994cba803SRyan Moeller                    end
30094cba803SRyan Moeller                end,
30194cba803SRyan Moeller                process = function(...)
30294cba803SRyan Moeller                    local ok, output = pcall(process, ...)
30394cba803SRyan Moeller                    if not ok then
30494cba803SRyan Moeller                        return nil, output
30594cba803SRyan Moeller                    end
30694cba803SRyan Moeller                    return output
30794cba803SRyan Moeller                end,
30894cba803SRyan Moeller             }, {
30994cba803SRyan Moeller                __tostring = function(...)
31094cba803SRyan Moeller                    local ok, output = pcall(process, ...)
31194cba803SRyan Moeller                    if not ok then
31294cba803SRyan Moeller                        return ""
31394cba803SRyan Moeller                    end
31494cba803SRyan Moeller                    return output
31594cba803SRyan Moeller            end })
31694cba803SRyan Moeller        end
31794cba803SRyan Moeller
31894cba803SRyan Moeller        return setmetatable({
31994cba803SRyan Moeller            render = render,
32094cba803SRyan Moeller            process = process
32194cba803SRyan Moeller        }, {
32294cba803SRyan Moeller            __tostring = process
32394cba803SRyan Moeller        })
32494cba803SRyan Moeller    end
32594cba803SRyan Moeller
32694cba803SRyan Moeller    function template.precompile(view, path, strip, plain)
32794cba803SRyan Moeller        local chunk = dump(template.compile(view, nil, plain), strip ~= false)
32894cba803SRyan Moeller        if path then
32994cba803SRyan Moeller            local file = open(path, "wb")
33094cba803SRyan Moeller            file:write(chunk)
33194cba803SRyan Moeller            file:close()
33294cba803SRyan Moeller        end
33394cba803SRyan Moeller        return chunk
33494cba803SRyan Moeller    end
33594cba803SRyan Moeller
33694cba803SRyan Moeller    function template.precompile_string(view, path, strip)
33794cba803SRyan Moeller        return template.precompile(view, path, strip, true)
33894cba803SRyan Moeller    end
33994cba803SRyan Moeller
34094cba803SRyan Moeller    function template.precompile_file(view, path, strip)
34194cba803SRyan Moeller        return template.precompile(view, path, strip, false)
34294cba803SRyan Moeller    end
34394cba803SRyan Moeller
34494cba803SRyan Moeller    function template.compile(view, cache_key, plain)
34594cba803SRyan Moeller        assert(view, "view was not provided for template.compile(view, cache_key, plain)")
34694cba803SRyan Moeller        if cache_key == "no-cache" then
34794cba803SRyan Moeller            return load_chunk(template.parse(view, plain)), false
34894cba803SRyan Moeller        end
34994cba803SRyan Moeller        cache_key = cache_key or view
35094cba803SRyan Moeller        local cache = template.cache
35194cba803SRyan Moeller        if cache[cache_key] then return cache[cache_key], true end
35294cba803SRyan Moeller        local func = load_chunk(template.parse(view, plain))
35394cba803SRyan Moeller        if caching then cache[cache_key] = func end
35494cba803SRyan Moeller        return func, false
35594cba803SRyan Moeller    end
35694cba803SRyan Moeller
35794cba803SRyan Moeller    function template.compile_file(view, cache_key)
35894cba803SRyan Moeller        return template.compile(view, cache_key, false)
35994cba803SRyan Moeller    end
36094cba803SRyan Moeller
36194cba803SRyan Moeller    function template.compile_string(view, cache_key)
36294cba803SRyan Moeller        return template.compile(view, cache_key, true)
36394cba803SRyan Moeller    end
36494cba803SRyan Moeller
36594cba803SRyan Moeller    function template.parse(view, plain)
36694cba803SRyan Moeller        assert(view, "view was not provided for template.parse(view, plain)")
36794cba803SRyan Moeller        if plain ~= true then
36894cba803SRyan Moeller            view = template.load(view, plain)
36994cba803SRyan Moeller            if byte(view, 1, 1) == ESC then return view end
37094cba803SRyan Moeller        end
37194cba803SRyan Moeller        local j = 2
37294cba803SRyan Moeller        local c = {[[
37394cba803SRyan Moellercontext=... or {}
37494cba803SRyan Moellerlocal ___,blocks,layout={},blocks or {}
37594cba803SRyan Moellerlocal function include(v, c) return template.process(v, c or context) end
37694cba803SRyan Moellerlocal function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end
37794cba803SRyan Moeller]] }
37894cba803SRyan Moeller        local i, s = 1, find(view, "{", 1, true)
37994cba803SRyan Moeller        while s do
38094cba803SRyan Moeller            local t, p = byte(view, s + 1, s + 1), s + 2
38194cba803SRyan Moeller            if t == LCUB then
38294cba803SRyan Moeller                local e = find(view, "}}", p, true)
38394cba803SRyan Moeller                if e then
38494cba803SRyan Moeller                    local z, w = escaped(view, s)
38594cba803SRyan Moeller                    if i < s - w then
38694cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
38794cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
38894cba803SRyan Moeller                        c[j+2] = "]=]\n"
38994cba803SRyan Moeller                        j=j+3
39094cba803SRyan Moeller                    end
39194cba803SRyan Moeller                    if z then
39294cba803SRyan Moeller                        i = s
39394cba803SRyan Moeller                    else
39494cba803SRyan Moeller                        c[j] = "___[#___+1]=template.escape("
39594cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{")
39694cba803SRyan Moeller                        c[j+2] = ")\n"
39794cba803SRyan Moeller                        j=j+3
39894cba803SRyan Moeller                        s, i = e + 1, e + 2
39994cba803SRyan Moeller                    end
40094cba803SRyan Moeller                end
40194cba803SRyan Moeller            elseif t == AST then
40294cba803SRyan Moeller                local e = find(view, "*}", p, true)
40394cba803SRyan Moeller                if e then
40494cba803SRyan Moeller                    local z, w = escaped(view, s)
40594cba803SRyan Moeller                    if i < s - w then
40694cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
40794cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
40894cba803SRyan Moeller                        c[j+2] = "]=]\n"
40994cba803SRyan Moeller                        j=j+3
41094cba803SRyan Moeller                    end
41194cba803SRyan Moeller                    if z then
41294cba803SRyan Moeller                        i = s
41394cba803SRyan Moeller                    else
41494cba803SRyan Moeller                        c[j] = "___[#___+1]=template.output("
41594cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*")
41694cba803SRyan Moeller                        c[j+2] = ")\n"
41794cba803SRyan Moeller                        j=j+3
41894cba803SRyan Moeller                        s, i = e + 1, e + 2
41994cba803SRyan Moeller                    end
42094cba803SRyan Moeller                end
42194cba803SRyan Moeller            elseif t == PERCNT then
42294cba803SRyan Moeller                local e = find(view, "%}", p, true)
42394cba803SRyan Moeller                if e then
42494cba803SRyan Moeller                    local z, w = escaped(view, s)
42594cba803SRyan Moeller                    if z then
42694cba803SRyan Moeller                        if i < s - w then
42794cba803SRyan Moeller                            c[j] = "___[#___+1]=[=[\n"
42894cba803SRyan Moeller                            c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
42994cba803SRyan Moeller                            c[j+2] = "]=]\n"
43094cba803SRyan Moeller                            j=j+3
43194cba803SRyan Moeller                        end
43294cba803SRyan Moeller                        i = s
43394cba803SRyan Moeller                    else
43494cba803SRyan Moeller                        local n = e + 2
43594cba803SRyan Moeller                        if byte(view, n, n) == LF then
43694cba803SRyan Moeller                            n = n + 1
43794cba803SRyan Moeller                        end
43894cba803SRyan Moeller                        local r = rpos(view, s - 1)
43994cba803SRyan Moeller                        if i <= r then
44094cba803SRyan Moeller                            c[j] = "___[#___+1]=[=[\n"
44194cba803SRyan Moeller                            c[j+1] = visit(visitors, sub(view, i, r))
44294cba803SRyan Moeller                            c[j+2] = "]=]\n"
44394cba803SRyan Moeller                            j=j+3
44494cba803SRyan Moeller                        end
44594cba803SRyan Moeller                        c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%")
44694cba803SRyan Moeller                        c[j+1] = "\n"
44794cba803SRyan Moeller                        j=j+2
44894cba803SRyan Moeller                        s, i = n - 1, n
44994cba803SRyan Moeller                    end
45094cba803SRyan Moeller                end
45194cba803SRyan Moeller            elseif t == LPAR then
45294cba803SRyan Moeller                local e = find(view, ")}", p, true)
45394cba803SRyan Moeller                if e then
45494cba803SRyan Moeller                    local z, w = escaped(view, s)
45594cba803SRyan Moeller                    if i < s - w then
45694cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
45794cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
45894cba803SRyan Moeller                        c[j+2] = "]=]\n"
45994cba803SRyan Moeller                        j=j+3
46094cba803SRyan Moeller                    end
46194cba803SRyan Moeller                    if z then
46294cba803SRyan Moeller                        i = s
46394cba803SRyan Moeller                    else
46494cba803SRyan Moeller                        local f = visit(visitors, sub(view, p, e - 1), "(")
46594cba803SRyan Moeller                        local x = find(f, ",", 2, true)
46694cba803SRyan Moeller                        if x then
46794cba803SRyan Moeller                            c[j] = "___[#___+1]=include([=["
46894cba803SRyan Moeller                            c[j+1] = trim(sub(f, 1, x - 1))
46994cba803SRyan Moeller                            c[j+2] = "]=],"
47094cba803SRyan Moeller                            c[j+3] = trim(sub(f, x + 1))
47194cba803SRyan Moeller                            c[j+4] = ")\n"
47294cba803SRyan Moeller                            j=j+5
47394cba803SRyan Moeller                        else
47494cba803SRyan Moeller                            c[j] = "___[#___+1]=include([=["
47594cba803SRyan Moeller                            c[j+1] = trim(f)
47694cba803SRyan Moeller                            c[j+2] = "]=])\n"
47794cba803SRyan Moeller                            j=j+3
47894cba803SRyan Moeller                        end
47994cba803SRyan Moeller                        s, i = e + 1, e + 2
48094cba803SRyan Moeller                    end
48194cba803SRyan Moeller                end
48294cba803SRyan Moeller            elseif t == LSQB then
48394cba803SRyan Moeller                local e = find(view, "]}", p, true)
48494cba803SRyan Moeller                if e then
48594cba803SRyan Moeller                    local z, w = escaped(view, s)
48694cba803SRyan Moeller                    if i < s - w then
48794cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
48894cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
48994cba803SRyan Moeller                        c[j+2] = "]=]\n"
49094cba803SRyan Moeller                        j=j+3
49194cba803SRyan Moeller                    end
49294cba803SRyan Moeller                    if z then
49394cba803SRyan Moeller                        i = s
49494cba803SRyan Moeller                    else
49594cba803SRyan Moeller                        c[j] = "___[#___+1]=include("
49694cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[")
49794cba803SRyan Moeller                        c[j+2] = ")\n"
49894cba803SRyan Moeller                        j=j+3
49994cba803SRyan Moeller                        s, i = e + 1, e + 2
50094cba803SRyan Moeller                    end
50194cba803SRyan Moeller                end
50294cba803SRyan Moeller            elseif t == MINUS then
50394cba803SRyan Moeller                local e = find(view, "-}", p, true)
50494cba803SRyan Moeller                if e then
50594cba803SRyan Moeller                    local x, y = find(view, sub(view, s, e + 1), e + 2, true)
50694cba803SRyan Moeller                    if x then
50794cba803SRyan Moeller                        local z, w = escaped(view, s)
50894cba803SRyan Moeller                        if z then
50994cba803SRyan Moeller                            if i < s - w then
51094cba803SRyan Moeller                                c[j] = "___[#___+1]=[=[\n"
51194cba803SRyan Moeller                                c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
51294cba803SRyan Moeller                                c[j+2] = "]=]\n"
51394cba803SRyan Moeller                                j=j+3
51494cba803SRyan Moeller                            end
51594cba803SRyan Moeller                            i = s
51694cba803SRyan Moeller                        else
51794cba803SRyan Moeller                            y = y + 1
51894cba803SRyan Moeller                            x = x - 1
51994cba803SRyan Moeller                            if byte(view, y, y) == LF then
52094cba803SRyan Moeller                                y = y + 1
52194cba803SRyan Moeller                            end
52294cba803SRyan Moeller                            local b = trim(sub(view, p, e - 1))
52394cba803SRyan Moeller                            if b == "verbatim" or b == "raw" then
52494cba803SRyan Moeller                                if i < s - w then
52594cba803SRyan Moeller                                    c[j] = "___[#___+1]=[=[\n"
52694cba803SRyan Moeller                                    c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
52794cba803SRyan Moeller                                    c[j+2] = "]=]\n"
52894cba803SRyan Moeller                                    j=j+3
52994cba803SRyan Moeller                                end
53094cba803SRyan Moeller                                c[j] = "___[#___+1]=[=["
53194cba803SRyan Moeller                                c[j+1] = visit(visitors, sub(view, e + 2, x))
53294cba803SRyan Moeller                                c[j+2] = "]=]\n"
53394cba803SRyan Moeller                                j=j+3
53494cba803SRyan Moeller                            else
53594cba803SRyan Moeller                                if byte(view, x, x) == LF then
53694cba803SRyan Moeller                                    x = x - 1
53794cba803SRyan Moeller                                end
53894cba803SRyan Moeller                                local r = rpos(view, s - 1)
53994cba803SRyan Moeller                                if i <= r then
54094cba803SRyan Moeller                                    c[j] = "___[#___+1]=[=[\n"
54194cba803SRyan Moeller                                    c[j+1] = visit(visitors, sub(view, i, r))
54294cba803SRyan Moeller                                    c[j+2] = "]=]\n"
54394cba803SRyan Moeller                                    j=j+3
54494cba803SRyan Moeller                                end
54594cba803SRyan Moeller                                c[j] = 'blocks["'
54694cba803SRyan Moeller                                c[j+1] = b
54794cba803SRyan Moeller                                c[j+2] = '"]=include[=['
54894cba803SRyan Moeller                                c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b)
54994cba803SRyan Moeller                                c[j+4] = "]=]\n"
55094cba803SRyan Moeller                                j=j+5
55194cba803SRyan Moeller                            end
55294cba803SRyan Moeller                            s, i = y - 1, y
55394cba803SRyan Moeller                        end
55494cba803SRyan Moeller                    end
55594cba803SRyan Moeller                end
55694cba803SRyan Moeller            elseif t == NUM then
55794cba803SRyan Moeller                local e = find(view, "#}", p, true)
55894cba803SRyan Moeller                if e then
55994cba803SRyan Moeller                    local z, w = escaped(view, s)
56094cba803SRyan Moeller                    if i < s - w then
56194cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
56294cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
56394cba803SRyan Moeller                        c[j+2] = "]=]\n"
56494cba803SRyan Moeller                        j=j+3
56594cba803SRyan Moeller                    end
56694cba803SRyan Moeller                    if z then
56794cba803SRyan Moeller                        i = s
56894cba803SRyan Moeller                    else
56994cba803SRyan Moeller                        e = e + 2
57094cba803SRyan Moeller                        if byte(view, e, e) == LF then
57194cba803SRyan Moeller                            e = e + 1
57294cba803SRyan Moeller                        end
57394cba803SRyan Moeller                        s, i = e - 1, e
57494cba803SRyan Moeller                    end
57594cba803SRyan Moeller                end
57694cba803SRyan Moeller            end
57794cba803SRyan Moeller            s = find(view, "{", s + 1, true)
57894cba803SRyan Moeller        end
57994cba803SRyan Moeller        s = sub(view, i)
58094cba803SRyan Moeller        if s and s ~= EMPTY then
58194cba803SRyan Moeller            c[j] = "___[#___+1]=[=[\n"
58294cba803SRyan Moeller            c[j+1] = visit(visitors, s)
58394cba803SRyan Moeller            c[j+2] = "]=]\n"
58494cba803SRyan Moeller            j=j+3
58594cba803SRyan Moeller        end
58694cba803SRyan Moeller        c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore
58794cba803SRyan Moeller        return concat(c)
58894cba803SRyan Moeller    end
58994cba803SRyan Moeller
59094cba803SRyan Moeller    function template.parse_file(view)
59194cba803SRyan Moeller        return template.parse(view, false)
59294cba803SRyan Moeller    end
59394cba803SRyan Moeller
59494cba803SRyan Moeller    function template.parse_string(view)
59594cba803SRyan Moeller        return template.parse(view, true)
59694cba803SRyan Moeller    end
59794cba803SRyan Moeller
59894cba803SRyan Moeller    function template.process(view, context, cache_key, plain)
59994cba803SRyan Moeller        assert(view, "view was not provided for template.process(view, context, cache_key, plain)")
60094cba803SRyan Moeller        return template.compile(view, cache_key, plain)(context)
60194cba803SRyan Moeller    end
60294cba803SRyan Moeller
60394cba803SRyan Moeller    function template.process_file(view, context, cache_key)
60494cba803SRyan Moeller        assert(view, "view was not provided for template.process_file(view, context, cache_key)")
60594cba803SRyan Moeller        return template.compile(view, cache_key, false)(context)
60694cba803SRyan Moeller    end
60794cba803SRyan Moeller
60894cba803SRyan Moeller    function template.process_string(view, context, cache_key)
60994cba803SRyan Moeller        assert(view, "view was not provided for template.process_string(view, context, cache_key)")
61094cba803SRyan Moeller        return template.compile(view, cache_key, true)(context)
61194cba803SRyan Moeller    end
61294cba803SRyan Moeller
61394cba803SRyan Moeller    function template.render(view, context, cache_key, plain)
61494cba803SRyan Moeller        assert(view, "view was not provided for template.render(view, context, cache_key, plain)")
61594cba803SRyan Moeller        template.print(template.process(view, context, cache_key, plain))
61694cba803SRyan Moeller    end
61794cba803SRyan Moeller
61894cba803SRyan Moeller    function template.render_file(view, context, cache_key)
61994cba803SRyan Moeller        assert(view, "view was not provided for template.render_file(view, context, cache_key)")
62094cba803SRyan Moeller        template.render(view, context, cache_key, false)
62194cba803SRyan Moeller    end
62294cba803SRyan Moeller
62394cba803SRyan Moeller    function template.render_string(view, context, cache_key)
62494cba803SRyan Moeller        assert(view, "view was not provided for template.render_string(view, context, cache_key)")
62594cba803SRyan Moeller        template.render(view, context, cache_key, true)
62694cba803SRyan Moeller    end
62794cba803SRyan Moeller
62894cba803SRyan Moeller    if safe then
62994cba803SRyan Moeller        return setmetatable({}, {
63094cba803SRyan Moeller            __index = function(_, k)
63194cba803SRyan Moeller                if type(template[k]) == "function" then
63294cba803SRyan Moeller                    return function(...)
63394cba803SRyan Moeller                        local ok, a, b = pcall(template[k], ...)
63494cba803SRyan Moeller                        if not ok then
63594cba803SRyan Moeller                            return nil, a
63694cba803SRyan Moeller                        end
63794cba803SRyan Moeller                        return a, b
63894cba803SRyan Moeller                    end
63994cba803SRyan Moeller                end
64094cba803SRyan Moeller                return template[k]
64194cba803SRyan Moeller            end,
64294cba803SRyan Moeller            __new_index = function(_, k, v)
64394cba803SRyan Moeller                template[k] = v
64494cba803SRyan Moeller            end,
64594cba803SRyan Moeller        })
64694cba803SRyan Moeller    end
64794cba803SRyan Moeller
64894cba803SRyan Moeller    return template
64994cba803SRyan Moellerend
65094cba803SRyan Moeller
65194cba803SRyan Moellerreturn new()
652