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