1 -- Simple template language
3 -- The new() function takes a pattern and an escape function and returns
4 -- a render() function. Both are required.
6 -- The function render() takes a string template and a table of values.
7 -- Sequences like {name} in the template string are substituted
8 -- with values from the table, optionally depending on a modifier
12 -- {name} is substituted for values["name"] and is escaped using the
13 -- second argument to new_render(). To disable the escaping, use {name!}.
14 -- {name.item} can be used to access table items.
15 -- To renter lists of items: {name# item number {idx} is {item} }
16 -- Or key-value pairs: {name% t[ {idx} ] = {item} }
17 -- To show a defaults for missing values {name? sub-template } can be used,
18 -- which renders a sub-template if values["name"] is false-ish.
19 -- {name& sub-template } does the opposite, the sub-template is rendered
20 -- if the selected value is anything but false or nil.
22 local type, tostring = type, tostring;
23 local pairs
, ipairs
= pairs
, ipairs
;
24 local s_sub
, s_gsub
, s_match
= string.sub
, string.gsub, string.match
;
25 local t_concat
= table.concat
;
27 local function new_render(pat
, escape
, funcs
)
28 -- assert(type(pat) == "string", "bad argument #1 to 'new_render' (string expected)");
29 -- assert(type(escape) == "function", "bad argument #2 to 'new_render' (function expected)");
30 local function render(template
, values
)
31 -- assert(type(template) == "string", "bad argument #1 to 'render' (string expected)");
32 -- assert(type(values) == "table", "bad argument #2 to 'render' (table expected)");
33 return (s_gsub(template
, pat
, function (block
)
34 block
= s_sub(block
, 2, -2);
35 local name
, opt
, e
= s_match(block
, "^([%a_][%w_.]*)(%p?)()");
36 if not name
then return end
37 local value
= values
[name
];
38 if not value
and name
:find(".", 2, true) then
40 for word
in name
:gmatch
"[^.]+" do
42 if not value
then break; end
46 while value
~= nil and opt
== '|' do
48 f
, opt
, e
= s_match(block
, "^([%a_][%w_.]*)(%p?)()", e
);
50 if f
then value
= f(value
); end
53 if opt
== '#' or opt
== '%' then
54 if type(value
) ~= "table" then return ""; end
55 local iter
= opt
== '#' and ipairs
or pairs
;
56 local out
, i
, subtpl
= {}, 1, s_sub(block
, e
);
57 local subvalues
= setmetatable({}, { __index
= values
});
58 for idx
, item
in iter(value
) do
60 subvalues
.item
= item
;
61 out
[i
], i
= render(subtpl
, subvalues
), i
+1;
64 elseif opt
== '&' then
65 if not value
then return ""; end
66 return render(s_sub(block
, e
), values
);
67 elseif opt
== '?' and not value
then
68 return render(s_sub(block
, e
), values
);
69 elseif value
~= nil then
70 if type(value
) ~= "string" then
71 value
= tostring(value
);