Module:parameters: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
local export = {} | local export = {} | ||
local function track(page, calling_module, calling_function, param_name) | |||
return true | |||
end | |||
function export.process(args, params, return_unknown, calling_module, calling_function) | |||
function export.process(args, params, return_unknown) | |||
local args_new = {} | 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 | -- Process parameters for specific properties | ||
Line 20: | Line 23: | ||
for name, param in pairs(params) do | for name, param in pairs(params) do | ||
if param.required then | if param.required then | ||
if param.alias_of then | |||
track("required alias", calling_module, calling_function, name) | |||
end | |||
required[name] = true | 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 | end | ||
if param.list then | 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 | local key = name | ||
if type(name) == "string" then | if type(name) == "string" then | ||
Line 68: | Line 86: | ||
end | end | ||
--Process required changes to `params` | --Process required changes to `params`. | ||
for _, name in ipairs(names_with_equal_sign) do | 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 | end | ||
-- Process the arguments | -- Process the arguments | ||
local args_unknown = {} | local args_unknown = {} | ||
local max_index | |||
for name, val in pairs(args) do | for name, val in pairs(args) do | ||
Line 111: | Line 141: | ||
-- If no index was found, use 1 as the default index. | -- If no index was found, use 1 as the default index. | ||
-- This makes list parameters like g, g2, g3 put g at index 1. | -- This makes list parameters like g, g2, g3 put g at index 1. | ||
index = index or 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. | -- If the argument is not in the list of parameters, trigger an error. | ||
Line 130: | Line 161: | ||
if val == "" and not param.allow_empty then | if val == "" and not param.allow_empty then | ||
val = nil | 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 {{l|en|term|}}, but not {{l|en||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 | end | ||
Line 137: | Line 187: | ||
elseif param.type == "number" then | elseif param.type == "number" then | ||
val = tonumber(val) | 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 | end | ||
Line 153: | Line 206: | ||
-- Store the highest index we find. | -- Store the highest index we find. | ||
args_new[name].maxindex = math.max(index, args_new[name].maxindex) | 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 | elseif args[param.alias_of] == nil then | ||
if params[param.alias_of] and params[param.alias_of].list then | if params[param.alias_of] and params[param.alias_of].list then | ||
Line 185: | Line 242: | ||
-- The required table should now be empty. | -- The required table should now be empty. | ||
-- If any entry remains, trigger an error, unless we're in the template namespace. | -- If any entry remains, trigger an error, unless we're in the template namespace. | ||
if mw.title.getCurrentTitle(). | if mw.title.getCurrentTitle().namespace ~= 10 then | ||
local list = {} | local list = {} | ||
for name, param in pairs(required) do | for name, param in pairs(required) do | ||
table.insert(list, name) | table.insert(list, name) | ||
end | end | ||
if #list > 0 then | |||
error('The parameters "' .. mw.text.listToText(list, '", "', '" and "') .. '" are required.', 2) | |||
error('The parameters "' .. mw.text.listToText(list, '", "', '" | |||
end | end | ||
end | end | ||
Line 204: | Line 255: | ||
for name, val in pairs(args_new) do | for name, val in pairs(args_new) do | ||
if type(val) == "table" and not params[name].allow_holes then | if type(val) == "table" and not params[name].allow_holes then | ||
args_new[name] = remove_holes(val) | args_new[name] = require("Module:parameters/remove_holes")(val) | ||
end | end | ||
end | end |
Revision as of 14:44, 3 May 2023
- The following documentation is located at Module:parameters/doc.[edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local function track(page, calling_module, calling_function, param_name)
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 {{l|en|term|}}, but not {{l|en||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 = not (not val or val == "" or val == "0" or val == "no" or val == "n" or val == "false")
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