Module:ForArgs

From Oxygen Not Included Wiki
Jump to navigation Jump to search

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