xref: /freebsd-14.2/tools/lua/template.lua (revision 6ef644f5)
1*94cba803SRyan Moeller-- From lua-resty-template (modified to remove external dependencies)
2*94cba803SRyan Moeller--[[
3*94cba803SRyan MoellerCopyright (c) 2014 - 2020 Aapo Talvensaari
4*94cba803SRyan MoellerAll rights reserved.
5*94cba803SRyan Moeller
6*94cba803SRyan MoellerRedistribution and use in source and binary forms, with or without modification,
7*94cba803SRyan Moellerare permitted provided that the following conditions are met:
8*94cba803SRyan Moeller
9*94cba803SRyan Moeller* Redistributions of source code must retain the above copyright notice, this
10*94cba803SRyan Moeller  list of conditions and the following disclaimer.
11*94cba803SRyan Moeller
12*94cba803SRyan Moeller* Redistributions in binary form must reproduce the above copyright notice, this
13*94cba803SRyan Moeller  list of conditions and the following disclaimer in the documentation and/or
14*94cba803SRyan Moeller  other materials provided with the distribution.
15*94cba803SRyan Moeller
16*94cba803SRyan Moeller* Neither the name of the {organization} nor the names of its
17*94cba803SRyan Moeller  contributors may be used to endorse or promote products derived from
18*94cba803SRyan Moeller  this software without specific prior written permission.
19*94cba803SRyan Moeller
20*94cba803SRyan MoellerTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21*94cba803SRyan MoellerANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22*94cba803SRyan MoellerWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23*94cba803SRyan MoellerDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24*94cba803SRyan MoellerANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25*94cba803SRyan Moeller(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26*94cba803SRyan MoellerLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27*94cba803SRyan MoellerANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28*94cba803SRyan Moeller(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29*94cba803SRyan MoellerSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30*94cba803SRyan Moeller]]--
31*94cba803SRyan Moeller
32*94cba803SRyan Moellerlocal setmetatable = setmetatable
33*94cba803SRyan Moellerlocal loadstring = loadstring
34*94cba803SRyan Moellerlocal tostring = tostring
35*94cba803SRyan Moellerlocal setfenv = setfenv
36*94cba803SRyan Moellerlocal require = require
37*94cba803SRyan Moellerlocal concat = table.concat
38*94cba803SRyan Moellerlocal assert = assert
39*94cba803SRyan Moellerlocal write = io.write
40*94cba803SRyan Moellerlocal pcall = pcall
41*94cba803SRyan Moellerlocal phase
42*94cba803SRyan Moellerlocal open = io.open
43*94cba803SRyan Moellerlocal load = load
44*94cba803SRyan Moellerlocal type = type
45*94cba803SRyan Moellerlocal dump = string.dump
46*94cba803SRyan Moellerlocal find = string.find
47*94cba803SRyan Moellerlocal gsub = string.gsub
48*94cba803SRyan Moellerlocal byte = string.byte
49*94cba803SRyan Moellerlocal null
50*94cba803SRyan Moellerlocal sub = string.sub
51*94cba803SRyan Moellerlocal var
52*94cba803SRyan Moeller
53*94cba803SRyan Moellerlocal _VERSION = _VERSION
54*94cba803SRyan Moellerlocal _ENV = _ENV -- luacheck: globals _ENV
55*94cba803SRyan Moellerlocal _G = _G
56*94cba803SRyan Moeller
57*94cba803SRyan Moellerlocal HTML_ENTITIES = {
58*94cba803SRyan Moeller    ["&"] = "&",
59*94cba803SRyan Moeller    ["<"] = "&lt;",
60*94cba803SRyan Moeller    [">"] = "&gt;",
61*94cba803SRyan Moeller    ['"'] = "&quot;",
62*94cba803SRyan Moeller    ["'"] = "&#39;",
63*94cba803SRyan Moeller    ["/"] = "&#47;"
64*94cba803SRyan Moeller}
65*94cba803SRyan Moeller
66*94cba803SRyan Moellerlocal CODE_ENTITIES = {
67*94cba803SRyan Moeller    ["{"] = "&#123;",
68*94cba803SRyan Moeller    ["}"] = "&#125;",
69*94cba803SRyan Moeller    ["&"] = "&amp;",
70*94cba803SRyan Moeller    ["<"] = "&lt;",
71*94cba803SRyan Moeller    [">"] = "&gt;",
72*94cba803SRyan Moeller    ['"'] = "&quot;",
73*94cba803SRyan Moeller    ["'"] = "&#39;",
74*94cba803SRyan Moeller    ["/"] = "&#47;"
75*94cba803SRyan Moeller}
76*94cba803SRyan Moeller
77*94cba803SRyan Moellerlocal VAR_PHASES
78*94cba803SRyan Moeller
79*94cba803SRyan Moellerlocal ESC    = byte("\27")
80*94cba803SRyan Moellerlocal NUL    = byte("\0")
81*94cba803SRyan Moellerlocal HT     = byte("\t")
82*94cba803SRyan Moellerlocal VT     = byte("\v")
83*94cba803SRyan Moellerlocal LF     = byte("\n")
84*94cba803SRyan Moellerlocal SOL    = byte("/")
85*94cba803SRyan Moellerlocal BSOL   = byte("\\")
86*94cba803SRyan Moellerlocal SP     = byte(" ")
87*94cba803SRyan Moellerlocal AST    = byte("*")
88*94cba803SRyan Moellerlocal NUM    = byte("#")
89*94cba803SRyan Moellerlocal LPAR   = byte("(")
90*94cba803SRyan Moellerlocal LSQB   = byte("[")
91*94cba803SRyan Moellerlocal LCUB   = byte("{")
92*94cba803SRyan Moellerlocal MINUS  = byte("-")
93*94cba803SRyan Moellerlocal PERCNT = byte("%")
94*94cba803SRyan Moeller
95*94cba803SRyan Moellerlocal EMPTY  = ""
96*94cba803SRyan Moeller
97*94cba803SRyan Moellerlocal VIEW_ENV
98*94cba803SRyan Moellerif _VERSION == "Lua 5.1" then
99*94cba803SRyan Moeller    VIEW_ENV = { __index = function(t, k)
100*94cba803SRyan Moeller        return t.context[k] or t.template[k] or _G[k]
101*94cba803SRyan Moeller    end }
102*94cba803SRyan Moellerelse
103*94cba803SRyan Moeller    VIEW_ENV = { __index = function(t, k)
104*94cba803SRyan Moeller        return t.context[k] or t.template[k] or _ENV[k]
105*94cba803SRyan Moeller    end }
106*94cba803SRyan Moellerend
107*94cba803SRyan Moeller
108*94cba803SRyan Moellerlocal newtab
109*94cba803SRyan Moellerdo
110*94cba803SRyan Moeller    local ok
111*94cba803SRyan Moeller    ok, newtab = pcall(require, "table.new")
112*94cba803SRyan Moeller    if not ok then newtab = function() return {} end end
113*94cba803SRyan Moellerend
114*94cba803SRyan Moeller
115*94cba803SRyan Moellerlocal function enabled(val)
116*94cba803SRyan Moeller    if val == nil then return true end
117*94cba803SRyan Moeller    return val == true or (val == "1" or val == "true" or val == "on")
118*94cba803SRyan Moellerend
119*94cba803SRyan Moeller
120*94cba803SRyan Moellerlocal function trim(s)
121*94cba803SRyan Moeller    return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY)
122*94cba803SRyan Moellerend
123*94cba803SRyan Moeller
124*94cba803SRyan Moellerlocal function rpos(view, s)
125*94cba803SRyan Moeller    while s > 0 do
126*94cba803SRyan Moeller        local c = byte(view, s, s)
127*94cba803SRyan Moeller        if c == SP or c == HT or c == VT or c == NUL then
128*94cba803SRyan Moeller            s = s - 1
129*94cba803SRyan Moeller        else
130*94cba803SRyan Moeller            break
131*94cba803SRyan Moeller        end
132*94cba803SRyan Moeller    end
133*94cba803SRyan Moeller    return s
134*94cba803SRyan Moellerend
135*94cba803SRyan Moeller
136*94cba803SRyan Moellerlocal function escaped(view, s)
137*94cba803SRyan Moeller    if s > 1 and byte(view, s - 1, s - 1) == BSOL then
138*94cba803SRyan Moeller        if s > 2 and byte(view, s - 2, s - 2) == BSOL then
139*94cba803SRyan Moeller            return false, 1
140*94cba803SRyan Moeller        else
141*94cba803SRyan Moeller            return true, 1
142*94cba803SRyan Moeller        end
143*94cba803SRyan Moeller    end
144*94cba803SRyan Moeller    return false, 0
145*94cba803SRyan Moellerend
146*94cba803SRyan Moeller
147*94cba803SRyan Moellerlocal function read_file(path)
148*94cba803SRyan Moeller    local file, err = open(path, "rb")
149*94cba803SRyan Moeller    if not file then return nil, err end
150*94cba803SRyan Moeller    local content
151*94cba803SRyan Moeller    content, err = file:read "*a"
152*94cba803SRyan Moeller    file:close()
153*94cba803SRyan Moeller    return content, err
154*94cba803SRyan Moellerend
155*94cba803SRyan Moeller
156*94cba803SRyan Moellerlocal function load_view(template)
157*94cba803SRyan Moeller    return function(view, plain)
158*94cba803SRyan Moeller	if plain == true then return view end
159*94cba803SRyan Moeller	local path, root = view, template.root
160*94cba803SRyan Moeller	if root and root ~= EMPTY then
161*94cba803SRyan Moeller	    if byte(root, -1) == SOL then root = sub(root, 1, -2) end
162*94cba803SRyan Moeller	    if byte(view,  1) == SOL then path = sub(view, 2) end
163*94cba803SRyan Moeller	    path = root .. "/" .. path
164*94cba803SRyan Moeller	end
165*94cba803SRyan Moeller	return plain == false and assert(read_file(path)) or read_file(path) or view
166*94cba803SRyan Moeller    end
167*94cba803SRyan Moellerend
168*94cba803SRyan Moeller
169*94cba803SRyan Moellerlocal function load_file(func)
170*94cba803SRyan Moeller    return function(view) return func(view, false) end
171*94cba803SRyan Moellerend
172*94cba803SRyan Moeller
173*94cba803SRyan Moellerlocal function load_string(func)
174*94cba803SRyan Moeller    return function(view) return func(view, true) end
175*94cba803SRyan Moellerend
176*94cba803SRyan Moeller
177*94cba803SRyan Moellerlocal function loader(template)
178*94cba803SRyan Moeller    return function(view)
179*94cba803SRyan Moeller	return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV)))
180*94cba803SRyan Moeller    end
181*94cba803SRyan Moellerend
182*94cba803SRyan Moeller
183*94cba803SRyan Moellerlocal function visit(visitors, content, tag, name)
184*94cba803SRyan Moeller    if not visitors then
185*94cba803SRyan Moeller        return content
186*94cba803SRyan Moeller    end
187*94cba803SRyan Moeller
188*94cba803SRyan Moeller    for i = 1, visitors.n do
189*94cba803SRyan Moeller        content = visitors[i](content, tag, name)
190*94cba803SRyan Moeller    end
191*94cba803SRyan Moeller
192*94cba803SRyan Moeller    return content
193*94cba803SRyan Moellerend
194*94cba803SRyan Moeller
195*94cba803SRyan Moellerlocal function new(template, safe)
196*94cba803SRyan Moeller    template = template or newtab(0, 26)
197*94cba803SRyan Moeller
198*94cba803SRyan Moeller    template._VERSION    = "2.0"
199*94cba803SRyan Moeller    template.cache       = {}
200*94cba803SRyan Moeller    template.load        = load_view(template)
201*94cba803SRyan Moeller    template.load_file   = load_file(template.load)
202*94cba803SRyan Moeller    template.load_string = load_string(template.load)
203*94cba803SRyan Moeller    template.print       = write
204*94cba803SRyan Moeller
205*94cba803SRyan Moeller    local load_chunk = loader(template)
206*94cba803SRyan Moeller
207*94cba803SRyan Moeller    local caching
208*94cba803SRyan Moeller    if VAR_PHASES and VAR_PHASES[phase()] then
209*94cba803SRyan Moeller        caching = enabled(var.template_cache)
210*94cba803SRyan Moeller    else
211*94cba803SRyan Moeller        caching = true
212*94cba803SRyan Moeller    end
213*94cba803SRyan Moeller
214*94cba803SRyan Moeller    local visitors
215*94cba803SRyan Moeller    function template.visit(func)
216*94cba803SRyan Moeller        if not visitors then
217*94cba803SRyan Moeller            visitors = { func, n = 1 }
218*94cba803SRyan Moeller            return
219*94cba803SRyan Moeller        end
220*94cba803SRyan Moeller        visitors.n = visitors.n + 1
221*94cba803SRyan Moeller        visitors[visitors.n] = func
222*94cba803SRyan Moeller    end
223*94cba803SRyan Moeller
224*94cba803SRyan Moeller    function template.caching(enable)
225*94cba803SRyan Moeller        if enable ~= nil then caching = enable == true end
226*94cba803SRyan Moeller        return caching
227*94cba803SRyan Moeller    end
228*94cba803SRyan Moeller
229*94cba803SRyan Moeller    function template.output(s)
230*94cba803SRyan Moeller        if s == nil or s == null then return EMPTY end
231*94cba803SRyan Moeller        if type(s) == "function" then return template.output(s()) end
232*94cba803SRyan Moeller        return tostring(s)
233*94cba803SRyan Moeller    end
234*94cba803SRyan Moeller
235*94cba803SRyan Moeller    function template.escape(s, c)
236*94cba803SRyan Moeller        if type(s) == "string" then
237*94cba803SRyan Moeller            if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
238*94cba803SRyan Moeller            return gsub(s, "[\">/<'&]", HTML_ENTITIES)
239*94cba803SRyan Moeller        end
240*94cba803SRyan Moeller        return template.output(s)
241*94cba803SRyan Moeller    end
242*94cba803SRyan Moeller
243*94cba803SRyan Moeller    function template.new(view, layout)
244*94cba803SRyan Moeller        local vt = type(view)
245*94cba803SRyan Moeller
246*94cba803SRyan Moeller        if vt == "boolean" then return new(nil,  view) end
247*94cba803SRyan Moeller        if vt == "table"   then return new(view, safe) end
248*94cba803SRyan Moeller        if vt == "nil"     then return new(nil,  safe) end
249*94cba803SRyan Moeller
250*94cba803SRyan Moeller        local render
251*94cba803SRyan Moeller        local process
252*94cba803SRyan Moeller        if layout then
253*94cba803SRyan Moeller            if type(layout) == "table" then
254*94cba803SRyan Moeller                render = function(self, context)
255*94cba803SRyan Moeller                    context = context or self
256*94cba803SRyan Moeller                    context.blocks = context.blocks or {}
257*94cba803SRyan Moeller                    context.view = template.process(view, context)
258*94cba803SRyan Moeller                    layout.blocks = context.blocks or {}
259*94cba803SRyan Moeller                    layout.view = context.view or EMPTY
260*94cba803SRyan Moeller                    layout:render()
261*94cba803SRyan Moeller                end
262*94cba803SRyan Moeller                process = function(self, context)
263*94cba803SRyan Moeller                    context = context or self
264*94cba803SRyan Moeller                    context.blocks = context.blocks or {}
265*94cba803SRyan Moeller                    context.view = template.process(view, context)
266*94cba803SRyan Moeller                    layout.blocks = context.blocks or {}
267*94cba803SRyan Moeller                    layout.view = context.view
268*94cba803SRyan Moeller                    return tostring(layout)
269*94cba803SRyan Moeller                end
270*94cba803SRyan Moeller            else
271*94cba803SRyan Moeller                render = function(self, context)
272*94cba803SRyan Moeller                    context = context or self
273*94cba803SRyan Moeller                    context.blocks = context.blocks or {}
274*94cba803SRyan Moeller                    context.view = template.process(view, context)
275*94cba803SRyan Moeller                    template.render(layout, context)
276*94cba803SRyan Moeller                end
277*94cba803SRyan Moeller                process = function(self, context)
278*94cba803SRyan Moeller                    context = context or self
279*94cba803SRyan Moeller                    context.blocks = context.blocks or {}
280*94cba803SRyan Moeller                    context.view = template.process(view, context)
281*94cba803SRyan Moeller                    return template.process(layout, context)
282*94cba803SRyan Moeller                end
283*94cba803SRyan Moeller            end
284*94cba803SRyan Moeller        else
285*94cba803SRyan Moeller            render = function(self, context)
286*94cba803SRyan Moeller                return template.render(view, context or self)
287*94cba803SRyan Moeller            end
288*94cba803SRyan Moeller            process = function(self, context)
289*94cba803SRyan Moeller                return template.process(view, context or self)
290*94cba803SRyan Moeller            end
291*94cba803SRyan Moeller        end
292*94cba803SRyan Moeller
293*94cba803SRyan Moeller        if safe then
294*94cba803SRyan Moeller            return setmetatable({
295*94cba803SRyan Moeller                render = function(...)
296*94cba803SRyan Moeller                    local ok, err = pcall(render, ...)
297*94cba803SRyan Moeller                    if not ok then
298*94cba803SRyan Moeller                        return nil, err
299*94cba803SRyan Moeller                    end
300*94cba803SRyan Moeller                end,
301*94cba803SRyan Moeller                process = function(...)
302*94cba803SRyan Moeller                    local ok, output = pcall(process, ...)
303*94cba803SRyan Moeller                    if not ok then
304*94cba803SRyan Moeller                        return nil, output
305*94cba803SRyan Moeller                    end
306*94cba803SRyan Moeller                    return output
307*94cba803SRyan Moeller                end,
308*94cba803SRyan Moeller             }, {
309*94cba803SRyan Moeller                __tostring = function(...)
310*94cba803SRyan Moeller                    local ok, output = pcall(process, ...)
311*94cba803SRyan Moeller                    if not ok then
312*94cba803SRyan Moeller                        return ""
313*94cba803SRyan Moeller                    end
314*94cba803SRyan Moeller                    return output
315*94cba803SRyan Moeller            end })
316*94cba803SRyan Moeller        end
317*94cba803SRyan Moeller
318*94cba803SRyan Moeller        return setmetatable({
319*94cba803SRyan Moeller            render = render,
320*94cba803SRyan Moeller            process = process
321*94cba803SRyan Moeller        }, {
322*94cba803SRyan Moeller            __tostring = process
323*94cba803SRyan Moeller        })
324*94cba803SRyan Moeller    end
325*94cba803SRyan Moeller
326*94cba803SRyan Moeller    function template.precompile(view, path, strip, plain)
327*94cba803SRyan Moeller        local chunk = dump(template.compile(view, nil, plain), strip ~= false)
328*94cba803SRyan Moeller        if path then
329*94cba803SRyan Moeller            local file = open(path, "wb")
330*94cba803SRyan Moeller            file:write(chunk)
331*94cba803SRyan Moeller            file:close()
332*94cba803SRyan Moeller        end
333*94cba803SRyan Moeller        return chunk
334*94cba803SRyan Moeller    end
335*94cba803SRyan Moeller
336*94cba803SRyan Moeller    function template.precompile_string(view, path, strip)
337*94cba803SRyan Moeller        return template.precompile(view, path, strip, true)
338*94cba803SRyan Moeller    end
339*94cba803SRyan Moeller
340*94cba803SRyan Moeller    function template.precompile_file(view, path, strip)
341*94cba803SRyan Moeller        return template.precompile(view, path, strip, false)
342*94cba803SRyan Moeller    end
343*94cba803SRyan Moeller
344*94cba803SRyan Moeller    function template.compile(view, cache_key, plain)
345*94cba803SRyan Moeller        assert(view, "view was not provided for template.compile(view, cache_key, plain)")
346*94cba803SRyan Moeller        if cache_key == "no-cache" then
347*94cba803SRyan Moeller            return load_chunk(template.parse(view, plain)), false
348*94cba803SRyan Moeller        end
349*94cba803SRyan Moeller        cache_key = cache_key or view
350*94cba803SRyan Moeller        local cache = template.cache
351*94cba803SRyan Moeller        if cache[cache_key] then return cache[cache_key], true end
352*94cba803SRyan Moeller        local func = load_chunk(template.parse(view, plain))
353*94cba803SRyan Moeller        if caching then cache[cache_key] = func end
354*94cba803SRyan Moeller        return func, false
355*94cba803SRyan Moeller    end
356*94cba803SRyan Moeller
357*94cba803SRyan Moeller    function template.compile_file(view, cache_key)
358*94cba803SRyan Moeller        return template.compile(view, cache_key, false)
359*94cba803SRyan Moeller    end
360*94cba803SRyan Moeller
361*94cba803SRyan Moeller    function template.compile_string(view, cache_key)
362*94cba803SRyan Moeller        return template.compile(view, cache_key, true)
363*94cba803SRyan Moeller    end
364*94cba803SRyan Moeller
365*94cba803SRyan Moeller    function template.parse(view, plain)
366*94cba803SRyan Moeller        assert(view, "view was not provided for template.parse(view, plain)")
367*94cba803SRyan Moeller        if plain ~= true then
368*94cba803SRyan Moeller            view = template.load(view, plain)
369*94cba803SRyan Moeller            if byte(view, 1, 1) == ESC then return view end
370*94cba803SRyan Moeller        end
371*94cba803SRyan Moeller        local j = 2
372*94cba803SRyan Moeller        local c = {[[
373*94cba803SRyan Moellercontext=... or {}
374*94cba803SRyan Moellerlocal ___,blocks,layout={},blocks or {}
375*94cba803SRyan Moellerlocal function include(v, c) return template.process(v, c or context) end
376*94cba803SRyan Moellerlocal function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end
377*94cba803SRyan Moeller]] }
378*94cba803SRyan Moeller        local i, s = 1, find(view, "{", 1, true)
379*94cba803SRyan Moeller        while s do
380*94cba803SRyan Moeller            local t, p = byte(view, s + 1, s + 1), s + 2
381*94cba803SRyan Moeller            if t == LCUB then
382*94cba803SRyan Moeller                local e = find(view, "}}", p, true)
383*94cba803SRyan Moeller                if e then
384*94cba803SRyan Moeller                    local z, w = escaped(view, s)
385*94cba803SRyan Moeller                    if i < s - w then
386*94cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
387*94cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
388*94cba803SRyan Moeller                        c[j+2] = "]=]\n"
389*94cba803SRyan Moeller                        j=j+3
390*94cba803SRyan Moeller                    end
391*94cba803SRyan Moeller                    if z then
392*94cba803SRyan Moeller                        i = s
393*94cba803SRyan Moeller                    else
394*94cba803SRyan Moeller                        c[j] = "___[#___+1]=template.escape("
395*94cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{")
396*94cba803SRyan Moeller                        c[j+2] = ")\n"
397*94cba803SRyan Moeller                        j=j+3
398*94cba803SRyan Moeller                        s, i = e + 1, e + 2
399*94cba803SRyan Moeller                    end
400*94cba803SRyan Moeller                end
401*94cba803SRyan Moeller            elseif t == AST then
402*94cba803SRyan Moeller                local e = find(view, "*}", p, true)
403*94cba803SRyan Moeller                if e then
404*94cba803SRyan Moeller                    local z, w = escaped(view, s)
405*94cba803SRyan Moeller                    if i < s - w then
406*94cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
407*94cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
408*94cba803SRyan Moeller                        c[j+2] = "]=]\n"
409*94cba803SRyan Moeller                        j=j+3
410*94cba803SRyan Moeller                    end
411*94cba803SRyan Moeller                    if z then
412*94cba803SRyan Moeller                        i = s
413*94cba803SRyan Moeller                    else
414*94cba803SRyan Moeller                        c[j] = "___[#___+1]=template.output("
415*94cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*")
416*94cba803SRyan Moeller                        c[j+2] = ")\n"
417*94cba803SRyan Moeller                        j=j+3
418*94cba803SRyan Moeller                        s, i = e + 1, e + 2
419*94cba803SRyan Moeller                    end
420*94cba803SRyan Moeller                end
421*94cba803SRyan Moeller            elseif t == PERCNT then
422*94cba803SRyan Moeller                local e = find(view, "%}", p, true)
423*94cba803SRyan Moeller                if e then
424*94cba803SRyan Moeller                    local z, w = escaped(view, s)
425*94cba803SRyan Moeller                    if z then
426*94cba803SRyan Moeller                        if i < s - w then
427*94cba803SRyan Moeller                            c[j] = "___[#___+1]=[=[\n"
428*94cba803SRyan Moeller                            c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
429*94cba803SRyan Moeller                            c[j+2] = "]=]\n"
430*94cba803SRyan Moeller                            j=j+3
431*94cba803SRyan Moeller                        end
432*94cba803SRyan Moeller                        i = s
433*94cba803SRyan Moeller                    else
434*94cba803SRyan Moeller                        local n = e + 2
435*94cba803SRyan Moeller                        if byte(view, n, n) == LF then
436*94cba803SRyan Moeller                            n = n + 1
437*94cba803SRyan Moeller                        end
438*94cba803SRyan Moeller                        local r = rpos(view, s - 1)
439*94cba803SRyan Moeller                        if i <= r then
440*94cba803SRyan Moeller                            c[j] = "___[#___+1]=[=[\n"
441*94cba803SRyan Moeller                            c[j+1] = visit(visitors, sub(view, i, r))
442*94cba803SRyan Moeller                            c[j+2] = "]=]\n"
443*94cba803SRyan Moeller                            j=j+3
444*94cba803SRyan Moeller                        end
445*94cba803SRyan Moeller                        c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%")
446*94cba803SRyan Moeller                        c[j+1] = "\n"
447*94cba803SRyan Moeller                        j=j+2
448*94cba803SRyan Moeller                        s, i = n - 1, n
449*94cba803SRyan Moeller                    end
450*94cba803SRyan Moeller                end
451*94cba803SRyan Moeller            elseif t == LPAR then
452*94cba803SRyan Moeller                local e = find(view, ")}", p, true)
453*94cba803SRyan Moeller                if e then
454*94cba803SRyan Moeller                    local z, w = escaped(view, s)
455*94cba803SRyan Moeller                    if i < s - w then
456*94cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
457*94cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
458*94cba803SRyan Moeller                        c[j+2] = "]=]\n"
459*94cba803SRyan Moeller                        j=j+3
460*94cba803SRyan Moeller                    end
461*94cba803SRyan Moeller                    if z then
462*94cba803SRyan Moeller                        i = s
463*94cba803SRyan Moeller                    else
464*94cba803SRyan Moeller                        local f = visit(visitors, sub(view, p, e - 1), "(")
465*94cba803SRyan Moeller                        local x = find(f, ",", 2, true)
466*94cba803SRyan Moeller                        if x then
467*94cba803SRyan Moeller                            c[j] = "___[#___+1]=include([=["
468*94cba803SRyan Moeller                            c[j+1] = trim(sub(f, 1, x - 1))
469*94cba803SRyan Moeller                            c[j+2] = "]=],"
470*94cba803SRyan Moeller                            c[j+3] = trim(sub(f, x + 1))
471*94cba803SRyan Moeller                            c[j+4] = ")\n"
472*94cba803SRyan Moeller                            j=j+5
473*94cba803SRyan Moeller                        else
474*94cba803SRyan Moeller                            c[j] = "___[#___+1]=include([=["
475*94cba803SRyan Moeller                            c[j+1] = trim(f)
476*94cba803SRyan Moeller                            c[j+2] = "]=])\n"
477*94cba803SRyan Moeller                            j=j+3
478*94cba803SRyan Moeller                        end
479*94cba803SRyan Moeller                        s, i = e + 1, e + 2
480*94cba803SRyan Moeller                    end
481*94cba803SRyan Moeller                end
482*94cba803SRyan Moeller            elseif t == LSQB then
483*94cba803SRyan Moeller                local e = find(view, "]}", p, true)
484*94cba803SRyan Moeller                if e then
485*94cba803SRyan Moeller                    local z, w = escaped(view, s)
486*94cba803SRyan Moeller                    if i < s - w then
487*94cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
488*94cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
489*94cba803SRyan Moeller                        c[j+2] = "]=]\n"
490*94cba803SRyan Moeller                        j=j+3
491*94cba803SRyan Moeller                    end
492*94cba803SRyan Moeller                    if z then
493*94cba803SRyan Moeller                        i = s
494*94cba803SRyan Moeller                    else
495*94cba803SRyan Moeller                        c[j] = "___[#___+1]=include("
496*94cba803SRyan Moeller                        c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[")
497*94cba803SRyan Moeller                        c[j+2] = ")\n"
498*94cba803SRyan Moeller                        j=j+3
499*94cba803SRyan Moeller                        s, i = e + 1, e + 2
500*94cba803SRyan Moeller                    end
501*94cba803SRyan Moeller                end
502*94cba803SRyan Moeller            elseif t == MINUS then
503*94cba803SRyan Moeller                local e = find(view, "-}", p, true)
504*94cba803SRyan Moeller                if e then
505*94cba803SRyan Moeller                    local x, y = find(view, sub(view, s, e + 1), e + 2, true)
506*94cba803SRyan Moeller                    if x then
507*94cba803SRyan Moeller                        local z, w = escaped(view, s)
508*94cba803SRyan Moeller                        if z then
509*94cba803SRyan Moeller                            if i < s - w then
510*94cba803SRyan Moeller                                c[j] = "___[#___+1]=[=[\n"
511*94cba803SRyan Moeller                                c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
512*94cba803SRyan Moeller                                c[j+2] = "]=]\n"
513*94cba803SRyan Moeller                                j=j+3
514*94cba803SRyan Moeller                            end
515*94cba803SRyan Moeller                            i = s
516*94cba803SRyan Moeller                        else
517*94cba803SRyan Moeller                            y = y + 1
518*94cba803SRyan Moeller                            x = x - 1
519*94cba803SRyan Moeller                            if byte(view, y, y) == LF then
520*94cba803SRyan Moeller                                y = y + 1
521*94cba803SRyan Moeller                            end
522*94cba803SRyan Moeller                            local b = trim(sub(view, p, e - 1))
523*94cba803SRyan Moeller                            if b == "verbatim" or b == "raw" then
524*94cba803SRyan Moeller                                if i < s - w then
525*94cba803SRyan Moeller                                    c[j] = "___[#___+1]=[=[\n"
526*94cba803SRyan Moeller                                    c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
527*94cba803SRyan Moeller                                    c[j+2] = "]=]\n"
528*94cba803SRyan Moeller                                    j=j+3
529*94cba803SRyan Moeller                                end
530*94cba803SRyan Moeller                                c[j] = "___[#___+1]=[=["
531*94cba803SRyan Moeller                                c[j+1] = visit(visitors, sub(view, e + 2, x))
532*94cba803SRyan Moeller                                c[j+2] = "]=]\n"
533*94cba803SRyan Moeller                                j=j+3
534*94cba803SRyan Moeller                            else
535*94cba803SRyan Moeller                                if byte(view, x, x) == LF then
536*94cba803SRyan Moeller                                    x = x - 1
537*94cba803SRyan Moeller                                end
538*94cba803SRyan Moeller                                local r = rpos(view, s - 1)
539*94cba803SRyan Moeller                                if i <= r then
540*94cba803SRyan Moeller                                    c[j] = "___[#___+1]=[=[\n"
541*94cba803SRyan Moeller                                    c[j+1] = visit(visitors, sub(view, i, r))
542*94cba803SRyan Moeller                                    c[j+2] = "]=]\n"
543*94cba803SRyan Moeller                                    j=j+3
544*94cba803SRyan Moeller                                end
545*94cba803SRyan Moeller                                c[j] = 'blocks["'
546*94cba803SRyan Moeller                                c[j+1] = b
547*94cba803SRyan Moeller                                c[j+2] = '"]=include[=['
548*94cba803SRyan Moeller                                c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b)
549*94cba803SRyan Moeller                                c[j+4] = "]=]\n"
550*94cba803SRyan Moeller                                j=j+5
551*94cba803SRyan Moeller                            end
552*94cba803SRyan Moeller                            s, i = y - 1, y
553*94cba803SRyan Moeller                        end
554*94cba803SRyan Moeller                    end
555*94cba803SRyan Moeller                end
556*94cba803SRyan Moeller            elseif t == NUM then
557*94cba803SRyan Moeller                local e = find(view, "#}", p, true)
558*94cba803SRyan Moeller                if e then
559*94cba803SRyan Moeller                    local z, w = escaped(view, s)
560*94cba803SRyan Moeller                    if i < s - w then
561*94cba803SRyan Moeller                        c[j] = "___[#___+1]=[=[\n"
562*94cba803SRyan Moeller                        c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
563*94cba803SRyan Moeller                        c[j+2] = "]=]\n"
564*94cba803SRyan Moeller                        j=j+3
565*94cba803SRyan Moeller                    end
566*94cba803SRyan Moeller                    if z then
567*94cba803SRyan Moeller                        i = s
568*94cba803SRyan Moeller                    else
569*94cba803SRyan Moeller                        e = e + 2
570*94cba803SRyan Moeller                        if byte(view, e, e) == LF then
571*94cba803SRyan Moeller                            e = e + 1
572*94cba803SRyan Moeller                        end
573*94cba803SRyan Moeller                        s, i = e - 1, e
574*94cba803SRyan Moeller                    end
575*94cba803SRyan Moeller                end
576*94cba803SRyan Moeller            end
577*94cba803SRyan Moeller            s = find(view, "{", s + 1, true)
578*94cba803SRyan Moeller        end
579*94cba803SRyan Moeller        s = sub(view, i)
580*94cba803SRyan Moeller        if s and s ~= EMPTY then
581*94cba803SRyan Moeller            c[j] = "___[#___+1]=[=[\n"
582*94cba803SRyan Moeller            c[j+1] = visit(visitors, s)
583*94cba803SRyan Moeller            c[j+2] = "]=]\n"
584*94cba803SRyan Moeller            j=j+3
585*94cba803SRyan Moeller        end
586*94cba803SRyan Moeller        c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore
587*94cba803SRyan Moeller        return concat(c)
588*94cba803SRyan Moeller    end
589*94cba803SRyan Moeller
590*94cba803SRyan Moeller    function template.parse_file(view)
591*94cba803SRyan Moeller        return template.parse(view, false)
592*94cba803SRyan Moeller    end
593*94cba803SRyan Moeller
594*94cba803SRyan Moeller    function template.parse_string(view)
595*94cba803SRyan Moeller        return template.parse(view, true)
596*94cba803SRyan Moeller    end
597*94cba803SRyan Moeller
598*94cba803SRyan Moeller    function template.process(view, context, cache_key, plain)
599*94cba803SRyan Moeller        assert(view, "view was not provided for template.process(view, context, cache_key, plain)")
600*94cba803SRyan Moeller        return template.compile(view, cache_key, plain)(context)
601*94cba803SRyan Moeller    end
602*94cba803SRyan Moeller
603*94cba803SRyan Moeller    function template.process_file(view, context, cache_key)
604*94cba803SRyan Moeller        assert(view, "view was not provided for template.process_file(view, context, cache_key)")
605*94cba803SRyan Moeller        return template.compile(view, cache_key, false)(context)
606*94cba803SRyan Moeller    end
607*94cba803SRyan Moeller
608*94cba803SRyan Moeller    function template.process_string(view, context, cache_key)
609*94cba803SRyan Moeller        assert(view, "view was not provided for template.process_string(view, context, cache_key)")
610*94cba803SRyan Moeller        return template.compile(view, cache_key, true)(context)
611*94cba803SRyan Moeller    end
612*94cba803SRyan Moeller
613*94cba803SRyan Moeller    function template.render(view, context, cache_key, plain)
614*94cba803SRyan Moeller        assert(view, "view was not provided for template.render(view, context, cache_key, plain)")
615*94cba803SRyan Moeller        template.print(template.process(view, context, cache_key, plain))
616*94cba803SRyan Moeller    end
617*94cba803SRyan Moeller
618*94cba803SRyan Moeller    function template.render_file(view, context, cache_key)
619*94cba803SRyan Moeller        assert(view, "view was not provided for template.render_file(view, context, cache_key)")
620*94cba803SRyan Moeller        template.render(view, context, cache_key, false)
621*94cba803SRyan Moeller    end
622*94cba803SRyan Moeller
623*94cba803SRyan Moeller    function template.render_string(view, context, cache_key)
624*94cba803SRyan Moeller        assert(view, "view was not provided for template.render_string(view, context, cache_key)")
625*94cba803SRyan Moeller        template.render(view, context, cache_key, true)
626*94cba803SRyan Moeller    end
627*94cba803SRyan Moeller
628*94cba803SRyan Moeller    if safe then
629*94cba803SRyan Moeller        return setmetatable({}, {
630*94cba803SRyan Moeller            __index = function(_, k)
631*94cba803SRyan Moeller                if type(template[k]) == "function" then
632*94cba803SRyan Moeller                    return function(...)
633*94cba803SRyan Moeller                        local ok, a, b = pcall(template[k], ...)
634*94cba803SRyan Moeller                        if not ok then
635*94cba803SRyan Moeller                            return nil, a
636*94cba803SRyan Moeller                        end
637*94cba803SRyan Moeller                        return a, b
638*94cba803SRyan Moeller                    end
639*94cba803SRyan Moeller                end
640*94cba803SRyan Moeller                return template[k]
641*94cba803SRyan Moeller            end,
642*94cba803SRyan Moeller            __new_index = function(_, k, v)
643*94cba803SRyan Moeller                template[k] = v
644*94cba803SRyan Moeller            end,
645*94cba803SRyan Moeller        })
646*94cba803SRyan Moeller    end
647*94cba803SRyan Moeller
648*94cba803SRyan Moeller    return template
649*94cba803SRyan Moellerend
650*94cba803SRyan Moeller
651*94cba803SRyan Moellerreturn new()
652