Module:Parameter validation explained

local util =

local function _readTemplateData(templateName) local title = mw.title.makeTitle(0, templateName) local templateContent = title and title.exists and title:getContent -- template's raw content local capture = templateContent and mw.ustring.match(templateContent, '(.*)') -- templatedata as text-- capture = capture and mw.ustring.gsub(capture, '"(%d+)"', tonumber) -- convert "1": to 1: . frame.args uses numerical indexes for order-based params. local trailingComma = capture and mw.ustring.find(capture, ',%s*[%]%}]') -- look for,] or,} : jsonDecode allows it, but it's verbotten in json if capture and not trailingComma then return pcall(mw.text.jsonDecode, capture) end return falseend

local function readTemplateData(templateName) if type(templateName)

'string' then templateName = end if type(templateName)

"table" then for _, name in ipairs(templateName) do local td, result = _readTemplateData(name) if td then return result end end end return nilend

-- this is the function to be called by other modules. it expects the frame, and then an optional list of subpages, e.g. .-- if second parameter is nil, only tempalte page will be searched for templatedata.function calculateViolations(frame, subpages)-- used for parameter type validy test. keyed by TD 'type' string. values are function(val) returning bool. local type_validators = function compatible(typ, val) local func = type_validators[typ] return type(func) ~= 'function' or util.empty(val) or func(val) end local t_frame = frame:getParent local t_args, template_name = t_frame.args, t_frame:getTitle template_name = mw.ustring.gsub(template_name, '/sandbox', , 1) local td_source = util.build_namelist(template_name, subpages) if frame.args['td_source'] then table.insert(td_source, frame.args['td_source']) end

local templatedata = readTemplateData(td_source) local td_params = templatedata and templatedata.params local all_aliases, all_series =,

if not td_params then return end -- from this point on, we know templatedata is valid.

local res = -- before returning to caller, we'll prune empty tables

-- allow for aliases for x, p in pairs(td_params) do for y, alias in ipairs(p.aliases or) do p['primary'] = x td_params[x] = p all_aliases[alias] = p if tonumber(alias) then all_aliases[tonumber(alias)] = p end end end

-- handle undeclared and deprecated local already_seen = local series = frame.args['series'] for p_name, value in pairs(t_args) do local tp_param, noval, numeric, table_name = td_params[p_name] or all_aliases[p_name], util.empty(value), tonumber(p_name) local hasval = not noval

if not tp_param and series then -- 2nd chance. check to see if series for s_name, p in pairs(td_params) do if mw.ustring.match(p_name, '^' .. s_name .. '%d+' .. '$') then -- mw.log('found p_name '.. p_name .. ' s_name:' .. s_name, ' p is:', p) debugging series support tp_param = p end -- don't bother breaking. td always correct. end end

if not tp_param then -- not in TD: this is called undeclared -- calculate the relevant table for this undeclared parameter, based on parameter and value types table_name = noval and numeric and 'empty-undeclared-numeric' or noval and not numeric and 'empty-undeclared' or hasval and numeric and 'undeclared-numeric' or 'undeclared' -- tzvototi nishar. else -- in td: test for deprecation and mistype. if deprecated, no further tests table_name = tp_param.deprecated and hasval and 'deprecated' or tp_param.deprecated and noval and 'empty-deprecated' or not compatible(tp_param.type, value) and 'incompatible' or not series and already_seen[tp_param] and hasval and 'duplicate'

if hasval and table_name ~= 'duplicate' then already_seen[tp_param] = p_name end end -- report it. if table_name then res[table_name] = res[table_name] or if table_name

'duplicate' then local primary_param = tp_param['primary'] local primaryData = res[table_name][primary_param] if not primaryData then primaryData = table.insert(primaryData, already_seen[tp_param]) end table.insert(primaryData, p_name) res[table_name][primary_param] = primaryData else res[table_name][p_name] = value end end end

-- check for empty/missing parameters declared "required" for p_name, param in pairs(td_params) do if param.required and util.empty(t_args[p_name]) then local is_alias for _, alias in ipairs(param.aliases or) do is_alias = is_alias or not util.empty(t_args[alias]) end if not is_alias then res['empty-required'] = res['empty-required'] or res['empty-required'][p_name] = end end end mw.logObject(res) return resend

-- wraps report in hidden framefunction wrapReport(report, template_name, options) mw.logObject(report) if util.empty(report) then return end local naked = mw.title.new(template_name)['text'] naked = mw.ustring.gsub(naked, 'Infobox', 'infobox', 1) report = (options['wrapper-prefix'] or "

") .. report .. (options['wrapper-suffix'] or "

") report = mw.ustring.gsub(report, 'tname_naked', naked) report = mw.ustring.gsub(report, 'templatename', template_name)

return reportend

-- this is the "user" version, called with returns a string, as defined by the options parameterfunction validateParams(frame) local options, report, template_name = util.extract_options(frame), , frame:getParent:getTitle

local ignore = function(p_name) for _, pattern in ipairs(options['ignore'] or) do if mw.ustring.match(p_name, '^' .. pattern .. '$') then return true end end return false end

local replace_macros = function(error_type, s, param_names) function concat_and_escape(t, sep) sep = sep or ', ' local s = table.concat(t, sep) return (mw.ustring.gsub(s, '%%', '%%%%')) end if s and (type(param_names)

'table') then local k_ar, kv_ar =, for k, v in pairs(param_names) do table.insert(k_ar, k) if type(v)

'table' then v = table.concat(v, ', ') end if error_type

'duplicate' then table.insert(kv_ar, v) else table.insert(kv_ar, k .. ': ' .. v) end end s = mw.ustring.gsub(s, 'paramname', concat_and_escape(k_ar)) s = mw.ustring.gsub(s, 'paramandvalue', concat_and_escape(kv_ar, ' AND '))

if mw.getCurrentFrame:preprocess("") ~= "" then s = mw.ustring.gsub(s, "

", "", 1) end end return s end

local report_params = function(key, param_names) local res = replace_macros(key, options[key], param_names) res = frame:preprocess(res or ) report = report .. (res or ) return res end

-- no option no work. if util.table_empty(options) then return end

-- get the errors. local violations = calculateViolations(frame, options['doc-subpage']) -- special request of bora: use skip_empty_numeric if violations['empty-undeclared-numeric'] then for i = 1, tonumber(options['skip-empty-numeric']) or 0 do violations['empty-undeclared-numeric'][i] = nil end end -- handle ignore list, and prune empty violations - in that order! local offenders = 0 for name, tab in pairs(violations) do -- remove ignored parameters from all violations for pname in pairs(tab) do if ignore(pname) then tab[pname] = nil end end -- prune empty violations if util.table_empty(tab) then violations[name] = nil end -- WORK IS DONE. report the errors. -- if report then count it. if violations[name] and report_params(name, tab) then offenders = offenders + 1 end end

if offenders > 1 then report_params('multiple') end if offenders ~= 0 then report_params('any') end -- could have tested for empty(report), but since we count them anyway... return wrapReport(report, template_name, options)end

return