1 -- From lua-resty-template (modified to remove external dependencies)
3 Copyright (c) 2014 - 2020 Aapo Talvensaari
6 Redistribution and use in source and binary forms, with or without modification,
7 are permitted provided that the following conditions are met:
9 * Redistributions of source code must retain the above copyright notice, this
10 list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright notice, this
13 list of conditions and the following disclaimer in the documentation and/or
14 other materials provided with the distribution.
16 * Neither the name of the {organization} nor the names of its
17 contributors may be used to endorse or promote products derived from
18 this software without specific prior written permission.
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 local setmetatable = setmetatable
34 local loadstring = loadstring
35 local tostring = tostring
36 local setfenv = setfenv
37 local require = require
38 local concat = table.concat
40 local write = io.write
46 local dump = string.dump
47 local find = string.find
48 local gsub = string.gsub
49 local byte = string.byte
51 local sub = string.sub
54 local _VERSION = _VERSION
55 local _ENV = _ENV -- luacheck: globals _ENV
58 local HTML_ENTITIES = {
67 local CODE_ENTITIES = {
80 local ESC = byte("\27")
81 local NUL = byte("\0")
86 local BSOL = byte("\\")
90 local LPAR = byte("(")
91 local LSQB = byte("[")
92 local LCUB = byte("{")
93 local MINUS = byte("-")
94 local PERCNT = byte("%")
99 if _VERSION == "Lua 5.1" then
100 VIEW_ENV = { __index = function(t, k)
101 return t.context[k] or t.template[k] or _G[k]
104 VIEW_ENV = { __index = function(t, k)
105 return t.context[k] or t.template[k] or _ENV[k]
112 ok, newtab = pcall(require, "table.new")
113 if not ok then newtab = function() return {} end end
116 local function enabled(val)
117 if val == nil then return true end
118 return val == true or (val == "1" or val == "true" or val == "on")
121 local function trim(s)
122 return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY)
125 local function rpos(view, s)
127 local c = byte(view, s, s)
128 if c == SP or c == HT or c == VT or c == NUL then
137 local function escaped(view, s)
138 if s > 1 and byte(view, s - 1, s - 1) == BSOL then
139 if s > 2 and byte(view, s - 2, s - 2) == BSOL then
148 local function read_file(path)
149 local file, err = open(path, "rb")
150 if not file then return nil, err end
152 content, err = file:read "*a"
157 local function load_view(template)
158 return function(view, plain)
159 if plain == true then return view end
160 local path, root = view, template.root
161 if root and root ~= EMPTY then
162 if byte(root, -1) == SOL then root = sub(root, 1, -2) end
163 if byte(view, 1) == SOL then path = sub(view, 2) end
164 path = root .. "/" .. path
166 return plain == false and assert(read_file(path)) or read_file(path) or view
170 local function load_file(func)
171 return function(view) return func(view, false) end
174 local function load_string(func)
175 return function(view) return func(view, true) end
178 local function loader(template)
179 return function(view)
180 return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV)))
184 local function visit(visitors, content, tag, name)
189 for i = 1, visitors.n do
190 content = visitors[i](content, tag, name)
196 local function new(template, safe)
197 template = template or newtab(0, 26)
199 template._VERSION = "2.0"
201 template.load = load_view(template)
202 template.load_file = load_file(template.load)
203 template.load_string = load_string(template.load)
204 template.print = write
206 local load_chunk = loader(template)
209 if VAR_PHASES and VAR_PHASES[phase()] then
210 caching = enabled(var.template_cache)
216 function template.visit(func)
218 visitors = { func, n = 1 }
221 visitors.n = visitors.n + 1
222 visitors[visitors.n] = func
225 function template.caching(enable)
226 if enable ~= nil then caching = enable == true end
230 function template.output(s)
231 if s == nil or s == null then return EMPTY end
232 if type(s) == "function" then return template.output(s()) end
236 function template.escape(s, c)
237 if type(s) == "string" then
238 if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
239 return gsub(s, "[\">/<'&]", HTML_ENTITIES)
241 return template.output(s)
244 function template.new(view, layout)
245 local vt = type(view)
247 if vt == "boolean" then return new(nil, view) end
248 if vt == "table" then return new(view, safe) end
249 if vt == "nil" then return new(nil, safe) end
254 if type(layout) == "table" then
255 render = function(self, context)
256 context = context or self
257 context.blocks = context.blocks or {}
258 context.view = template.process(view, context)
259 layout.blocks = context.blocks or {}
260 layout.view = context.view or EMPTY
263 process = function(self, context)
264 context = context or self
265 context.blocks = context.blocks or {}
266 context.view = template.process(view, context)
267 layout.blocks = context.blocks or {}
268 layout.view = context.view
269 return tostring(layout)
272 render = function(self, context)
273 context = context or self
274 context.blocks = context.blocks or {}
275 context.view = template.process(view, context)
276 template.render(layout, context)
278 process = function(self, context)
279 context = context or self
280 context.blocks = context.blocks or {}
281 context.view = template.process(view, context)
282 return template.process(layout, context)
286 render = function(self, context)
287 return template.render(view, context or self)
289 process = function(self, context)
290 return template.process(view, context or self)
295 return setmetatable({
296 render = function(...)
297 local ok, err = pcall(render, ...)
302 process = function(...)
303 local ok, output = pcall(process, ...)
310 __tostring = function(...)
311 local ok, output = pcall(process, ...)
319 return setmetatable({
327 function template.precompile(view, path, strip, plain)
328 local chunk = dump(template.compile(view, nil, plain), strip ~= false)
330 local file = open(path, "wb")
337 function template.precompile_string(view, path, strip)
338 return template.precompile(view, path, strip, true)
341 function template.precompile_file(view, path, strip)
342 return template.precompile(view, path, strip, false)
345 function template.compile(view, cache_key, plain)
346 assert(view, "view was not provided for template.compile(view, cache_key, plain)")
347 if cache_key == "no-cache" then
348 return load_chunk(template.parse(view, plain)), false
350 cache_key = cache_key or view
351 local cache = template.cache
352 if cache[cache_key] then return cache[cache_key], true end
353 local func = load_chunk(template.parse(view, plain))
354 if caching then cache[cache_key] = func end
358 function template.compile_file(view, cache_key)
359 return template.compile(view, cache_key, false)
362 function template.compile_string(view, cache_key)
363 return template.compile(view, cache_key, true)
366 function template.parse(view, plain)
367 assert(view, "view was not provided for template.parse(view, plain)")
368 if plain ~= true then
369 view = template.load(view, plain)
370 if byte(view, 1, 1) == ESC then return view end
375 local ___,blocks,layout={},blocks or {}
376 local function include(v, c) return template.process(v, c or context) end
377 local function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end
379 local i, s = 1, find(view, "{", 1, true)
381 local t, p = byte(view, s + 1, s + 1), s + 2
383 local e = find(view, "}}", p, true)
385 local z, w = escaped(view, s)
387 c[j] = "___[#___+1]=[=[\n"
388 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
395 c[j] = "___[#___+1]=template.escape("
396 c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{")
403 local e = find(view, "*}", p, true)
405 local z, w = escaped(view, s)
407 c[j] = "___[#___+1]=[=[\n"
408 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
415 c[j] = "___[#___+1]=template.output("
416 c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*")
422 elseif t == PERCNT then
423 local e = find(view, "%}", p, true)
425 local z, w = escaped(view, s)
428 c[j] = "___[#___+1]=[=[\n"
429 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
436 if byte(view, n, n) == LF then
439 local r = rpos(view, s - 1)
441 c[j] = "___[#___+1]=[=[\n"
442 c[j+1] = visit(visitors, sub(view, i, r))
446 c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%")
452 elseif t == LPAR then
453 local e = find(view, ")}", p, true)
455 local z, w = escaped(view, s)
457 c[j] = "___[#___+1]=[=[\n"
458 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
465 local f = visit(visitors, sub(view, p, e - 1), "(")
466 local x = find(f, ",", 2, true)
468 c[j] = "___[#___+1]=include([=["
469 c[j+1] = trim(sub(f, 1, x - 1))
471 c[j+3] = trim(sub(f, x + 1))
475 c[j] = "___[#___+1]=include([=["
483 elseif t == LSQB then
484 local e = find(view, "]}", p, true)
486 local z, w = escaped(view, s)
488 c[j] = "___[#___+1]=[=[\n"
489 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
496 c[j] = "___[#___+1]=include("
497 c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[")
503 elseif t == MINUS then
504 local e = find(view, "-}", p, true)
506 local x, y = find(view, sub(view, s, e + 1), e + 2, true)
508 local z, w = escaped(view, s)
511 c[j] = "___[#___+1]=[=[\n"
512 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
520 if byte(view, y, y) == LF then
523 local b = trim(sub(view, p, e - 1))
524 if b == "verbatim" or b == "raw" then
526 c[j] = "___[#___+1]=[=[\n"
527 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
531 c[j] = "___[#___+1]=[=["
532 c[j+1] = visit(visitors, sub(view, e + 2, x))
536 if byte(view, x, x) == LF then
539 local r = rpos(view, s - 1)
541 c[j] = "___[#___+1]=[=[\n"
542 c[j+1] = visit(visitors, sub(view, i, r))
548 c[j+2] = '"]=include[=['
549 c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b)
558 local e = find(view, "#}", p, true)
560 local z, w = escaped(view, s)
562 c[j] = "___[#___+1]=[=[\n"
563 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
571 if byte(view, e, e) == LF then
578 s = find(view, "{", s + 1, true)
581 if s and s ~= EMPTY then
582 c[j] = "___[#___+1]=[=[\n"
583 c[j+1] = visit(visitors, s)
587 c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore
591 function template.parse_file(view)
592 return template.parse(view, false)
595 function template.parse_string(view)
596 return template.parse(view, true)
599 function template.process(view, context, cache_key, plain)
600 assert(view, "view was not provided for template.process(view, context, cache_key, plain)")
601 return template.compile(view, cache_key, plain)(context)
604 function template.process_file(view, context, cache_key)
605 assert(view, "view was not provided for template.process_file(view, context, cache_key)")
606 return template.compile(view, cache_key, false)(context)
609 function template.process_string(view, context, cache_key)
610 assert(view, "view was not provided for template.process_string(view, context, cache_key)")
611 return template.compile(view, cache_key, true)(context)
614 function template.render(view, context, cache_key, plain)
615 assert(view, "view was not provided for template.render(view, context, cache_key, plain)")
616 template.print(template.process(view, context, cache_key, plain))
619 function template.render_file(view, context, cache_key)
620 assert(view, "view was not provided for template.render_file(view, context, cache_key)")
621 template.render(view, context, cache_key, false)
624 function template.render_string(view, context, cache_key)
625 assert(view, "view was not provided for template.render_string(view, context, cache_key)")
626 template.render(view, context, cache_key, true)
630 return setmetatable({}, {
631 __index = function(_, k)
632 if type(template[k]) == "function" then
634 local ok, a, b = pcall(template[k], ...)
643 __new_index = function(_, k, v)