Mercurial > prosody-hg
comparison util/interpolation.lua @ 6720:936cf2f7531f
util.interpolation: A template engine for text
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Tue, 19 May 2015 15:33:20 +0200 |
| parents | |
| children | 60957dd5b41b |
comparison
equal
deleted
inserted
replaced
| 6719:0ef7a8c8fe8c | 6720:936cf2f7531f |
|---|---|
| 1 -- Simple template language | |
| 2 -- | |
| 3 -- The new() function takes a pattern and an escape function and returns | |
| 4 -- a render() function. Both are required. | |
| 5 -- | |
| 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 | |
| 9 -- symbol. | |
| 10 -- | |
| 11 -- Variants are: | |
| 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. | |
| 21 | |
| 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; | |
| 26 | |
| 27 local function new_render(pat, escape) | |
| 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 | |
| 39 value = values; | |
| 40 for word in name:gmatch"[^.]+" do | |
| 41 value = value[word]; | |
| 42 if not value then break; end | |
| 43 end | |
| 44 end | |
| 45 if opt == '#' or opt == '%' then | |
| 46 if type(value) ~= "table" then return ""; end | |
| 47 local iter = opt == '#' and ipairs or pairs; | |
| 48 local out, i, subtpl = {}, 1, s_sub(block, e); | |
| 49 local subvalues = setmetatable({}, { __index = values }); | |
| 50 for idx, item in iter(value) do | |
| 51 subvalues.idx = idx; | |
| 52 subvalues.item = item; | |
| 53 out[i], i = render(subtpl, subvalues), i+1; | |
| 54 end | |
| 55 return t_concat(out); | |
| 56 elseif opt == '&' then | |
| 57 if not value then return ""; end | |
| 58 return render(s_sub(block, e), values); | |
| 59 elseif opt == '?' and not value then | |
| 60 return render(s_sub(block, e), values); | |
| 61 elseif value ~= nil then | |
| 62 if type(value) ~= "string" then | |
| 63 value = tostring(value); | |
| 64 end | |
| 65 if opt ~= '!' then | |
| 66 return escape(value); | |
| 67 end | |
| 68 return value; | |
| 69 end | |
| 70 end)); | |
| 71 end | |
| 72 return render; | |
| 73 end | |
| 74 | |
| 75 return { | |
| 76 new = new_render; | |
| 77 }; |
