Module:Params/sandbox/frameSpoof
< Module:Params | sandbox
File:Test Template Info-Icon - Version (2).svg Module documentation[create] [purge]
require[[strict]]
--- ---
--- LOCAL ENVIRONMENT ---
--- ________________________________ ---
--- ---
--[[ Abstract utilities ]]--
----------------------------
-- Helper function for `string.gsub()` (for managing zero-padded numbers)
local function zero_padded (str)
return ('%03d%s'):format(#str, str)
end
-- Helper function for `table.sort()` (for natural sorting)
local function natural_sort (var1, var2)
return tostring(var1):gsub('%d+', zero_padded) <
tostring(var2):gsub('%d+', zero_padded)
end
-- Return a copy or a reference to a table
local function copy_or_ref_table (src, refonly)
if refonly then return src end
local newtab = {}
for key, val in pairs(src) do newtab[key] = val end
return newtab
end
-- Remove some numeric elements from a table, shifting everything to the left
local function remove_numeric_keys (tbl, idx, len)
local cache = {}
local tmp = idx + len - 1
for key, val in pairs(tbl) do
if type(key) == 'number' and key >= idx then
if key > tmp then cache[key - len] = val end
tbl[key] = nil
end
end
for key, val in pairs(cache) do tbl[key] = val end
end
-- Make a reduced copy of a table (shifting in both directions if necessary)
local function copy_table_reduced (tbl, idx, len)
local ret = {}
local tmp = idx + len - 1
if idx > 0 then
for key, val in pairs(tbl) do
if type(key) ~= 'number' or key < idx then
ret[key] = val
elseif key > tmp then ret[key - len] = val end
end
elseif tmp > 0 then
local nshift = 1 - idx
for key, val in pairs(tbl) do
if type(key) ~= 'number' then ret[key] = val
elseif key > tmp then ret[key - tmp] = val
elseif key < idx then ret[key + nshift] = val end
end
else
for key, val in pairs(tbl) do
if type(key) ~= 'number' or key > tmp then
ret[key] = val
elseif key < idx then ret[key + len] = val end
end
end
return ret
end
-- Make an expanded copy of a table (shifting in both directions if necessary)
--[[
local function copy_table_expanded (tbl, idx, len)
local ret = {}
local tmp = idx + len - 1
if idx > 0 then
for key, val in pairs(tbl) do
if type(key) ~= 'number' or key < idx then
ret[key] = val
else ret[key + len] = val end
end
elseif tmp > 0 then
local nshift = idx - 1
for key, val in pairs(tbl) do
if type(key) ~= 'number' then ret[key] = val
elseif key > 0 then ret[key + tmp] = val
elseif key < 1 then ret[key + nshift] = val end
end
else
for key, val in pairs(tbl) do
if type(key) ~= 'number' or key > tmp then
ret[key] = val
else ret[key - len] = val end
end
end
return ret
end
]]--
-- Move a key from a table to another, but only if under a different name and
-- always parsing numeric strings as numbers
local function steal_if_renamed (val, src, skey, dest, dkey)
local realkey = tonumber(dkey) or dkey:match'^%s*(.-)%s*$'
if skey ~= realkey then
dest[realkey] = val
src[skey] = nil
end
end
--[[ Public strings ]]--
------------------------
-- Special match keywords (functions and modifiers MUST avoid these names)
local mkeywords = {
['or'] = 0,
pattern = 1,
plain = 2,
strict = 3
}
-- Sort functions (functions and modifiers MUST avoid these names)
local sortfunctions = {
--alphabetically = false, -- Simply uncommenting enables the option
naturally = natural_sort
}
-- Callback styles for the `mapping_*` and `renaming_*` class of modifiers
-- (functions and modifiers MUST avoid these names)
--[[
Meanings of the columns:
col[1] = Loop type (0-3)
col[2] = Number of module arguments that the style requires (1-3)
col[3] = Minimum number of sequential parameters passed to the callback
col[4] = Name of the callback parameter where to place each parameter name
col[5] = Name of the callback parameter where to place each parameter value
col[6] = Argument in the modifier's invocation that will override `col[4]`
col[7] = Argument in the modifier's invocation that will override `col[5]`
A value of `-1` indicates that no meaningful value is stored (i.e. `nil`)
]]--
local mapping_styles = {
names_and_values = { 3, 2, 2, 1, 2, -1, -1 },
values_and_names = { 3, 2, 2, 2, 1, -1, -1 },
values_only = { 1, 2, 1, -1, 1, -1, -1 },
names_only = { 2, 2, 1, 1, -1, -1, -1 },
names_and_values_as = { 3, 4, 0, -1, -1, 2, 3 },
names_only_as = { 2, 3, 0, -1, -1, 2, -1 },
values_only_as = { 1, 3, 0, -1, -1, -1, 2 },
blindly = { 0, 2, 0, -1, -1, -1, -1 }
}
-- Memory slots (functions and modifiers MUST avoid these names)
local memoryslots = {
i = 'itersep',
l = 'lastsep',
p = 'pairsep',
h = 'header',
f = 'footer',
n = 'ifngiven'
}
-- Possible trimming modes for the `parsing` modifier
local trim_parse_opts = {
trim_none = { false, false },
trim_positional = { false, true },
trim_named = { true, false },
trim_all = { true, true }
}
-- Possible string modes for the iteration separator in the `parsing` and
-- `reinterpreting` modifiers
local isep_parse_opts = {
splitter_pattern = false,
splitter_string = true
}
-- Possible string modes for the key-value separator in the `parsing` and
-- `reinterpreting` modifiers
local psep_parse_opts = {
setter_pattern = false,
setter_string = true
}
-- Functions and modifiers MUST avoid these names too: `let`
--[[ Module's private environment ]]--
--------------------------------------
-- Hard-coded name of the module (to avoid going through `frame:getTitle()`)
local modulename = 'Module:Params'
-- The functions listed here declare that they don't need the `frame.args`
-- metatable to be copied into a regular table; if they are modifiers they also
-- guarantee that they will make their own (modified) copy available
local refpipe = {
call_for_each_group = true,
coins = true,
count = true,
for_each = true,
list = true,
list_values = true,
value_of = true
}
-- The functions listed here declare that they don't need the
-- `frame:getParent().args` metatable to be copied into a regular table; if
-- they are modifiers they also guarantee that they will make their own
-- (modified) copy available
local refparams = {
call_for_each_group = true,
combining_by_calling = true,
concat_and_call = true,
concat_and_invoke = true,
concat_and_magic = true,
converting_names_to_uppercase = true,
converting_names_to_lowercase = true,
count = true,
--inserting = true,
grouping_by_calling = true,
mixing_names_and_values = true,
renaming_by_mixing = true,
--renaming_to_values = true,
--swapping_names_and_values = true,
value_of = true,
with_name_matching = true
}
-- Maximum number of numeric parameters that can be filled, if missing (we
-- chose an arbitrary number for this constant; you can discuss about its
-- optimal value at Module talk:Params)
local maxfill = 1024
-- The private table of functions
local library = {}
-- Functions and modifiers that can only be invoked in first position
local static_iface = {}
-- Create a new context
local function context_new (frame)
local ctx = {}
ctx.frame = frame
ctx.oparams = frame.args
ctx.firstposonly = static_iface
ctx.iterfunc = pairs
ctx.sorttype = 0
ctx.n_parents = 0
ctx.n_children = 0
ctx.n_available = maxfill
return ctx
end
-- Move to the next action within the user-given list
local function context_iterate (ctx, n_forward)
local nextfn
if ctx.pipe[n_forward] ~= nil then
nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)'
end
if nextfn == nil then error(modulename ..
': You must specify a function to call', 0) end
if library[nextfn] == nil then
if ctx.firstposonly[nextfn] == nil then error(modulename ..
': The function ‘' .. nextfn .. '’ does not exist', 0)
else error(modulename .. ': The ‘' .. nextfn ..
'’ directive can only appear in first position', 0)
end
end
remove_numeric_keys(ctx.pipe, 1, n_forward)
return library[nextfn]
end
-- Main loop
local function main_loop (ctx, start_with)
local fn = start_with
repeat fn = fn(ctx) until not fn
if ctx.n_parents > 0 then error(modulename ..
': One or more ‘merging_substack’ directives are missing', 0) end
if ctx.n_children > 0 then error(modulename ..
', For some of the snapshots either the ‘flushing’ directive is missing or a group has not been properly closed with ‘merging_substack’', 0) end
end
-- Add a new stack of parameters to `ctx.children`
local function push_cloned_stack (ctx, tbl)
local newparams = {}
local currsnap = ctx.n_children + 1
if ctx.children == nil then ctx.children = { newparams }
else ctx.children[currsnap] = newparams end
for key, val in pairs(tbl) do newparams[key] = val end
ctx.n_children = currsnap
end
-- Parse optional user arguments of type `...|[let]|[...][number of additional
-- parameters]|[parameter 1]|[parameter 2]|[...]`
local function load_child_opts (src, start_from, append_after)
local names
local tmp
local tbl = {}
local pin = start_from
if src[pin] ~= nil and src[pin]:match'^%s*let%s*$' then
names = {}
repeat
tmp = src[pin + 1] or ''
names[tonumber(tmp) or tmp:match'^%s*(.-)%s*$' or ''] =
src[pin + 2]
pin = pin + 3
until src[pin] == nil or not src[pin]:match'^%s*let%s*$'
end
tmp = tonumber(src[pin])
if tmp ~= nil then
if tmp < 0 then tmp = -1 end
local shf = append_after - pin
for idx = pin + 1, pin + tmp do tbl[idx + shf] = src[idx] end
pin = pin + tmp + 1
end
if names ~= nil then
for key, val in pairs(names) do tbl[key] = val end
end
return tbl, pin
end
-- Load the optional arguments of some of the `mapping_*` and `renaming_*`
-- class of modifiers
local function load_callback_opts (src, n_skip, default_style)
local style
local shf
local tmp = src[n_skip + 1]
if tmp ~= nil then style = mapping_styles[tmp:match'^%s*(.-)%s*$'] end
if style == nil then
style = default_style
shf = n_skip - 1
else shf = n_skip end
local n_exist = style[3]
local karg = style[4]
local varg = style[5]
tmp = style[6]
if tmp > -1 then
tmp = src[tmp + shf]
karg = tonumber(tmp)
if karg == nil then karg = tmp:match'^%s*(.-)%s*$'
else n_exist = math.max(n_exist, karg) end
end
tmp = style[7]
if tmp > -1 then
tmp = src[tmp + shf]
varg = tonumber(tmp)
if varg == nil then varg = tmp:match'^%s*(.-)%s*$'
else n_exist = math.max(n_exist, varg) end
end
local dest, nargs = load_child_opts(src, style[2] + shf, n_exist)
tmp = style[1]
if (tmp == 3 or tmp == 2) and dest[karg] ~= nil then
tmp = tmp - 2 end
if (tmp == 3 or tmp == 1) and dest[varg] ~= nil then
tmp = tmp - 1 end
return dest, nargs, tmp, karg, varg
end
-- Parse the arguments of some of the `mapping_*` and `renaming_*` class of
-- modifiers
local function load_replace_args (opts, fname)
if opts[1] == nil then error(modulename ..
', ‘' .. fname .. '’: No pattern string was given', 0) end
if opts[2] == nil then error(modulename ..
', ‘' .. fname .. '’: No replacement string was given', 0) end
local ptn = opts[1]
local repl = opts[2]
local argc = 3
local nmax = tonumber(opts[3])
if nmax ~= nil or (opts[3] or ''):match'^%s*$' ~= nil then argc = 4 end
local flg = opts[argc]
if flg ~= nil then flg = mkeywords[flg:match'^%s*(.-)%s*$'] end
if flg == 0 then flg = nil elseif flg ~= nil then argc = argc + 1 end
return ptn, repl, nmax, flg, argc, (nmax ~= nil and nmax < 1) or
(flg == 3 and ptn == repl)
end
-- Parse the arguments of the `with_*_matching` class of modifiers
local function load_pattern_args (opts, fname)
local state = 0
local cnt = 1
local keyw
local nptns = 0
local ptns = {}
for _, val in ipairs(opts) do
if state == 0 then
nptns = nptns + 1
ptns[nptns] = { val, false, false }
state = -1
else
keyw = val:match'^%s*(.*%S)'
if keyw == nil or mkeywords[keyw] == nil or (
state > 0 and mkeywords[keyw] > 0
) then break
else
state = mkeywords[keyw]
if state > 1 then ptns[nptns][2] = true end
if state == 3 then ptns[nptns][3] = true end
end
end
cnt = cnt + 1
end
if state == 0 then error(modulename .. ', ‘' .. fname ..
'’: No pattern was given', 0) end
return ptns, nptns, cnt
end
-- Load the optional arguments of the `parsing` and `reinterpreting` modifiers
local function load_parse_opts (opts, start_from)
local argc = start_from
local tmp
local optslots = { true, true, true }
local noptslots = 3
local trimn = true
local trimu = false
local iplain = true
local pplain = true
local isp = '|'
local psp = '='
repeat
noptslots = noptslots - 1
tmp = opts[argc]
if tmp == nil then break end
tmp = tmp:match'^%s*(.-)%s*$'
if optslots[1] ~= nil and trim_parse_opts[tmp] ~= nil then
tmp = trim_parse_opts[tmp]
trimn = tmp[1]
trimu = tmp[2]
optslots[1] = nil
elseif optslots[2] ~= nil and isep_parse_opts[tmp] ~= nil then
argc = argc + 1
iplain = isep_parse_opts[tmp]
isp = opts[argc]
optslots[2] = nil
elseif optslots[3] ~= nil and psep_parse_opts[tmp] ~= nil then
argc = argc + 1
pplain = psep_parse_opts[tmp]
psp = opts[argc]
optslots[3] = nil
else break end
argc = argc + 1
until noptslots < 1
return isp, iplain, psp, pplain, trimn, trimu, argc
end
-- Map parameters' values using a custom callback and a referenced table
local value_maps = {
[0] = function (tbl, margs, karg, varg, fn)
for key in pairs(tbl) do tbl[key] = fn() end
end,
[1] = function (tbl, margs, karg, varg, fn)
for key, val in pairs(tbl) do
margs[varg] = val
tbl[key] = fn()
end
end,
[2] = function (tbl, margs, karg, varg, fn)
for key in pairs(tbl) do
margs[karg] = key
tbl[key] = fn()
end
end,
[3] = function (tbl, margs, karg, varg, fn)
for key, val in pairs(tbl) do
margs[karg] = key
margs[varg] = val
tbl[key] = fn()
end
end
}
-- Private table for `map_names()`
local name_thieves = {
[0] = function (cache, tbl, rargs, karg, varg, fn)
for key, val in pairs(tbl) do
steal_if_renamed(val, tbl, key, cache, fn())
end
end,
[1] = function (cache, tbl, rargs, karg, varg, fn)
for key, val in pairs(tbl) do
rargs[varg] = val
steal_if_renamed(val, tbl, key, cache, fn())
end
end,
[2] = function (cache, tbl, rargs, karg, varg, fn)
for key, val in pairs(tbl) do
rargs[karg] = key
steal_if_renamed(val, tbl, key, cache, fn())
end
end,
[3] = function (cache, tbl, rargs, karg, varg, fn)
for key, val in pairs(tbl) do
rargs[karg] = key
rargs[varg] = val
steal_if_renamed(val, tbl, key, cache, fn())
end
end
}
-- Map parameters' names using a custom callback and a referenced table
local function map_names (tbl, rargs, karg, varg, looptype, fn)
local cache = {}
name_thieves[looptype](cache, tbl, rargs, karg, varg, fn)
for key, val in pairs(cache) do tbl[key] = val end
end
-- Return a new table that contains `src` regrouped according to the numeric
-- suffixes in its keys
local function make_groups (src)
-- NOTE: `src` might be the original metatable!
local tmp
local prefix
local gid
local groups = {}
for key, val in pairs(src) do
-- `key` must only be a string or a number...
gid = tonumber(key)
if gid == nil then
prefix, gid = key:match'^%s*(.-)%s*(%-?%d*)%s*$'
gid = tonumber(gid) or ''
else prefix = '' end
if groups[gid] == nil then groups[gid] = {} end
tmp = tonumber(prefix)
if tmp ~= nil then
if tmp < 1 then prefix = tmp - 1 else prefix = tmp end
end
groups[gid][prefix] = val
end
return groups
end
-- Split into parts a string containing the `$#` and `$@` placeholders and
-- return the information as a skeleton table, a canvas table and a length
local function parse_placeholder_string (target)
local skel = {}
local canvas = {}
local idx = 1
local s_pos = 1
local e_pos = string.find(target, '%$[@#]', 1, false)
while e_pos ~= nil do
canvas[idx] = target:sub(s_pos, e_pos - 1)
skel[idx + 1] = target:sub(e_pos, e_pos + 1) == '$@'
idx = idx + 2
s_pos = e_pos + 2
e_pos = string.find(target, '%$[@#]', s_pos, false)
end
if (s_pos > target:len()) then idx = idx - 1
else canvas[idx] = target:sub(s_pos) end
return skel, canvas, idx
end
-- Populate a table by parsing a parameter string
local function parse_parameter_string (tbl, str, isp, ipl, psp, ppl, trn, tru)
local key
local val
local spos1
local spos2
local pos1
local pos2
local pos3 = 0
local idx = 1
local lenplone = #str + 1
if isp == nil or isp == '' then
if psp == nil or psp == '' then
if tru then tbl[idx] = str:match'^%s*(.-)%s*$'
else tbl[idx] = str end
return tbl
end
spos1, spos2 = str:find(psp, 1, ppl)
if spos1 == nil then
key = idx
if tru then val = str:match'^%s*(.-)%s*$'
else val = str end
idx = idx + 1
else
key = str:sub(1, spos1 - 1)
key = tonumber(key) or key:match'^%s*(.-)%s*$'
val = str:sub(spos2 + 1)
if trn then val = val:match'^%s*(.-)%s*$' end
end
tbl[key] = val
return tbl
end
if psp == nil or psp == '' then
repeat
pos1 = pos3 + 1
pos2, pos3 = str:find(isp, pos1, ipl)
val = str:sub(pos1, (pos2 or lenplone) - 1)
if tru then val = val:match'^%s*(.-)%s*$' end
tbl[idx] = val
idx = idx + 1
until pos2 == nil
return tbl
end
repeat
pos1 = pos3 + 1
pos2, pos3 = str:find(isp, pos1, ipl)
val = str:sub(pos1, (pos2 or lenplone) - 1)
spos1, spos2 = val:find(psp, 1, ppl)
if spos1 == nil then
key = idx
if tru then val = val:match'^%s*(.-)%s*$' end
idx = idx + 1
else
key = val:sub(1, spos1 - 1)
key = tonumber(key) or key:match'^%s*(.-)%s*$'
val = val:sub(spos2 + 1)
if trn then val = val:match'^%s*(.-)%s*$' end
end
tbl[key] = val
until pos2 == nil
return tbl
end
-- Concatenate the numeric keys from the table of parameters to the numeric
-- keys from the table of options; non-numeric keys from the table of options
-- will prevail over colliding non-numeric keys from the table of parameters
local function concat_params (ctx)
local tbl = ctx.params
local nmax = table.maxn(ctx.pipe)
local retval = {}
if ctx.subset == 1 then
-- We need only the sequence
for key, val in ipairs(tbl) do retval[key + nmax] = val end
else
if ctx.subset == -1 then
for key in ipairs(tbl) do tbl[key] = nil end
end
for key, val in pairs(tbl) do
if type(key) == 'number' and key > 0 then
retval[key + nmax] = val
else retval[key] = val end
end
end
for key, val in pairs(ctx.pipe) do retval[key] = val end
return retval
end
-- Flush the parameters by calling a custom function for each value (after this
-- function has been invoked `ctx.params` will be no longer usable)
local function flush_params (ctx, fn)
local tbl = ctx.params
if ctx.subset == 1 then
for key, val in ipairs(tbl) do fn(key, val) end
return
end
if ctx.subset == -1 then
for key, val in ipairs(tbl) do tbl[key] = nil end
end
if ctx.sorttype > 0 then
local nums = {}
local words = {}
local nn = 0
local nw = 0
for key, val in pairs(tbl) do
if type(key) == 'number' then
nn = nn + 1
nums[nn] = key
else
nw = nw + 1
words[nw] = key
end
end
table.sort(nums)
table.sort(words, natural_sort)
if ctx.sorttype == 2 then
for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
return
end
for idx = 1, nn do fn(nums[idx], tbl[nums[idx]]) end
for idx = 1, nw do fn(words[idx], tbl[words[idx]]) end
return
end
if ctx.subset ~= -1 then
for key, val in ipairs(tbl) do
fn(key, val)
tbl[key] = nil
end
end
for key, val in pairs(tbl) do fn(key, val) end
end
local currentFrame = mw.getCurrentFrame()
local parentFrame = currentFrame:getParent()
local function invoke(modname, fnname, modargs0, noemu)
local baseFrame = parentFrame or currentFrame -- No need to pass a frame with no parent
--[=[ Input validation:
Scribunto does a fair job of its own input validation and throws errors when we pass these on
but we spoof "modargs" to the target *raw*, so in order to not cause issues for the target
sanitized and validate everything.
]=]
assert('string' == type(modname), 'Module name must a string')
assert('string' == type(fnname), 'Function name must a string')
local modargs = {} -- Make a sanitized copy; this would be tricky to do field by field on demand
for k, v in pairs(modargs0) do
local kt, vt = type(k), type(v)
if 'boolean' == vt then
v = v and '1' or '' -- "checkArgs" used by "newChild", "callParserfunction", etc. does this
elseif 'number' == vt then
v = tostring(v)
else
assert('string' == vt, 'invalid type ' .. vt .. ' for arg "' .. k .. '"')
end
if 'number' == kt then
--XXX: math.ldexp(1, 53) == 9007199254740992; IEEE 754 binary64 => 53-bit significand
--XXX: 9007199254740992 limit is from [[mw:LuaSandbox]]
if math.floor(k) ~= k or math.abs(k) > math.ldexp(1, 53) or k ~= k then
k = tostring(k) -- Convert noninteger parameters to strings
end
elseif 'string' == kt then
k = k:match("^%s*(.-)%s*$") -- Trim string parameters
if k:match("^%-?%d+$") then
local int = tonumber(k)
if int and math.abs(int) <= math.ldexp(1, 53) then
k = int -- Convert integer parameters to numbers
end
end
else
error('arg keys must be strings or numbers, ' .. kt .. ' given', 0)
end
if 'string' == type(k) then
v = v:match("^%s*(.-)%s*$") -- Trim arguments of string parameters
end
modargs[k] = v
end
if noemu then
table.insert(modargs, 1, fnname)
table.insert(modargs, 1, modname)
return baseFrame:callParserFunction('#invoke', modargs)
end
--[=[
Emulate a "#invoke" call with "require", a frameSpoof and a monkey patched "getCurrentFrame"
providing a spoofed "args", "getParent" and "getTitle"
falling back to creating an on demand "newChild" frame for other fields
]=]
--XXX: mw.title.makeTitle('Module', modname); handle illegal pagenames
--XXX: mw.site.namespaces.Module.canonicalName .. ':' .. modname; localize namespace
local modtitle = 'Module:' .. modname
--[=[ Spoof a "#invoke" frame for the target:
Scribunto frame objects are just regular Lua tables with method functions, however, those
functions are also lexical closures that depends on their upvalues and require one to pass
in the correct frame reference, so we cannot copy those function references into our
"frameSpoof" but we can build redirector stub functions that punt calls to those, so long
as we ensure we always call them with the right frame reference. We want the target module
to never use these stubs as creating a frame via "newFrame" is expensive, so we go one
further and instead create delay stub functions that only do that on demand when the target
module tries to access any fields we do not end up directly spoofing. The delay stubs also
have to punt to newly created frame but only after it has created it. Creating the new frame
should only happen once so even though we create many delay stubs, only one will actually
create it. To ensure that only one winning delay stub is ever called, each delay stub updates
all fields of the "frameSpoof" to always punting stubs instead, thereby effectively caching
the frame creation. So we start by filling all frame fields of "frameSpoof" with delay stubs
that: first create a new frame, then cache it by update all frame fields with always punting
stubs, and finally punt themselves. Of course the "args" fields is not a function so it we
must handle it different but we at first ignore that and later ensure to overwrite it in
both cases: when writing always punting stubs we just write the actual frame "args" value and
when writing delay stubs we just write in our local "args" spoofer. Of course, we overwrite
the delay stubs with a few more local spoofs that are easy to create and commonly accessed
by the target: "getParent" and "getTitle". We might be able to improve performance by
spoofing more fields but many call back into the PHP parser so they would be hard to emulate
adequately without creating an actual parser frame and the ones we do spoof cover those
most frequently accessed by any target module: the "args" and the parent "args".
]=]
local frameSpoof = {}
-- Punt to "newChild" frame on demand for all function fields; Spoofed fields are overwritten later
for name in pairs(baseFrame) do
frameSpoof[name] = function(self, ...)
local frameDelayed = baseFrame:newChild{title = modtitle, args = modargs}
-- Overwrite all fields on demand effectively caching this so it will only happen once
for name in pairs(frameDelayed) do
frameSpoof[name] = function(self, ...)
if self == frameSpoof then
self = frameDelayed -- Only redirect correct value and let error propagate
end
-- All future accesses will punt since we made an actual frame we lose nothing
return frameDelayed[name](self, ...)
end
end
-- Overwrite "args" again because it is not a function
frameSpoof.args = frameDelayed.args
if self == frameSpoof then
self = frameDelayed -- Only redirect correct value and let error propagate
end
-- First punt after updating all fields to punt in the future
return frameDelayed[name](self, ...)
end
end
-- Spoof "getParent"
function frameSpoof:getParent(...)
if self ~= frameSpoof then
return baseFrame.getParent(self, ...) -- Let a real frame throw the error
end
return baseFrame
end
-- Spoof "getTitle"
function frameSpoof:getTitle(...)
if self ~= frameSpoof then
return baseFrame.getTitle(self, ...) -- Let a real frame throw the error
end
return modtitle
end
-- Spoof "args" including recreating a metatable for read-only access to upvalues
local args_mt = {}
frameSpoof.args = setmetatable({}, args_mt)
args_mt.__index = modargs
function args_mt:__pairs()
return pairs(modargs)
end
function args_mt:__ipairs()
return ipairs(modargs)
end
-- Emulate the "#invoke" call with "require" and our "frameSpoof"
local getCurrentFrame = mw.getCurrentFrame
-- Monkey patch "getCurrentFrame" to return our "frameSpoof"
function mw.getCurrentFrame()
return frameSpoof
end
-- Save contents of "package.loaded"
local loaded = package.loaded
local pkgloaded = {}
for k, v in pairs(loaded) do
pkgloaded[k] = v
end
loaded[modtitle] = nil -- Force "require" to load from scratch
-- "require" here so the target initialization block sees our monkey patched "getCurrentFrame"
local mod = require(modtitle) --TODO: Wrap with "pcall" to catch "require" errors?
-- Restore "package.loaded" and pretend "require" was never called here
for k in pairs(loaded) do
if nil == pkgloaded[k] then
loaded[k] = nil -- Delete anything new
end
end
for k, v in pairs(pkgloaded) do
loaded[k] = v -- Restore everthing else
end
local fn, results
if 'table' == type(mod) then -- Only handle table returns from "require"
fn = mod[fnname]
end
if 'function' == type(fn) then --XXX: We don't support callable tables; neither does "#invoke"?
results = { fn(frameSpoof) } -- Support multiple return values as sequence; same as "#invoke"
end
mw.getCurrentFrame = getCurrentFrame -- Revert earlier monkey patch
--TODO: if "'table' ~= type(mod)", throw an error about "'modtitle' is not a table package"
--TODO: if "fn" is "nil", , throw an error about "'fnname' is not a key/field in 'modtitle'"
--TODO: if "results" is "nil", throw an error about "'fnname' is not a function in 'modtitle'"
local stringResults = {}
for i, result in ipairs(results) do
stringResults[i] = tostring(result)
end
return table.concat(stringResults)
end
--[[ Modifiers ]]--
-----------------------------
-- Syntax: #invoke:params|sequential|pipe to
library.sequential = function (ctx)
if ctx.subset == -1 then error(modulename ..
': The two directives ‘non-sequential’ and ‘sequential’ are in contradiction with each other', 0) end
if ctx.sorttype > 0 then error(modulename ..
': The ‘all_sorted’ and ‘reassorted’ directives are redundant when followed by ‘sequential’', 0) end
ctx.iterfunc = ipairs
ctx.subset = 1
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|non-sequential|pipe to
library['non-sequential'] = function (ctx)
if ctx.subset == 1 then error(modulename ..
': The two directives ‘sequential’ and ‘non-sequential’ are in contradiction with each other', 0) end
ctx.iterfunc = pairs
ctx.subset = -1
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|all_sorted|pipe to
library.all_sorted = function (ctx)
if ctx.subset == 1 then error(modulename ..
': The ‘all_sorted’ directive is redundant after ‘sequential’', 0) end
if ctx.sorttype == 2 then error(modulename ..
': The two directives ‘reassorted’ and ‘sequential’ are in contradiction with each other', 0) end
ctx.sorttype = 1
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|reassorted|pipe to
library.reassorted = function (ctx)
if ctx.subset == 1 then error(modulename ..
': The ‘reassorted’ directive is redundant after ‘sequential’', 0) end
if ctx.sorttype == 1 then error(modulename ..
': The two directives ‘sequential’ and ‘reassorted’ are in contradiction with each other', 0) end
ctx.sorttype = 2
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|setting|directives|...|pipe to
library.setting = function (ctx)
local opts = ctx.pipe
local cmd = opts[1]
if cmd ~= nil then
cmd = cmd:gsub('%s+', ''):gsub('/+', '/'):match'^/*(.*[^/])'
end
if cmd == nil then error(modulename ..
', ‘setting’: No directive was given', 0) end
local sep = string.byte('/')
local argc = 2
local dest = {}
local vname
local chr
for idx = 1, #cmd do
chr = cmd:byte(idx)
if chr == sep then
for key, val in ipairs(dest) do
ctx[val] = opts[argc]
dest[key] = nil
end
argc = argc + 1
else
vname = memoryslots[string.char(chr)]
if vname == nil then error(modulename ..
', ‘setting’: Unknown slot ‘' ..
string.char(chr) .. '’', 0) end
table.insert(dest, vname)
end
end
for key, val in ipairs(dest) do ctx[val] = opts[argc] end
return context_iterate(ctx, argc + 1)
end
-- Syntax: #invoke:params|squeezing|pipe to
library.squeezing = function (ctx)
local tbl = ctx.params
local store = {}
local indices = {}
local newlen = 0
for key, val in pairs(tbl) do
if type(key) == 'number' then
newlen = newlen + 1
indices[newlen] = key
store[key] = val
tbl[key] = nil
end
end
table.sort(indices)
for idx = 1, newlen do tbl[idx] = store[indices[idx]] end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|filling_the_gaps|pipe to
library.filling_the_gaps = function (ctx)
local tbl = ctx.params
local nmin = 1
local nmax = nil
local nnums = -1
local tmp = {}
for key, val in pairs(tbl) do
if type(key) == 'number' then
if nmax == nil then
if key < nmin then nmin = key end
nmax = key
elseif key > nmax then nmax = key
elseif key < nmin then nmin = key end
nnums = nnums + 1
tmp[key] = val
end
end
if nmax ~= nil and nmax - nmin > nnums then
ctx.n_available = ctx.n_available + nmin + nnums - nmax
if ctx.n_available < 0 then error(modulename ..
', ‘filling_the_gaps’: It is possible to fill at most ' ..
tostring(maxfill) .. ' parameters', 0) end
for idx = nmin, nmax, 1 do tbl[idx] = '' end
for key, val in pairs(tmp) do tbl[key] = val end
end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|clearing|pipe to
library.clearing = function (ctx)
local tbl = ctx.params
local numerics = {}
for key, val in pairs(tbl) do
if type(key) == 'number' then
numerics[key] = val
tbl[key] = nil
end
end
for key, val in ipairs(numerics) do tbl[key] = val end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|cutting|left cut|right cut|pipe to
library.cutting = function (ctx)
local lcut = tonumber(ctx.pipe[1])
if lcut == nil then error(modulename ..
', ‘cutting’: Left cut must be a number', 0) end
local rcut = tonumber(ctx.pipe[2])
if rcut == nil then error(modulename ..
', ‘cutting’: Right cut must be a number', 0) end
local tbl = ctx.params
local len = #tbl
if lcut < 0 then lcut = len + lcut end
if rcut < 0 then rcut = len + rcut end
local tot = lcut + rcut
if tot > 0 then
local cache = {}
if tot >= len then
for key in ipairs(tbl) do tbl[key] = nil end
tot = len
else
for idx = len - rcut + 1, len, 1 do tbl[idx] = nil end
for idx = 1, lcut, 1 do tbl[idx] = nil end
end
for key, val in pairs(tbl) do
if type(key) == 'number' and key > 0 then
if key > len then cache[key - tot] = val
else cache[key - lcut] = val end
tbl[key] = nil
end
end
for key, val in pairs(cache) do tbl[key] = val end
end
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|cropping|left crop|right crop|pipe to
library.cropping = function (ctx)
local lcut = tonumber(ctx.pipe[1])
if lcut == nil then error(modulename ..
', ‘cropping’: Left crop must be a number', 0) end
local rcut = tonumber(ctx.pipe[2])
if rcut == nil then error(modulename ..
', ‘cropping’: Right crop must be a number', 0) end
local tbl = ctx.params
local nmin
local nmax
for key in pairs(tbl) do
if type(key) == 'number' then
if nmin == nil then
nmin = key
nmax = key
elseif key > nmax then nmax = key
elseif key < nmin then nmin = key end
end
end
if nmin ~= nil then
local len = nmax - nmin + 1
if lcut < 0 then lcut = len + lcut end
if rcut < 0 then rcut = len + rcut end
if lcut + rcut - len > -1 then
for key in pairs(tbl) do
if type(key) == 'number' then tbl[key] = nil end
end
elseif lcut + rcut > 0 then
for idx = nmax - rcut + 1, nmax do tbl[idx] = nil end
for idx = nmin, nmin + lcut - 1 do tbl[idx] = nil end
local lshift = nmin + lcut - 1
if lshift > 0 then
for idx = lshift + 1, nmax, 1 do
tbl[idx - lshift] = tbl[idx]
tbl[idx] = nil
end
end
end
end
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|purging|start offset|length|pipe to
library.purging = function (ctx)
local idx = tonumber(ctx.pipe[1])
if idx == nil then error(modulename ..
', ‘purging’: Start offset must be a number', 0) end
local len = tonumber(ctx.pipe[2])
if len == nil then error(modulename ..
', ‘purging’: Length must be a number', 0) end
local tbl = ctx.params
if len < 1 then
len = len + table.maxn(tbl)
if idx > len then return context_iterate(ctx, 3) end
len = len - idx + 1
end
ctx.params = copy_table_reduced(tbl, idx, len)
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|backpurging|start offset|length|pipe to
library.backpurging = function (ctx)
local last = tonumber(ctx.pipe[1])
if last == nil then error(modulename ..
', ‘backpurging’: Start offset must be a number', 0) end
local len = tonumber(ctx.pipe[2])
if len == nil then error(modulename ..
', ‘backpurging’: Length must be a number', 0) end
local idx
local tbl = ctx.params
if len > 0 then
idx = last - len + 1
else
for key in pairs(tbl) do
if type(key) == 'number' and (idx == nil or
key < idx) then idx = key end
end
if idx == nil then return context_iterate(ctx, 3) end
idx = idx - len
if last < idx then return context_iterate(ctx, 3) end
len = last - idx + 1
end
ctx.params = copy_table_reduced(ctx.params, idx, len)
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|reversing_numeric_names|pipe to
library.reversing_numeric_names = function (ctx)
local tbl = ctx.params
local numerics = {}
local nmax = 0
for key, val in pairs(tbl) do
if type(key) == 'number' then
numerics[key] = val
tbl[key] = nil
if key > nmax then nmax = key end
end
end
for key, val in pairs(numerics) do tbl[nmax - key + 1] = val end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|pivoting_numeric_names|pipe to
--[[
library.pivoting_numeric_names = function (ctx)
local tbl = ctx.params
local shift = #tbl + 1
if shift < 2 then return library.reversing_numeric_names(ctx) end
local numerics = {}
for key, val in pairs(tbl) do
if type(key) == 'number' then
numerics[key] = val
tbl[key] = nil
end
end
for key, val in pairs(numerics) do tbl[shift - key] = val end
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|mirroring_numeric_names|pipe to
--[[
library.mirroring_numeric_names = function (ctx)
local tbl = ctx.params
local numerics = {}
local nmax
local nmin
for key, val in pairs(tbl) do
if type(key) == 'number' then
numerics[key] = val
tbl[key] = nil
if nmax == nil then
nmax = key
nmin = key
elseif key > nmax then nmax = key
elseif key < nmin then nmin = key end
end
end
for key, val in pairs(numerics) do tbl[nmax + nmin - key] = val end
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|swapping_numeric_names|pipe to
--[[
library.swapping_numeric_names = function (ctx)
local tbl = ctx.params
local cache = {}
local nsize = 0
local tmp
for key in pairs(tbl) do
if type(key) == 'number' then
nsize = nsize + 1
cache[nsize] = key
end
end
table.sort(cache)
for idx = math.floor(nsize / 2), 1, -1 do
tmp = tbl[cache[idx] ]
tbl[cache[idx] ] = tbl[cache[nsize - idx + 1] ]
tbl[cache[nsize - idx + 1] ] = tmp
end
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|sorting_sequential_values|[criterion]|pipe to
library.sorting_sequential_values = function (ctx)
local sortfn
if ctx.pipe[1] ~= nil then sortfn = sortfunctions[ctx.pipe[1]] end
if sortfn then table.sort(ctx.params, sortfn)
else table.sort(ctx.params) end -- i.e. either `false` or `nil`
if sortfn == nil then return context_iterate(ctx, 1) end
return context_iterate(ctx, 2)
end
-- Syntax: #invoke:params|inserting|position|how many|...|pipe to
--[[
library.inserting = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local idx = tonumber(ctx.pipe[1])
if idx == nil then error(modulename ..
', ‘inserting’: Position must be a number', 0) end
local len = tonumber(ctx.pipe[2])
if len == nil or len < 1 then error(modulename ..
', ‘inserting’: The amount must be a number greater than zero', 0) end
local opts = ctx.pipe
local tbl = copy_table_expanded(ctx.params, idx, len)
for key = idx, idx + len - 1 do tbl[key] = opts[key - idx + 3] end
ctx.params = tbl
return context_iterate(ctx, len + 3)
end
]]--
-- Syntax: #invoke:params|imposing|name|value|pipe to
library.imposing = function (ctx)
if ctx.pipe[1] == nil then error(modulename ..
', ‘imposing’: Missing parameter name to impose', 0) end
local key = ctx.pipe[1]:match'^%s*(.-)%s*$'
ctx.params[tonumber(key) or key] = ctx.pipe[2]
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|providing|name|value|pipe to
library.providing = function (ctx)
if ctx.pipe[1] == nil then error(modulename ..
', ‘providing’: Missing parameter name to provide', 0) end
local key = ctx.pipe[1]:match'^%s*(.-)%s*$'
key = tonumber(key) or key
if ctx.params[key] == nil then ctx.params[key] = ctx.pipe[2] end
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|discarding|name|[how many]|pipe to
library.discarding = function (ctx)
if ctx.pipe[1] == nil then error(modulename ..
', ‘discarding’: Missing parameter name to discard', 0) end
local key = ctx.pipe[1]
local len = tonumber(ctx.pipe[2])
if len == nil then
ctx.params[tonumber(key) or key:match'^%s*(.-)%s*$'] = nil
return context_iterate(ctx, 2)
end
key = tonumber(key)
if key == nil then error(modulename ..
', ‘discarding’: A range was provided, but the initial parameter name is not numeric', 0) end
if len < 1 then error(modulename ..
', ‘discarding’: A range can only be a number greater than zero', 0) end
for idx = key, key + len - 1 do ctx.params[idx] = nil end
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|excluding_non-numeric_names|pipe to
library['excluding_non-numeric_names'] = function (ctx)
local tmp = ctx.params
for key, val in pairs(tmp) do
if type(key) ~= 'number' then tmp[key] = nil end
end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|excluding_numeric_names|pipe to
library.excluding_numeric_names = function (ctx)
local tmp = ctx.params
for key, val in pairs(tmp) do
if type(key) == 'number' then tmp[key] = nil end
end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|with_name_matching|target 1|[plain flag 1]|[or]
-- |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag
-- N]|pipe to
library.with_name_matching = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local targets, nptns, argc = load_pattern_args(ctx.pipe,
'with_name_matching')
local tmp
local ptn
local tbl = ctx.params
local newparams = {}
for idx = 1, nptns do
ptn = targets[idx]
if ptn[3] then
tmp = tonumber(ptn[1]) or ptn[1]
newparams[tmp] = tbl[tmp]
else
for key, val in pairs(tbl) do
if tostring(key):find(ptn[1], 1, ptn[2]) then
newparams[key] = val
end
end
end
end
ctx.params = newparams
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|with_name_not_matching|target 1|[plain flag 1]
-- |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain
-- flag N]|pipe to
library.with_name_not_matching = function (ctx)
local targets, nptns, argc = load_pattern_args(ctx.pipe,
'with_name_not_matching')
local tbl = ctx.params
if nptns == 1 and targets[1][3] then
local tmp = targets[1][1]
tbl[tonumber(tmp) or tmp] = nil
return context_iterate(ctx, argc)
end
local yesmatch
local ptn
for key in pairs(tbl) do
yesmatch = true
for idx = 1, nptns do
ptn = targets[idx]
if ptn[3] then
if tostring(key) ~= ptn[1] then
yesmatch = false
break
end
elseif not tostring(key):find(ptn[1], 1, ptn[2]) then
yesmatch = false
break
end
end
if yesmatch then tbl[key] = nil end
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|with_value_matching|target 1|[plain flag 1]|[or]
-- |[target 2]|[plain flag 2]|[or]|[...]|[target N]|[plain flag
-- N]|pipe to
library.with_value_matching = function (ctx)
local tbl = ctx.params
local targets, nptns, argc = load_pattern_args(ctx.pipe,
'with_value_matching')
local nomatch
local ptn
for key, val in pairs(tbl) do
nomatch = true
for idx = 1, nptns do
ptn = targets[idx]
if ptn[3] then
if val == ptn[1] then
nomatch = false
break
end
elseif val:find(ptn[1], 1, ptn[2]) then
nomatch = false
break
end
end
if nomatch then tbl[key] = nil end
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|with_value_not_matching|target 1|[plain flag 1]
-- |[and]|[target 2]|[plain flag 2]|[and]|[...]|[target N]|[plain
-- flag N]|pipe to
library.with_value_not_matching = function (ctx)
local tbl = ctx.params
local targets, nptns, argc = load_pattern_args(ctx.pipe,
'with_value_not_matching')
local yesmatch
local ptn
for key, val in pairs(tbl) do
yesmatch = true
for idx = 1, nptns do
ptn = targets[idx]
if ptn[3] then
if val ~= ptn[1] then
yesmatch = false
break
end
elseif not val:find(ptn[1], 1, ptn[2]) then
yesmatch = false
break
end
end
if yesmatch then tbl[key] = nil end
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|trimming_values|pipe to
library.trimming_values = function (ctx)
local tbl = ctx.params
for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|converting_values_to_lowercase|pipe to
library.converting_values_to_lowercase = function (ctx)
local tbl = ctx.params
for key, val in pairs(tbl) do tbl[key] = val:lower() end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|converting_values_to_uppercase|pipe to
library.converting_values_to_uppercase = function (ctx)
local tbl = ctx.params
for key, val in pairs(tbl) do tbl[key] = val:upper() end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|mapping_by_calling|template name|[call
-- style]|[let]|[...][number of additional parameters]|[parameter
-- 1]|[parameter 2]|[...]|[parameter N]|pipe to
library.mapping_by_calling = function (ctx)
local opts = ctx.pipe
local tname
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(modulename ..
', ‘mapping_by_calling’: No template name was provided', 0) end
local margs, argc, looptype, karg, varg = load_callback_opts(opts, 1,
mapping_styles.values_only)
local model = { title = tname, args = margs }
value_maps[looptype](ctx.params, margs, karg, varg, function ()
return ctx.frame:expandTemplate(model)
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|mapping_by_invoking|module name|function
-- name|[call style]|[let]|[...]|[number of additional
-- arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to
library.mapping_by_invoking = function (ctx)
local opts = ctx.pipe
local mname
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if mname == nil then error(modulename ..
', ‘mapping_by_invoking’: No module name was provided', 0) end
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(modulename ..
', ‘mapping_by_invoking’: No function name was provided', 0) end
local margs, argc, looptype, karg, varg = load_callback_opts(opts, 2,
mapping_styles.values_only)
value_maps[looptype](ctx.params, margs, karg, varg, function ()
return invoke(mname, fname, margs)
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|mapping_by_magic|parser function|[call
-- style]|[let]|[...][number of additional arguments]|[argument
-- 1]|[argument 2]|[...]|[argument N]|pipe to
library.mapping_by_magic = function (ctx)
local opts = ctx.pipe
local magic
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if magic == nil then error(modulename ..
', ‘mapping_by_magic’: No parser function was provided', 0) end
local margs, argc, looptype, karg, varg = load_callback_opts(opts, 1,
mapping_styles.values_only)
value_maps[looptype](ctx.params, margs, karg, varg, function ()
return ctx.frame:callParserFunction(magic, margs)
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|mapping_by_replacing|target|replace|[count]|[plain
-- flag]|pipe to
library.mapping_by_replacing = function (ctx)
local ptn, repl, nmax, flg, argc, die =
load_replace_args(ctx.pipe, 'mapping_by_replacing')
if die then return context_iterate(ctx, argc) end
local tbl = ctx.params
if flg == 3 then
for key, val in pairs(tbl) do
if val == ptn then tbl[key] = repl end
end
else
if flg == 2 then
-- Copied from Module:String's `str._escapePattern()`
ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
end
for key, val in pairs(tbl) do
tbl[key] = val:gsub(ptn, repl, nmax)
end
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|mapping_by_mixing|mixing string|pipe to
library.mapping_by_mixing = function (ctx)
if ctx.pipe[1] == nil then error(modulename ..
', ‘mapping_by_mixing’: No mixing string was provided', 0) end
local mix = ctx.pipe[1]
local tbl = ctx.params
if mix == '$#' then
for key in pairs(tbl) do tbl[key] = key end
return context_iterate(ctx, 2)
end
local skel, cnv, n_parts = parse_placeholder_string(mix)
for key, val in pairs(tbl) do
for idx = 2, n_parts, 2 do
if skel[idx] then cnv[idx] = val else cnv[idx] = tostring(key) end
end
tbl[key] = table.concat(cnv)
end
return context_iterate(ctx, 2)
end
-- Syntax: #invoke:params|mapping_to_names|pipe to
--[[
library.mapping_to_names = function (ctx)
local tbl = ctx.params
for key in pairs(tbl) do tbl[key] = key end
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|converting_names_to_lowercase|pipe to
library.converting_names_to_lowercase = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local cache = {}
for key, val in pairs(ctx.params) do
if type(key) == 'string' then cache[key:lower()] = val else
cache[key] = val end
end
ctx.params = cache
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|converting_names_to_uppercase|pipe to
library.converting_names_to_uppercase = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local cache = {}
for key, val in pairs(ctx.params) do
if type(key) == 'string' then cache[key:upper()] = val else
cache[key] = val end
end
ctx.params = cache
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|renaming_by_calling|template name|[call
-- style]|[let]|[...][number of additional parameters]|[parameter
-- 1]|[parameter 2]|[...]|[parameter N]|pipe to
library.renaming_by_calling = function (ctx)
local opts = ctx.pipe
local tname
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(modulename ..
', ‘renaming_by_calling’: No template name was provided', 0) end
local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 1,
mapping_styles.names_only)
local model = { title = tname, args = rargs }
map_names(ctx.params, rargs, karg, varg, looptype, function ()
return ctx.frame:expandTemplate(model)
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|renaming_by_invoking|module name|function
-- name|[call style]|[let]|[...]|[number of additional
-- arguments]|[argument 1]|[argument 2]|[...]|[argument N]|pipe to
library.renaming_by_invoking = function (ctx)
local opts = ctx.pipe
local mname
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if mname == nil then error(modulename ..
', ‘renaming_by_invoking’: No module name was provided', 0) end
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(modulename ..
', ‘renaming_by_invoking’: No function name was provided', 0) end
local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 2,
mapping_styles.names_only)
map_names(ctx.params, rargs, karg, varg, looptype, function ()
local tmp = invoke(mname, fname, rargs)
return tonumber(tmp) or tmp
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|renaming_by_magic|parser function|[call
-- style]|[let]|[...][number of additional arguments]|[argument
-- 1]|[argument 2]|[...]|[argument N]|pipe to
library.renaming_by_magic = function (ctx)
local opts = ctx.pipe
local magic
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if magic == nil then error(modulename ..
', ‘renaming_by_magic’: No parser function was provided', 0) end
local rargs, argc, looptype, karg, varg = load_callback_opts(opts, 1,
mapping_styles.names_only)
map_names(ctx.params, rargs, karg, varg, looptype, function ()
return ctx.frame:callParserFunction(magic, rargs)
end)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|renaming_by_replacing|target|replace|[count]|[plain
-- flag]|pipe to
library.renaming_by_replacing = function (ctx)
local ptn, repl, nmax, flg, argc, die =
load_replace_args(ctx.pipe, 'renaming_by_replacing')
if die then return context_iterate(ctx, argc) end
local tbl = ctx.params
if flg == 3 then
local key = tonumber(ptn) or ptn:match'^%s*(.-)%s*$'
local val = tbl[key]
if val ~= nil then
tbl[key] = nil
tbl[tonumber(repl) or repl:match'^%s*(.-)%s*$'] = val
end
else
if flg == 2 then
-- Copied from Module:String's `str._escapePattern()`
ptn = ptn:gsub('[%(%)%.%%%+%-%*%?%[%^%$%]]', '%%%0')
end
local cache = {}
for key, val in pairs(tbl) do
steal_if_renamed(val, tbl, key, cache,
tostring(key):gsub(ptn, repl, nmax))
end
for key, val in pairs(cache) do tbl[key] = val end
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|renaming_by_mixing|mixing string|pipe to
library.renaming_by_mixing = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
if ctx.pipe[1] == nil then error(modulename ..
', ‘renaming_by_mixing’: No mixing string was provided', 0) end
local mix = ctx.pipe[1]:match'^%s*(.-)%s*$'
local cache = {}
if mix == '$@' then
for _, val in pairs(ctx.params) do cache[val] = val end
else
local skel, canvas, n_parts = parse_placeholder_string(mix)
local tmp
for key, val in pairs(ctx.params) do
for idx = 2, n_parts, 2 do
if skel[idx] then canvas[idx] = val
else canvas[idx] = tostring(key) end
end
tmp = table.concat(canvas)
cache[tonumber(tmp) or tmp] = val
end
end
ctx.params = cache
return context_iterate(ctx, 2)
end
-- Syntax: #invoke:params|renaming_to_values|pipe to
--[[
library.renaming_to_values = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local cache = {}
for _, val in pairs(ctx.params) do cache[val] = val end
ctx.params = cache
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|grouping_by_calling|template
-- name|[let]|[...]|[number of additional arguments]|[argument
-- 1]|[argument 2]|[...]|[argument N]|pipe to
library.grouping_by_calling = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local opts = ctx.pipe
local tmp
if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
if tmp == nil then error(modulename ..
', ‘grouping_by_calling’: No template name was provided', 0) end
local model = { title = tmp }
local tmp, argc = load_child_opts(opts, 2, 0)
local gargs = {}
for key, val in pairs(tmp) do
if type(key) == 'number' and key < 1 then gargs[key - 1] = val
else gargs[key] = val end
end
local groups = make_groups(ctx.params)
for gid, group in pairs(groups) do
for key, val in pairs(gargs) do group[key] = val end
group[0] = gid
model.args = group
groups[gid] = ctx.frame:expandTemplate(model)
end
ctx.params = groups
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|parsing|string to parse|[trim flag]|[iteration
-- delimiter setter]|[...]|[key-value delimiter setter]|[...]|pipe to
library.parsing = function (ctx)
local opts = ctx.pipe
if opts[1] == nil then error(modulename ..
', ‘parsing’: No string to parse was provided', 0) end
local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
load_parse_opts(opts, 2)
parse_parameter_string(ctx.params, opts[1], isep, iplain, psep, pplain,
trimnamed, trimunnamed)
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|reinterpreting|parameter to reinterpret|[trim
-- flag]|[iteration delimiter setter]|[...]|[key-value delimiter
-- setter]|[...]|pipe to
library.reinterpreting = function (ctx)
local opts = ctx.pipe
if opts[1] == nil then error(modulename ..
', ‘reinterpreting’: No parameter to reinterpret was provided', 0) end
local isep, iplain, psep, pplain, trimnamed, trimunnamed, argc =
load_parse_opts(opts, 2)
local tbl = ctx.params
local tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
local str = tbl[tmp]
if str ~= nil then
tbl[tmp] = nil
parse_parameter_string(tbl, str, isep, iplain, psep, pplain,
trimnamed, trimunnamed)
end
return context_iterate(ctx, argc)
end
-- Syntax: #invoke:params|mixing_names_and_values|mixing string|pipe to
library.mixing_names_and_values = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
if ctx.pipe[1] == nil then error(modulename ..
', ‘mixing_names_and_values’: No mixing string was provided for parameter names', 0) end
if ctx.pipe[2] == nil then error(modulename ..
', ‘mixing_names_and_values’: No mixing string was provided for parameter values', 0) end
local mix_k = ctx.pipe[1]:match'^%s*(.-)%s*$'
local mix_v = ctx.pipe[2]
local cache = {}
if mix_k == '$@' and mix_v == '$@' then
for _, val in pairs(ctx.params) do cache[val] = val end
elseif mix_k == '$@' and mix_v == '$#' then
for key, val in pairs(ctx.params) do cache[val] = key end
elseif mix_k == '$#' and mix_v == '$#' then
for _, val in pairs(ctx.params) do cache[key] = key end
else
local skel_k, cnv_k, n_parts_k = parse_placeholder_string(mix_k)
local skel_v, cnv_v, n_parts_v = parse_placeholder_string(mix_v)
local tmp
for key, val in pairs(ctx.params) do
tmp = tostring(key)
for idx = 2, n_parts_k, 2 do
if skel_k[idx] then cnv_k[idx] = val else cnv_k[idx] = tmp end
end
for idx = 2, n_parts_v, 2 do
if skel_v[idx] then cnv_v[idx] = val else cnv_v[idx] = tmp end
end
tmp = table.concat(cnv_k)
cache[tonumber(tmp) or tmp] = table.concat(cnv_v)
end
end
ctx.params = cache
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|swapping_names_and_values|pipe to
--[[
library.swapping_names_and_values = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local cache = {}
for key, val in pairs(ctx.params) do cache[val] = key end
ctx.params = cache
return context_iterate(ctx, 1)
end
]]--
-- Syntax: #invoke:params|combining_by_calling|template name|new parameter
-- name|pipe to
library.combining_by_calling = function (ctx)
-- NOTE: `ctx.params` might be the original metatable! As a modifier,
-- this function MUST create a copy of it before returning
local tname = ctx.pipe[1]
if tname ~= nil then tname = tname:match'^%s*(.*%S)'
else error(modulename ..
', ‘combining_by_calling’: No template name was provided', 0) end
local merge_into = ctx.pipe[2]
if merge_into == nil then error(modulename ..
', ‘combining_by_calling’: No parameter name was provided', 0) end
merge_into = tonumber(merge_into) or merge_into:match'^%s*(.-)%s*$'
ctx.params = {
[merge_into] = ctx.frame:expandTemplate{
title = tname,
args = ctx.params
}
}
return context_iterate(ctx, 3)
end
-- Syntax: #invoke:params|snapshotting|pipe to
library.snapshotting = function (ctx)
push_cloned_stack(ctx, ctx.params)
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|remembering|pipe to
library.remembering = function (ctx)
push_cloned_stack(ctx, ctx.oparams)
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|entering_substack|[new]|pipe to
library.entering_substack = function (ctx)
local tbl = ctx.params
local ncurrparent = ctx.n_parents + 1
if ctx.parents == nil then ctx.parents = { tbl }
else ctx.parents[ncurrparent] = tbl end
ctx.n_parents = ncurrparent
if ctx.pipe[1] ~= nil and ctx.pipe[1]:match'^%s*new%s*$' then
ctx.params = {}
return context_iterate(ctx, 2)
end
local currsnap = ctx.n_children
if currsnap > 0 then
ctx.params = ctx.children[currsnap]
ctx.children[currsnap] = nil
ctx.n_children = currsnap - 1
else
local newparams = {}
for key, val in pairs(tbl) do newparams[key] = val end
ctx.params = newparams
end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|pulling|parameter name|pipe to
library.pulling = function (ctx)
local opts = ctx.pipe
if opts[1] == nil then error(modulename ..
', ‘pulling’: No parameter to pull was provided', 0) end
local parent
local tmp = ctx.n_parents
if tmp < 1 then parent = ctx.oparams else parent = ctx.parents[tmp] end
tmp = tonumber(opts[1]) or opts[1]:match'^%s*(.-)%s*$'
if parent[tmp] ~= nil then ctx.params[tmp] = parent[tmp] end
return context_iterate(ctx, 2)
end
-- Syntax: #invoke:params|detaching_substack|pipe to
library.detaching_substack = function (ctx)
local ncurrparent = ctx.n_parents
if ncurrparent < 1 then error(modulename ..
', ‘detaching_substack’: No substack has been created', 0) end
local parent = ctx.parents[ncurrparent]
for key in pairs(ctx.params) do parent[key] = nil end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|leaving_substack|pipe to
library.leaving_substack = function (ctx)
local ncurrparent = ctx.n_parents
if ncurrparent < 1 then error(modulename ..
', ‘leaving_substack’: No substack has been created', 0) end
local currsnap = ctx.n_children + 1
if ctx.children == nil then ctx.children = { ctx.params }
else ctx.children[currsnap] = ctx.params end
ctx.params = ctx.parents[ncurrparent]
ctx.parents[ncurrparent] = nil
ctx.n_parents = ncurrparent - 1
ctx.n_children = currsnap
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|merging_substack|pipe to
library.merging_substack = function (ctx)
local ncurrparent = ctx.n_parents
if ncurrparent < 1 then error(modulename ..
', ‘merging_substack’: No substack has been created', 0) end
local parent = ctx.parents[ncurrparent]
local child = ctx.params
ctx.params = parent
ctx.parents[ncurrparent] = nil
ctx.n_parents = ncurrparent - 1
for key, val in pairs(child) do parent[key] = val end
return context_iterate(ctx, 1)
end
-- Syntax: #invoke:params|flushing|pipe to
library.flushing = function (ctx)
if ctx.n_children < 1 then error(modulename ..
', ‘flushing’: There are no substacks to flush', 0) end
local parent = ctx.params
local currsnap = ctx.n_children
for key, val in pairs(ctx.children[currsnap]) do parent[key] = val end
ctx.children[currsnap] = nil
ctx.n_children = currsnap - 1
return context_iterate(ctx, 1)
end
--[[ Functions ]]--
-----------------------------
-- Syntax: #invoke:params|count
library.count = function (ctx)
-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
local retval = 0
for _ in ctx.iterfunc(ctx.params) do retval = retval + 1 end
if ctx.subset == -1 then retval = retval - #ctx.params end
ctx.text = retval
return false
end
-- Syntax: #invoke:args|concat_and_call|template name|[prepend 1]|[prepend 2]
-- |[...]|[item n]|[named item 1=value 1]|[...]|[named item n=value
-- n]|[...]
library.concat_and_call = function (ctx)
-- NOTE: `ctx.params` might be the original metatable!
local opts = ctx.pipe
local tname
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(modulename ..
', ‘concat_and_call’: No template name was provided', 0) end
remove_numeric_keys(opts, 1, 1)
ctx.text = ctx.frame:expandTemplate{
title = tname,
args = concat_params(ctx)
}
return false
end
-- Syntax: #invoke:args|concat_and_invoke|module name|function name|[prepend
-- 1]|[prepend 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named
-- item n=value n]|[...]
library.concat_and_invoke = function (ctx)
-- NOTE: `ctx.params` might be the original metatable!
local opts = ctx.pipe
local mname
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if mname == nil then error(modulename ..
', ‘concat_and_invoke’: No module name was provided', 0) end
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(modulename ..
', ‘concat_and_invoke’: No function name was provided', 0) end
remove_numeric_keys(opts, 1, 2)
ctx.text = invoke(mname, fname, concat_params(ctx))
return false
end
-- Syntax: #invoke:args|concat_and_magic|parser function|[prepend 1]|[prepend
-- 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named item n=
-- value n]|[...]
library.concat_and_magic = function (ctx)
-- NOTE: `ctx.params` might be the original metatable!
local opts = ctx.pipe
local magic
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if magic == nil then error(modulename ..
', ‘concat_and_magic’: No parser function was provided', 0) end
remove_numeric_keys(opts, 1, 1)
ctx.text = ctx.frame:callParserFunction(magic, concat_params(ctx))
return false
end
-- Syntax: #invoke:params|value_of|parameter name
library.value_of = function (ctx)
-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
local opts = ctx.pipe
local kstr
if opts[1] ~= nil then kstr = opts[1]:match'^%s*(.*%S)' end
if kstr == nil then error(modulename ..
', ‘value_of’: No parameter name was provided', 0) end
local knum = tonumber(kstr)
local len = #ctx.params -- No worries: unused when in first position
local val = ctx.params[knum or kstr]
if val ~= nil and (
ctx.subset ~= -1 or knum == nil or knum > len or knum < 1
) and (
ctx.subset ~= 1 or (knum ~= nil and knum <= len and knum > 0)
) then
ctx.text = (ctx.header or '') .. val .. (ctx.footer or '')
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|list
library.list = function (ctx)
-- NOTE: `ctx.pipe` might be the original metatable!
local kvs = ctx.pairsep or ''
local pps = ctx.itersep or ''
local ret = {}
local nss = 0
flush_params(
ctx,
function (key, val)
ret[nss + 1] = pps
ret[nss + 2] = key
ret[nss + 3] = kvs
ret[nss + 4] = val
nss = nss + 4
end
)
if nss > 0 then
if nss > 4 and ctx.lastsep ~= nil then
ret[nss - 3] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|list_values
library.list_values = function (ctx)
-- NOTE: `ctx.pipe` might be the original metatable!
-- NOTE: `library.coins()` and `library.unique_coins()` rely on us
local pps = ctx.itersep or ''
local ret = {}
local nss = 0
flush_params(
ctx,
function (key, val)
ret[nss + 1] = pps
ret[nss + 2] = val
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|coins|[first coin = value 1]|[second coin = value
-- 2]|[...]|[last coin = value N]
library.coins = function (ctx)
-- NOTE: `ctx.pipe` might be the original metatable!
local opts = ctx.pipe
local tbl = ctx.params
for key, val in pairs(tbl) do tbl[key] = opts[tonumber(val) or val] end
return library.list_values(ctx)
end
-- Syntax: #invoke:params|unique_coins|[first coin = value 1]|[second coin =
-- value 2]|[...]|[last coin = value N]
library.unique_coins = function (ctx)
local opts = ctx.pipe
local tbl = ctx.params
local tmp
for key, val in pairs(tbl) do
tmp = tonumber(val) or val
tbl[key] = opts[tmp]
opts[tmp] = nil
end
return library.list_values(ctx)
end
-- Syntax: #invoke:params|for_each|wikitext
library.for_each = function (ctx)
-- NOTE: `ctx.pipe` might be the original metatable!
local txt = ctx.pipe[1] or ''
local pps = ctx.itersep or ''
local ret = {}
local nss = 0
local skel, cnv, n_parts = parse_placeholder_string(txt)
flush_params(
ctx,
function (key, val)
for idx = 2, n_parts, 2 do
if skel[idx] then cnv[idx] = val
else cnv[idx] = tostring(key) end
end
ret[nss + 1] = pps
ret[nss + 2] = table.concat(cnv)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|call_for_each|template name|[append 1]|[append 2]
-- |[...]|[append n]|[named param 1=value 1]|[...]|[named param
-- n=value n]|[...]
library.call_for_each = function (ctx)
local opts = ctx.pipe
local tname
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(modulename ..
', ‘call_for_each’: No template name was provided', 0) end
local model = { title = tname, args = opts }
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
table.insert(opts, 1, true)
flush_params(
ctx,
function (key, val)
opts[1] = key
opts[2] = val
ret[nss + 1] = ccs
ret[nss + 2] = ctx.frame:expandTemplate(model)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|invoke_for_each|module name|module function|[append
-- 1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...]
-- |[named param n=value n]|[...]
library.invoke_for_each = function (ctx)
local opts = ctx.pipe
local mname
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if mname == nil then error(modulename ..
', ‘invoke_for_each’: No module name was provided', 0) end
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(modulename ..
', ‘invoke_for_each’: No function name was provided', 0) end
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
flush_params(
ctx,
function (key, val)
opts[1] = key
opts[2] = val
ret[nss + 1] = ccs
ret[nss + 2] = invoke(mname, fname, opts)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|magic_for_each|parser function|[append 1]|[append 2]
-- |[...]|[append n]|[named param 1=value 1]|[...]|[named param
-- n=value n]|[...]
library.magic_for_each = function (ctx)
local opts = ctx.pipe
local magic
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if magic == nil then error(modulename ..
', ‘magic_for_each’: No parser function was provided', 0) end
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
table.insert(opts, 1, true)
flush_params(
ctx,
function (key, val)
opts[1] = key
opts[2] = val
ret[nss + 1] = ccs
ret[nss + 2] = ctx.frame:callParserFunction(magic, opts)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|call_for_each_value|template name|[append 1]|[append
-- 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param
-- n=value n]|[...]
library.call_for_each_value = function (ctx)
local opts = ctx.pipe
local tname
if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
if tname == nil then error(modulename ..
', ‘call_for_each_value’: No template name was provided', 0) end
local model = { title = tname, args = opts }
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
flush_params(
ctx,
function (key, val)
opts[1] = val
ret[nss + 1] = ccs
ret[nss + 2] = ctx.frame:expandTemplate(model)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|invoke_for_each_value|module name|[append 1]|[append
-- 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param
-- n=value n]|[...]
library.invoke_for_each_value = function (ctx)
local opts = ctx.pipe
local mname
local fname
if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
if mname == nil then error(modulename ..
', ‘invoke_for_each_value’: No module name was provided', 0) end
if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
if fname == nil then error(modulename ..
', ‘invoke_for_each_value’: No function name was provided', 0) end
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
remove_numeric_keys(opts, 1, 1)
flush_params(
ctx,
function (key, val)
opts[1] = val
ret[nss + 1] = ccs
ret[nss + 2] = invoke(mname, fname, opts)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|magic_for_each_value|parser function|[append 1]
-- |[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named
-- param n=value n]|[...]
library.magic_for_each_value = function (ctx)
local opts = ctx.pipe
local magic
if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
if magic == nil then error(modulename ..
', ‘magic_for_each_value’: No parser function was provided', 0) end
local ccs = ctx.itersep or ''
local ret = {}
local nss = 0
flush_params(
ctx,
function (key, val)
opts[1] = val
ret[nss + 1] = ccs
ret[nss + 2] = ctx.frame:callParserFunction(magic, opts)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
-- Syntax: #invoke:params|call_for_each_group|template name|[append 1]|[append
-- 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param
-- n=value n]|[...]
library.call_for_each_group = function (ctx)
-- NOTE: `ctx.pipe` and `ctx.params` might be the original metatables!
local opts = ctx.pipe
local tmp
if opts[1] ~= nil then tmp = opts[1]:match'^%s*(.*%S)' end
if tmp == nil then error(modulename ..
', ‘call_for_each_group’: No template name was provided', 0) end
local model = { title = tmp }
local ccs = ctx.itersep or ''
local nss = 0
local ret = {}
opts = {}
for key, val in pairs(ctx.pipe) do
if type(key) == 'number' then opts[key - 1] = val
else opts[key] = val end
end
ctx.pipe = opts
ctx.params = make_groups(ctx.params)
flush_params(
ctx,
function (gid, group)
for key, val in pairs(opts) do group[key] = val end
group[0] = gid
model.args = group
ret[nss + 1] = ccs
ret[nss + 2] = ctx.frame:expandTemplate(model)
nss = nss + 2
end
)
if nss > 0 then
if nss > 2 and ctx.lastsep ~= nil then
ret[nss - 1] = ctx.lastsep
end
ret[1] = ctx.header or ''
if ctx.footer ~= nil then ret[nss + 1] = ctx.footer end
ctx.text = table.concat(ret)
return false
end
ctx.text = ctx.ifngiven or ''
return false
end
--- ---
--- PUBLIC ENVIRONMENT ---
--- ________________________________ ---
--- ---
--[[ First-position-only modifiers ]]--
---------------------------------------
-- Syntax: #invoke:params|new|pipe to
static_iface.new = function (frame)
local ctx = context_new(frame:getParent())
ctx.pipe = copy_or_ref_table(frame.args, false)
ctx.params = {}
main_loop(ctx, context_iterate(ctx, 1))
return ctx.text
end
--[[ First-position-only functions ]]--
---------------------------------------
-- Syntax: #invoke:params|self
static_iface.self = function (frame)
return frame:getParent():getTitle()
end
--[[ Public metatable of functions ]]--
---------------------------------------
return setmetatable({}, {
__index = function (_, query)
local fname = query:match'^%s*(.*%S)'
if fname == nil then error(modulename ..
': You must specify a function to call', 0) end
local func = static_iface[fname]
if func ~= nil then return func end
func = library[fname]
if func == nil then error(modulename ..
': The function ‘' .. fname .. '’ does not exist', 0) end
return function (frame)
local ctx = context_new(frame:getParent())
ctx.pipe = copy_or_ref_table(frame.args, refpipe[fname])
ctx.params = copy_or_ref_table(ctx.oparams, refparams[fname])
main_loop(ctx, func)
return ctx.text
end
end
})