Module:Parameters

local export = {}

local function track(page, calling_module, calling_function, param_name) local track = require("Module:debug/track") local tracking_page = "parameters/" .. page -- Cascades down in specificity, as each level is a prerequisite for the next. track(tracking_page) if calling_module then track(tracking_page .. "/" .. calling_module) if calling_function then track(tracking_page .. "/" .. calling_module .. "/" .. calling_function) if param_name then track(tracking_page .. "/" .. calling_module .. "/" .. calling_function .. "/" .. param_name) end end end return true end

function export.process(args, params, return_unknown, calling_module, calling_function) local args_new = {} if not calling_module then track("no calling module") end if not calling_function then track("no calling function", calling_module) end -- Process parameters for specific properties local required = {} local patterns = {} local names_with_equal_sign = {} local list_from_index = nil for name, param in pairs(params) do		if param.required then if param.alias_of then track("required alias", calling_module, calling_function, name) end required[name] = true end if name == 1 and param.no_lang_code then if not params["notlangcode"] then error("The parameter \"notlangcode\" must be enabled for this template.", 2) elseif not args["notlangcode"] and require("Module:languages").getByCode(args[name]) then error("The parameter \"" .. name .. "\" should not be a language code.", 2) end end if param.list then -- A helper function to escape magic characters in a string -- Magic characters: ^$%.[]*+-? local plain = require("Module:string/pattern_escape")

local key = name if type(name) == "string" then key = string.gsub(name, "=", "") end if param.default ~= nil then args_new[key] = {param.default, maxindex = 1} else args_new[key] = {maxindex = 0} end if type(param.list) == "string" then -- If the list property is a string, then it represents the name -- to be used as the prefix for list items. This is for use with lists -- where the first item is a numbered parameter and the -- subsequent ones are named, such as 1, pl2, pl3. if string.find(param.list, "=") then patterns["^" .. string.gsub(plain(param.list), "=", "(%%d+)") .. "$"] = name else patterns["^" .. plain(param.list) .. "(%d+)$"] = name end elseif type(name) == "number" then -- If the name is a number, then all indexed parameters from -- this number onwards go in the list. list_from_index = name else if string.find(name, "=") then patterns["^" .. string.gsub(plain(name), "=", "(%%d+)") .. "$"] = string.gsub(name, "=", "") else patterns["^" .. plain(name) .. "(%d+)$"] = name end end if string.find(name, "=") then -- DO NOT SIDE-EFFECT A TABLE WHILE ITERATING OVER IT. -- Some elements may be skipped or processed twice if you do. -- Instead, track the changes we want to make to `params`, and -- do them after the iteration over `params` is done. table.insert(names_with_equal_sign, name) end elseif param.default ~= nil then args_new[name] = param.default end end

--Process required changes to `params`. if #names_with_equal_sign > 0 then local m_params_data = calling_module and mw.loadData("Module:parameters/data")[calling_module] -- If there is a ready-made version in the data module, use that. if m_params_data and m_params_data[calling_function .. "_no_equals"] then params = m_params_data[calling_function .. "_no_equals"] -- Otherwise, shallow copy the params table and substitute the keys. else params = require("Module:table").shallowcopy(params) for _, name in ipairs(names_with_equal_sign) do				track("name with equals", calling_module, calling_function, name) params[string.gsub(name, "=", "")] = params[name] params[name] = nil end end end

