]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/lua/template.lua
Optionally bind ktls threads to NUMA domains
[FreeBSD/FreeBSD.git] / tools / lua / template.lua
1 -- From lua-resty-template (modified to remove external dependencies)
2 --[[
3 Copyright (c) 2014 - 2020 Aapo Talvensaari
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without modification,
7 are permitted provided that the following conditions are met:
8
9 * Redistributions of source code must retain the above copyright notice, this
10   list of conditions and the following disclaimer.
11
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.
15
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.
19
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.
30 ]]--
31 -- $FreeBSD$
32
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
39 local assert = assert
40 local write = io.write
41 local pcall = pcall
42 local phase
43 local open = io.open
44 local load = load
45 local type = type
46 local dump = string.dump
47 local find = string.find
48 local gsub = string.gsub
49 local byte = string.byte
50 local null
51 local sub = string.sub
52 local var
53
54 local _VERSION = _VERSION
55 local _ENV = _ENV -- luacheck: globals _ENV
56 local _G = _G
57
58 local HTML_ENTITIES = {
59     ["&"] = "&",
60     ["<"] = "&lt;",
61     [">"] = "&gt;",
62     ['"'] = "&quot;",
63     ["'"] = "&#39;",
64     ["/"] = "&#47;"
65 }
66
67 local CODE_ENTITIES = {
68     ["{"] = "&#123;",
69     ["}"] = "&#125;",
70     ["&"] = "&amp;",
71     ["<"] = "&lt;",
72     [">"] = "&gt;",
73     ['"'] = "&quot;",
74     ["'"] = "&#39;",
75     ["/"] = "&#47;"
76 }
77
78 local VAR_PHASES
79
80 local ESC    = byte("\27")
81 local NUL    = byte("\0")
82 local HT     = byte("\t")
83 local VT     = byte("\v")
84 local LF     = byte("\n")
85 local SOL    = byte("/")
86 local BSOL   = byte("\\")
87 local SP     = byte(" ")
88 local AST    = byte("*")
89 local NUM    = byte("#")
90 local LPAR   = byte("(")
91 local LSQB   = byte("[")
92 local LCUB   = byte("{")
93 local MINUS  = byte("-")
94 local PERCNT = byte("%")
95
96 local EMPTY  = ""
97
98 local VIEW_ENV
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]
102     end }
103 else
104     VIEW_ENV = { __index = function(t, k)
105         return t.context[k] or t.template[k] or _ENV[k]
106     end }
107 end
108
109 local newtab
110 do
111     local ok
112     ok, newtab = pcall(require, "table.new")
113     if not ok then newtab = function() return {} end end
114 end
115
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")
119 end
120
121 local function trim(s)
122     return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY)
123 end
124
125 local function rpos(view, s)
126     while s > 0 do
127         local c = byte(view, s, s)
128         if c == SP or c == HT or c == VT or c == NUL then
129             s = s - 1
130         else
131             break
132         end
133     end
134     return s
135 end
136
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
140             return false, 1
141         else
142             return true, 1
143         end
144     end
145     return false, 0
146 end
147
148 local function read_file(path)
149     local file, err = open(path, "rb")
150     if not file then return nil, err end
151     local content
152     content, err = file:read "*a"
153     file:close()
154     return content, err
155 end
156
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
165         end
166         return plain == false and assert(read_file(path)) or read_file(path) or view
167     end
168 end
169
170 local function load_file(func)
171     return function(view) return func(view, false) end
172 end
173
174 local function load_string(func)
175     return function(view) return func(view, true) end
176 end
177
178 local function loader(template)
179     return function(view)
180         return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV)))
181     end
182 end
183
184 local function visit(visitors, content, tag, name)
185     if not visitors then
186         return content
187     end
188
189     for i = 1, visitors.n do
190         content = visitors[i](content, tag, name)
191     end
192
193     return content
194 end
195
196 local function new(template, safe)
197     template = template or newtab(0, 26)
198
199     template._VERSION    = "2.0"
200     template.cache       = {}
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
205
206     local load_chunk = loader(template)
207
208     local caching
209     if VAR_PHASES and VAR_PHASES[phase()] then
210         caching = enabled(var.template_cache)
211     else
212         caching = true
213     end
214
215     local visitors
216     function template.visit(func)
217         if not visitors then
218             visitors = { func, n = 1 }
219             return
220         end
221         visitors.n = visitors.n + 1
222         visitors[visitors.n] = func
223     end
224
225     function template.caching(enable)
226         if enable ~= nil then caching = enable == true end
227         return caching
228     end
229
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
233         return tostring(s)
234     end
235
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)
240         end
241         return template.output(s)
242     end
243
244     function template.new(view, layout)
245         local vt = type(view)
246
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
250
251         local render
252         local process
253         if layout then
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
261                     layout:render()
262                 end
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)
270                 end
271             else
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)
277                 end
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)
283                 end
284             end
285         else
286             render = function(self, context)
287                 return template.render(view, context or self)
288             end
289             process = function(self, context)
290                 return template.process(view, context or self)
291             end
292         end
293
294         if safe then
295             return setmetatable({
296                 render = function(...)
297                     local ok, err = pcall(render, ...)
298                     if not ok then
299                         return nil, err
300                     end
301                 end,
302                 process = function(...)
303                     local ok, output = pcall(process, ...)
304                     if not ok then
305                         return nil, output
306                     end
307                     return output
308                 end,
309              }, {
310                 __tostring = function(...)
311                     local ok, output = pcall(process, ...)
312                     if not ok then
313                         return ""
314                     end
315                     return output
316             end })
317         end
318
319         return setmetatable({
320             render = render,
321             process = process
322         }, {
323             __tostring = process
324         })
325     end
326
327     function template.precompile(view, path, strip, plain)
328         local chunk = dump(template.compile(view, nil, plain), strip ~= false)
329         if path then
330             local file = open(path, "wb")
331             file:write(chunk)
332             file:close()
333         end
334         return chunk
335     end
336
337     function template.precompile_string(view, path, strip)
338         return template.precompile(view, path, strip, true)
339     end
340
341     function template.precompile_file(view, path, strip)
342         return template.precompile(view, path, strip, false)
343     end
344
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
349         end
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
355         return func, false
356     end
357
358     function template.compile_file(view, cache_key)
359         return template.compile(view, cache_key, false)
360     end
361
362     function template.compile_string(view, cache_key)
363         return template.compile(view, cache_key, true)
364     end
365
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
371         end
372         local j = 2
373         local c = {[[
374 context=... or {}
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
378 ]] }
379         local i, s = 1, find(view, "{", 1, true)
380         while s do
381             local t, p = byte(view, s + 1, s + 1), s + 2
382             if t == LCUB then
383                 local e = find(view, "}}", p, true)
384                 if e then
385                     local z, w = escaped(view, s)
386                     if i < s - w then
387                         c[j] = "___[#___+1]=[=[\n"
388                         c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
389                         c[j+2] = "]=]\n"
390                         j=j+3
391                     end
392                     if z then
393                         i = s
394                     else
395                         c[j] = "___[#___+1]=template.escape("
396                         c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{")
397                         c[j+2] = ")\n"
398                         j=j+3
399                         s, i = e + 1, e + 2
400                     end
401                 end
402             elseif t == AST then
403                 local e = find(view, "*}", p, true)
404                 if e then
405                     local z, w = escaped(view, s)
406                     if i < s - w then
407                         c[j] = "___[#___+1]=[=[\n"
408                         c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
409                         c[j+2] = "]=]\n"
410                         j=j+3
411                     end
412                     if z then
413                         i = s
414                     else
415                         c[j] = "___[#___+1]=template.output("
416                         c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*")
417                         c[j+2] = ")\n"
418                         j=j+3
419                         s, i = e + 1, e + 2
420                     end
421                 end
422             elseif t == PERCNT then
423                 local e = find(view, "%}", p, true)
424                 if e then
425                     local z, w = escaped(view, s)
426                     if z then
427                         if i < s - w then
428                             c[j] = "___[#___+1]=[=[\n"
429                             c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
430                             c[j+2] = "]=]\n"
431                             j=j+3
432                         end
433                         i = s
434                     else
435                         local n = e + 2
436                         if byte(view, n, n) == LF then
437                             n = n + 1
438                         end
439                         local r = rpos(view, s - 1)
440                         if i <= r then
441                             c[j] = "___[#___+1]=[=[\n"
442                             c[j+1] = visit(visitors, sub(view, i, r))
443                             c[j+2] = "]=]\n"
444                             j=j+3
445                         end
446                         c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%")
447                         c[j+1] = "\n"
448                         j=j+2
449                         s, i = n - 1, n
450                     end
451                 end
452             elseif t == LPAR then
453                 local e = find(view, ")}", p, true)
454                 if e then
455                     local z, w = escaped(view, s)
456                     if i < s - w then
457                         c[j] = "___[#___+1]=[=[\n"
458                         c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
459                         c[j+2] = "]=]\n"
460                         j=j+3
461                     end
462                     if z then
463                         i = s
464                     else
465                         local f = visit(visitors, sub(view, p, e - 1), "(")
466                         local x = find(f, ",", 2, true)
467                         if x then
468                             c[j] = "___[#___+1]=include([=["
469                             c[j+1] = trim(sub(f, 1, x - 1))
470                             c[j+2] = "]=],"
471                             c[j+3] = trim(sub(f, x + 1))
472                             c[j+4] = ")\n"
473                             j=j+5
474                         else
475                             c[j] = "___[#___+1]=include([=["
476                             c[j+1] = trim(f)
477                             c[j+2] = "]=])\n"
478                             j=j+3
479                         end
480                         s, i = e + 1, e + 2
481                     end
482                 end
483             elseif t == LSQB then
484                 local e = find(view, "]}", p, true)
485                 if e then
486                     local z, w = escaped(view, s)
487                     if i < s - w then
488                         c[j] = "___[#___+1]=[=[\n"
489                         c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
490                         c[j+2] = "]=]\n"
491                         j=j+3
492                     end
493                     if z then
494                         i = s
495                     else
496                         c[j] = "___[#___+1]=include("
497                         c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[")
498                         c[j+2] = ")\n"
499                         j=j+3
500                         s, i = e + 1, e + 2
501                     end
502                 end
503             elseif t == MINUS then
504                 local e = find(view, "-}", p, true)
505                 if e then
506                     local x, y = find(view, sub(view, s, e + 1), e + 2, true)
507                     if x then
508                         local z, w = escaped(view, s)
509                         if z then
510                             if i < s - w then
511                                 c[j] = "___[#___+1]=[=[\n"
512                                 c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
513                                 c[j+2] = "]=]\n"
514                                 j=j+3
515                             end
516                             i = s
517                         else
518                             y = y + 1
519                             x = x - 1
520                             if byte(view, y, y) == LF then
521                                 y = y + 1
522                             end
523                             local b = trim(sub(view, p, e - 1))
524                             if b == "verbatim" or b == "raw" then
525                                 if i < s - w then
526                                     c[j] = "___[#___+1]=[=[\n"
527                                     c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
528                                     c[j+2] = "]=]\n"
529                                     j=j+3
530                                 end
531                                 c[j] = "___[#___+1]=[=["
532                                 c[j+1] = visit(visitors, sub(view, e + 2, x))
533                                 c[j+2] = "]=]\n"
534                                 j=j+3
535                             else
536                                 if byte(view, x, x) == LF then
537                                     x = x - 1
538                                 end
539                                 local r = rpos(view, s - 1)
540                                 if i <= r then
541                                     c[j] = "___[#___+1]=[=[\n"
542                                     c[j+1] = visit(visitors, sub(view, i, r))
543                                     c[j+2] = "]=]\n"
544                                     j=j+3
545                                 end
546                                 c[j] = 'blocks["'
547                                 c[j+1] = b
548                                 c[j+2] = '"]=include[=['
549                                 c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b)
550                                 c[j+4] = "]=]\n"
551                                 j=j+5
552                             end
553                             s, i = y - 1, y
554                         end
555                     end
556                 end
557             elseif t == NUM then
558                 local e = find(view, "#}", p, true)
559                 if e then
560                     local z, w = escaped(view, s)
561                     if i < s - w then
562                         c[j] = "___[#___+1]=[=[\n"
563                         c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
564                         c[j+2] = "]=]\n"
565                         j=j+3
566                     end
567                     if z then
568                         i = s
569                     else
570                         e = e + 2
571                         if byte(view, e, e) == LF then
572                             e = e + 1
573                         end
574                         s, i = e - 1, e
575                     end
576                 end
577             end
578             s = find(view, "{", s + 1, true)
579         end
580         s = sub(view, i)
581         if s and s ~= EMPTY then
582             c[j] = "___[#___+1]=[=[\n"
583             c[j+1] = visit(visitors, s)
584             c[j+2] = "]=]\n"
585             j=j+3
586         end
587         c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore
588         return concat(c)
589     end
590
591     function template.parse_file(view)
592         return template.parse(view, false)
593     end
594
595     function template.parse_string(view)
596         return template.parse(view, true)
597     end
598
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)
602     end
603
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)
607     end
608
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)
612     end
613
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))
617     end
618
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)
622     end
623
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)
627     end
628
629     if safe then
630         return setmetatable({}, {
631             __index = function(_, k)
632                 if type(template[k]) == "function" then
633                     return function(...)
634                         local ok, a, b = pcall(template[k], ...)
635                         if not ok then
636                             return nil, a
637                         end
638                         return a, b
639                     end
640                 end
641                 return template[k]
642             end,
643             __new_index = function(_, k, v)
644                 template[k] = v
645             end,
646         })
647     end
648
649     return template
650 end
651
652 return new()