|
|
if not modules then modules = { } end modules ['util-tpl'] = {
|
|
|
version = 1.001,
|
|
|
comment = "companion to luat-lib.mkiv",
|
|
|
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
|
copyright = "PRAGMA ADE / ConTeXt Development Team",
|
|
|
license = "see context related readme files"
|
|
|
}
|
|
|
|
|
|
-- This is experimental code. Coming from dos and windows, I've always used %whatever%
|
|
|
-- as template variables so let's stick to it. After all, it's easy to parse and stands
|
|
|
-- out well. A double %% is turned into a regular %.
|
|
|
|
|
|
utilities.templates = utilities.templates or { }
|
|
|
local templates = utilities.templates
|
|
|
|
|
|
local trace_template = false trackers.register("templates.trace",function(v) trace_template = v end)
|
|
|
local report_template = logs.reporter("template")
|
|
|
|
|
|
local tostring, next = tostring, next
|
|
|
local format, sub, byte = string.format, string.sub, string.byte
|
|
|
local P, C, R, Cs, Cc, Carg, lpegmatch, lpegpatterns = lpeg.P, lpeg.C, lpeg.R, lpeg.Cs, lpeg.Cc, lpeg.Carg, lpeg.match, lpeg.patterns
|
|
|
|
|
|
local formatters = string.formatters
|
|
|
|
|
|
-- todo: make installable template.new
|
|
|
|
|
|
local replacer
|
|
|
|
|
|
local function replacekey(k,t,how,recursive)
|
|
|
local v = t[k]
|
|
|
if not v then
|
|
|
if trace_template then
|
|
|
report_template("unknown key %a",k)
|
|
|
end
|
|
|
return ""
|
|
|
else
|
|
|
v = tostring(v)
|
|
|
if trace_template then
|
|
|
report_template("setting key %a to value %a",k,v)
|
|
|
end
|
|
|
if recursive then
|
|
|
return lpegmatch(replacer,v,1,t,how,recursive)
|
|
|
else
|
|
|
return v
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
local sqlescape = lpeg.replacer {
|
|
|
{ "'", "''" },
|
|
|
{ "\\", "\\\\" },
|
|
|
{ "\r\n", "\\n" },
|
|
|
{ "\r", "\\n" },
|
|
|
-- { "\t", "\\t" },
|
|
|
}
|
|
|
|
|
|
local sqlquoted = Cs(Cc("'") * sqlescape * Cc("'"))
|
|
|
|
|
|
lpegpatterns.sqlescape = sqlescape
|
|
|
lpegpatterns.sqlquoted = sqlquoted
|
|
|
|
|
|
-- escapeset : \0\1\2\3\4\5\6\7\8\9\10\11\12\13\14\15\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\"\\\127
|
|
|
-- test string: [[1\0\31test23"\\]] .. string.char(19) .. "23"
|
|
|
--
|
|
|
-- slow:
|
|
|
--
|
|
|
-- local luaescape = lpeg.replacer {
|
|
|
-- { '"', [[\"]] },
|
|
|
-- { '\\', [[\\]] },
|
|
|
-- { R("\0\9") * #R("09"), function(s) return "\\00" .. byte(s) end },
|
|
|
-- { R("\10\31") * #R("09"), function(s) return "\\0" .. byte(s) end },
|
|
|
-- { R("\0\31") , function(s) return "\\" .. byte(s) end },
|
|
|
-- }
|
|
|
--
|
|
|
-- slightly faster:
|
|
|
|
|
|
-- local luaescape = Cs ((
|
|
|
-- P('"' ) / [[\"]] +
|
|
|
-- P('\\') / [[\\]] +
|
|
|
-- Cc("\\00") * (R("\0\9") / byte) * #R("09") +
|
|
|
-- Cc("\\0") * (R("\10\31") / byte) * #R("09") +
|
|
|
-- Cc("\\") * (R("\0\31") / byte) +
|
|
|
-- P(1)
|
|
|
-- )^0)
|
|
|
|
|
|
----- xmlescape = lpegpatterns.xmlescape
|
|
|
----- texescape = lpegpatterns.texescape
|
|
|
local luaescape = lpegpatterns.luaescape
|
|
|
----- sqlquoted = lpegpatterns.sqlquoted
|
|
|
----- luaquoted = lpegpatterns.luaquoted
|
|
|
|
|
|
local escapers = {
|
|
|
lua = function(s)
|
|
|
-- return sub(format("%q",s),2,-2)
|
|
|
return lpegmatch(luaescape,s)
|
|
|
end,
|
|
|
sql = function(s)
|
|
|
return lpegmatch(sqlescape,s)
|
|
|
end,
|
|
|
}
|
|
|
|
|
|
local quotedescapers = {
|
|
|
lua = function(s)
|
|
|
-- return lpegmatch(luaquoted,s)
|
|
|
return format("%q",s)
|
|
|
end,
|
|
|
sql = function(s)
|
|
|
return lpegmatch(sqlquoted,s)
|
|
|
end,
|
|
|
}
|
|
|
|
|
|
local luaescaper = escapers.lua
|
|
|
local quotedluaescaper = quotedescapers.lua
|
|
|
|
|
|
local function replacekeyunquoted(s,t,how,recurse) -- ".. \" "
|
|
|
if how == false then
|
|
|
return replacekey(s,t,how,recurse)
|
|
|
else
|
|
|
local escaper = how and escapers[how] or luaescaper
|
|
|
return escaper(replacekey(s,t,how,recurse))
|
|
|
end
|
|
|
end
|
|
|
|
|
|
local function replacekeyquoted(s,t,how,recurse) -- ".. \" "
|
|
|
if how == false then
|
|
|
return replacekey(s,t,how,recurse)
|
|
|
else
|
|
|
local escaper = how and quotedescapers[how] or quotedluaescaper
|
|
|
return escaper(replacekey(s,t,how,recurse))
|
|
|
end
|
|
|
end
|
|
|
|
|
|
local function replaceoptional(l,m,r,t,how,recurse)
|
|
|
local v = t[l]
|
|
|
return v and v ~= "" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or ""
|
|
|
end
|
|
|
|
|
|
local function replaceformatted(l,m,r,t,how,recurse)
|
|
|
local v = t[r]
|
|
|
return v and formatters[l](v)
|
|
|
end
|
|
|
|
|
|
local single = P("%") -- test %test% test : resolves test
|
|
|
local double = P("%%") -- test 10%% test : %% becomes %
|
|
|
local lquoted = P("%[") -- test '%[test]%' test : resolves to test with escaped "'s
|
|
|
local rquoted = P("]%") --
|
|
|
local lquotedq = P("%(") -- test %(test)% test : resolves to 'test' with escaped "'s
|
|
|
local rquotedq = P(")%") --
|
|
|
|
|
|
local escape = double / '%%'
|
|
|
local nosingle = single / ''
|
|
|
local nodouble = double / ''
|
|
|
local nolquoted = lquoted / ''
|
|
|
local norquoted = rquoted / ''
|
|
|
local nolquotedq = lquotedq / ''
|
|
|
local norquotedq = rquotedq / ''
|
|
|
|
|
|
local nolformatted = P(":") / "%%"
|
|
|
local norformatted = P(":") / ""
|
|
|
|
|
|
local noloptional = P("%?") / ''
|
|
|
local noroptional = P("?%") / ''
|
|
|
local nomoptional = P(":") / ''
|
|
|
|
|
|
local args = Carg(1) * Carg(2) * Carg(3)
|
|
|
local key = nosingle * ((C((1-nosingle)^1) * args) / replacekey) * nosingle
|
|
|
local quoted = nolquotedq * ((C((1-norquotedq)^1) * args) / replacekeyquoted) * norquotedq
|
|
|
local unquoted = nolquoted * ((C((1-norquoted)^1) * args) / replacekeyunquoted) * norquoted
|
|
|
local optional = noloptional * ((C((1-nomoptional)^1) * nomoptional * C((1-noroptional)^1) * args) / replaceoptional) * noroptional
|
|
|
local formatted = nosingle * ((Cs(nolformatted * (1-norformatted )^1) * norformatted * C((1-nosingle)^1) * args) / replaceformatted) * nosingle
|
|
|
local any = P(1)
|
|
|
|
|
|
replacer = Cs((unquoted + quoted + formatted + escape + optional + key + any)^0)
|
|
|
|
|
|
local function replace(str,mapping,how,recurse)
|
|
|
if mapping and str then
|
|
|
return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str
|
|
|
else
|
|
|
return str
|
|
|
end
|
|
|
end
|
|
|
|
|
|
-- print(replace("test '%[x]%' test",{ x = [[a 'x' a]] }))
|
|
|
-- print(replace("test '%x%' test",{ x = [[a "x" a]] }))
|
|
|
-- print(replace([[test "%[x]%" test]],{ x = [[a "x" a]] }))
|
|
|
-- print(replace("test '%[x]%' test",{ x = true }))
|
|
|
-- print(replace("test '%[x]%' test",{ x = [[a 'x' a]], y = "oeps" },'sql'))
|
|
|
-- print(replace("test '%[x]%' test",{ x = [[a '%y%' a]], y = "oeps" },'sql',true))
|
|
|
-- print(replace([[test %[x]% test]],{ x = [[a "x" a]]}))
|
|
|
-- print(replace([[test %(x)% test]],{ x = [[a "x" a]]}))
|
|
|
-- print(replace([[convert %?x: -x "%x%" ?% %?y: -y "%y%" ?%]],{ x = "yes" }))
|
|
|
-- print(replace("test %:0.3N:x% test",{ x = 123.45 }))
|
|
|
-- print(replace("test %:0.3N:x% test",{ x = 12345 }))
|
|
|
|
|
|
templates.replace = replace
|
|
|
|
|
|
function templates.replacer(str,how,recurse) -- reads nicer
|
|
|
return function(mapping)
|
|
|
return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str
|
|
|
end
|
|
|
end
|
|
|
|
|
|
-- local cmd = templates.replacer([[foo %bar%]]) print(cmd { bar = "foo" })
|
|
|
|
|
|
function templates.load(filename,mapping,how,recurse)
|
|
|
local data = io.loaddata(filename) or ""
|
|
|
if mapping and next(mapping) then
|
|
|
return replace(data,mapping,how,recurse)
|
|
|
else
|
|
|
return data
|
|
|
end
|
|
|
end
|
|
|
|
|
|
function templates.resolve(t,mapping,how,recurse)
|
|
|
if not mapping then
|
|
|
mapping = t
|
|
|
end
|
|
|
for k, v in next, t do
|
|
|
t[k] = replace(v,mapping,how,recurse)
|
|
|
end
|
|
|
return t
|
|
|
end
|
|
|
|
|
|
-- inspect(utilities.templates.replace("test %one% test", { one = "%two%", two = "two" }))
|
|
|
-- inspect(utilities.templates.resolve({ one = "%two%", two = "two", three = "%three%" }))
|
|
|
-- inspect(utilities.templates.replace("test %one% test", { one = "%two%", two = "two" },false,true))
|
|
|
-- inspect(utilities.templates.replace("test %one% test", { one = "%two%", two = "two" },false))
|
|
|
|