-- Process the arguments local args_unknown = {} local max_index for name, val in pairs(args) do		local index = nil if type(name) == "number" then if list_from_index ~= nil and name >= list_from_index then index = name - list_from_index + 1 name = list_from_index end else -- Does this argument name match a pattern? for pattern, pname in pairs(patterns) do				index = mw.ustring.match(name, pattern) -- It matches, so store the parameter name and the -- numeric index extracted from the argument name. if index then index = tonumber(index) name = pname break end end end local param = params[name] -- If a parameter without the trailing index was found, and -- require_index is set on the param, set the param to nil to treat it -- as if it isn't recognized. if not index and param and param.require_index then param = nil end -- If no index was found, use 1 as the default index. -- This makes list parameters like g, g2, g3 put g at index 1. -- If `separate_no_index` is set, then use 0 as the default instead. index = index or (param and param.separate_no_index and 0) or 1 -- If the argument is not in the list of parameters, trigger an error. -- return_unknown suppresses the error, and stores it in a separate list instead. if not param then if return_unknown then args_unknown[name] = val else error("The parameter \"" .. name .. "\" is not used by this template.", 2) end else -- Remove leading and trailing whitespace unless allow_whitespace is true. if not param.allow_whitespace then val = mw.text.trim(val) end -- Empty string is equivalent to nil unless allow_empty is true. if val == "" and not param.allow_empty then val = nil -- Track empty parameters, unless (1) allow_empty is set or (2) they're numbered parameters where a higher numbered parameter is also in use (e.g. track, but not term). if type(name) == "number" and not max_index then -- Find the highest numbered parameter that's in use/an empty string, as we don't want parameters like 500= to mean we can't track any empty parameters with a lower index than 500. local max_contiguous_index = 0 while args[max_contiguous_index + 1] do						max_contiguous_index = max_contiguous_index + 1 end if max_contiguous_index > 0 then for name, val in pairs(args) do							if type(name) == "number" and name > 0 and name <= max_contiguous_index and ((not max_index) or name > max_index) and val ~= "" then max_index = name end end end max_index = max_index or 0 end if type(name) ~= "number" or name > max_index then track("empty parameter", calling_module, calling_function, name) end end -- Convert to proper type if necessary. if param.type == "boolean" then val = require("Module:yesno")(val, true) elseif param.type == "number" then val = tonumber(val) elseif param.type then track("unrecognized type", calling_module, calling_function, name) track("unrecognized type/" .. tostring(param.type), calling_module, calling_function, name) end -- Can't use "if val" alone, because val may be a boolean false. if val ~= nil then -- Mark it as no longer required, as it is present. required[param.alias_of or name] = nil -- Store the argument value. if param.list then -- If the parameter is an alias of another, store it as the original, -- but avoid overwriting it; the original takes precedence. if not param.alias_of then args_new[name][index] = val -- Store the highest index we find. args_new[name].maxindex = math.max(index, args_new[name].maxindex) if args_new[name][0] then args_new[name].default = args_new[name][0] args_new[name][0] = nil end elseif args[param.alias_of] == nil then if params[param.alias_of] and params[param.alias_of].list then args_new[param.alias_of][index] = val -- Store the highest index we find. args_new[param.alias_of].maxindex = math.max(index, args_new[param.alias_of].maxindex) else args_new[param.alias_of] = val end end else -- If the parameter is an alias of another, store it as the original, -- but avoid overwriting it; the original takes precedence. if not param.alias_of then args_new[name] = val elseif args[param.alias_of] == nil then if params[param.alias_of] and params[param.alias_of].list then args_new[param.alias_of][1] = val -- Store the highest index we find. args_new[param.alias_of].maxindex = math.max(1, args_new[param.alias_of].maxindex) else args_new[param.alias_of] = val end end end end end end -- The required table should now be empty. -- If any entry remains, trigger an error, unless we're in the template namespace. if mw.title.getCurrentTitle.namespace ~= 10 then local list = {} for name, param in pairs(required) do			table.insert(list, name) end if #list > 0 then error('The parameters "' .. mw.text.listToText(list, '", "', '" and "') .. '" are required.', 2) end end -- Remove holes in any list parameters if needed. for name, val in pairs(args_new) do		if type(val) == "table" and not params[name].allow_holes then args_new[name] = require("Module:parameters/remove_holes")(val) end end if return_unknown then return args_new, args_unknown else return args_new end end

return export