Module:Params#with name not matching

--- ---

--- 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

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,

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 == 3, 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

-- 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|rotating|pipe to

library.rotating = 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|pipe to

--[[

library.pivoting = function (ctx)

local tbl = ctx.params

local shift = #tbl + 1

if shift < 2 then return library.rotating(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|pipe to

--[[

library.mirroring = 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|pipe to

--[[

library.swapping = 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, targets,

'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, targets,

'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, targets,

'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, targets,

'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)

local model = { title = 'Module:' .. mname, args = margs }

local mfunc = require(model.title)[fname]

if mfunc == nil then error(modulename ..

', ‘mapping_by_invoking’: The function ‘' .. fname ..

'’ does not exist', 0) end

value_maps[looptype](ctx.params, margs, karg, varg, function ()

return tostring(mfunc(ctx.frame:newChild(model)))

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, is_strict, argc, die =

load_replace_args(ctx.pipe, 'mapping_by_replacing')

if die then return context_iterate(ctx, argc) end

local tbl = ctx.params

if is_strict 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|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)

local model = { title = 'Module:' .. mname, args = rargs }

local mfunc = require(model.title)[fname]

if mfunc == nil then error(modulename ..

', ‘renaming_by_invoking’: The function ‘' .. fname ..

'’ does not exist', 0) end

map_names(ctx.params, rargs, karg, varg, looptype, function ()

local tmp = mfunc(ctx.frame:newChild(model))

return tonumber(tmp) or tostring(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, is_strict, argc, die =

load_replace_args(ctx.pipe, 'renaming_by_replacing')

if die then return context_iterate(ctx, argc) end

local tbl = ctx.params

if is_strict 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|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|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)

local mfunc = require('Module:' .. mname)[fname]

if mfunc == nil then error(modulename ..

', ‘concat_and_invoke’: The function ‘' .. fname ..

'’ does not exist', 0) end

ctx.text = mfunc(ctx.frame:newChild{

title = 'Module:' .. fname,

args = 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 model = { title = 'Module:' .. mname, args = opts }

local mfunc = require(model.title)[fname]

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] = mfunc(ctx.frame:newChild(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|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 model = { title = 'Module:' .. mname, args = opts }

local mfunc = require(model.title)[fname]

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] = mfunc(ctx.frame:newChild(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|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

})