Module:ForArgs
This module allows iteration over numbered or unnumbered arguments. It works in a similar manner as forargs
and fornumargs
in mw:Extension:Loops, except that instead of inserting the values of each key and value into the variable of those names, it performs a direct search-and-replace on the provided block value for the names of the key and value with the values of the key and value. (This is due to the differences in operation between Lua modules and parser functions - the former, which this is, are incapable of modifying variables while running, among other things).
forNumArgs
Iterates only over numbered arguments. Syntax:
{{#invoke:ForArgs |forNumArgs |key |value |pattern |not = <arg names> |sep = <separator>}}
For parameter descriptions, see forArgs below.
Note that this function will attempt to detect and iterate over any empty numerical arguments. There are limitations on this: It can only detect empty numerical arguments preceding non-empty numerical arguments - so for example, if the parent page was called with {{Some Template||abc||def|}}
, where arguments 1, 3, and 5 were blank, only arguments 1 and 3 will be detected because they precede the non-empty arguments 2 and 4. Additionally, scanning for empty arguments will be limited to ten in a row for each gap between non-empty arguments, so for {{Other Template|one||||||||||||thirteen||fifteen}}
, where args 2-12 and 14 are empty, only arguments 3-12 and 14 will be detected.
forNamedArgs
Iterates over all non-numbered arguments. Syntax:
{{#invoke:ForArgs |forNamedArgs |prefix |key |value |pattern |not = <arg names> |sep = <separator>}}
For parameter descriptions, see forArgs below.
forArgs
Iterates over all arguments. Syntax:
{{#invoke:ForArgs |forArgs |prefix |key |value |pattern |not = <arg names> |sep = <separator>}}
- prefix
A prefix to filter arguments by. Only arguments whose name begins with this prefix value are iterated over. Leave blank to include all arguments.
- key
The key identifier to use. For each iteration, every instance of this value in the pattern will be replaced with the argument name. Leave blank to ignore.
- value
The value identifier to use. For each iteration, every instance of this value in the pattern will be replaced with the argument value. Leave blank to ignore.
- pattern
The pattern to be printed. For each iteration, this is the value which will be printed (with key and value identifiers replaced with the argument name and argument value, respectively).
- sep
Optional. The separator to print between each iteration.
- not
Optional. A comma-separated list of argument names to ignore.
Example
If the page "Template:Loops Test" contains
{{#invoke:ForArgs|forArgs
| arg
| key
| value
| key = value<br/>
}}
then the wiki markup
{{Loops Test
| arg1=val1
| spam=spammity
| arg5=val5
| argument=value
}}
produces:
1 = val1
5 = val5
ument = value
local p = {}
local norm = require("Module:ProcessArgs").norm
local str_escape = function(str)
return str:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
end
-- pattern-less substring replacement (like string.gsub but without patterns)
local str_replace = function (str, this, that)
local newThat = string.gsub(that, "%%", "%%%%")
return string.gsub(str, str_escape(this), newThat) -- apparently, only % needs to be escaped here
end
local setupArgs = function(frame)
local args = norm(frame.args)
local parent = frame:getParent() or {}
local parentArgs = norm(parent.args or {})
args['#at'] = 1
return args, parent, parentArgs
end
-- Helper function for determining if a given flag is set
-- in a function's arguments
local isFlagSet = function(flag)
if flag then
local word = tostring(flag)
return #word > 0 and tostring(flag) ~= 'false' and tostring(flag):sub(1, 1):lower() ~= 'n'
else
return false
end
end
-- The workhorse of the for*Args family of functions
-- Arguments:
-- frame: the MediaWiki frame or pseudoframe
-- args: the argument list to {{#invoke:ForArgs}} (i.e. to the child frame)
-- parentArgs: the argument list to the parent frame
-- filter: a filter function, which determines which argument keys to
-- iterate over. This can be nil to always process args which meet the
-- other requirements (see the "prefix" parameter). If not nil, this
-- should be a function of the form
-- filter(k, v) -> (hit, keys, values)
-- where
-- k is the incoming argument key to check
-- v is the incoming argument value to check
-- and where
-- hit indicates whether or not to process the argument (true/false)
-- keys is a list of key strings to process for this argument
-- values is a list of value strings to process for this argument
-- The final two return values (keys, values) are optional and can be
-- omitted, which is equivalent to specifying keys={k} and values={v}.
-- In this way, filters can specify additional/different key-value pairs
-- to be processed than what are actually present in parentArgs.
-- Note that keys and values will be iterated over using ipairs()!
local doForArgs = function(frame, args, parentArgs, filter)
local argsAt = tonumber(args['#at'])
local prefix, key, value, str = '', '', '', nil
if argsAt >= 1 then prefix = args[argsAt] or '' end
if argsAt >= 0 then key = args[argsAt + 1] or '' end
if argsAt >= -1 then value = args[argsAt + 2] or '' end
if argsAt >= -2 then str = args[argsAt + 3] end
if not str then return end
local sep = args.sep or ''
local nots = {}
if args['not'] and #args['not'] > 0 then
for str in string.gmatch(args['not'], '([^,]+)') do
table.insert(nots, str)
end
end
local result = ''
for k,v in pairs(parentArgs) do
if #prefix < 1 or string.sub(k, 1, #prefix) == prefix then
local filterHit, filterKeyList, filterValueList = true, nil
if filter and type(filter) == "function" then
filterHit, filterKeyList, filterValueList = filter(k, v)
end
if filterHit then
if not filterKeyList or type(filterKeyList) ~= "table" then filterKeyList = {k} end
if not filterValueList or type(filterValueList) ~= "table" then filterValueList = {v} end
for qi,qk in ipairs(filterKeyList) do
local filteredKey = tostring(qk)
local filteredValue = tostring(filterValueList[qi])
local passed = true
for _,str in ipairs(nots) do passed = passed and filteredKey ~= str end
if passed then
local line = str
if #key > 0 then line = str_replace(line, key, filteredKey) end
if #value > 0 then line = str_replace(line, value, filteredValue) end
if #result > 0 then result = result .. sep end
result = result .. frame:preprocess(line)
end
end
end
end
end
return result
end
function p.forNumArgs(frame)
local args, parent, parentArgs = setupArgs(frame)
args['#at'] = 0
local start = 1
local hitlist = {}
if args.start and tonumber(args.start) then
start = math.max(1, tonumber(args.start))
end
return doForArgs(frame, args, parentArgs, function (k,v)
local i = tonumber(k)
local hit = i and i >= start
local keys, values
if hit then
hitlist[i] = true
keys = {k}
values = {v}
for q=1,10 do
if i-q <= 0 or i-q < start or hitlist[i-q] then break end
table.insert(keys, 1, i-q)
table.insert(values, 1, '')
end
end
return hit, keys, values
end)
end
function p.forArgs(frame)
local args, parent, parentArgs = setupArgs(frame)
return doForArgs(frame, args, parentArgs, nil)
end
function p.forNamedArgs(frame)
local args, parent, parentArgs = setupArgs(frame)
return doForArgs(frame, args, parentArgs, function (k,v)
return not tonumber(k)
end)
end
return p