https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AParameter_validation%2Fsandbox Module:Parameter validation/sandbox - Revision history 2025-05-25T23:59:40Z Revision history for this page on the wiki MediaWiki 1.45.0-wmf.2 https://en.wikipedia.org/w/index.php?title=Module:Parameter_validation/sandbox&diff=1216634403&oldid=prev SilverLocust: Copying module to sandbox 2024-04-01T04:47:12Z <p>Copying module to sandbox</p> <p><b>New page</b></p><div>local util = {<br /> empty = function( s ) <br /> return s == nil or type( s ) == &#039;string&#039; and mw.text.trim( s ) == &#039;&#039; <br /> end<br /> , <br /> extract_options = function ( frame, optionsPrefix )<br /> optionsPrefix = optionsPrefix or &#039;options&#039; <br /> <br /> local options, n, more = {}<br /> if frame.args[&#039;module_options&#039;] then<br /> local module_options = mw.loadData( frame.args[&#039;module_options&#039;] ) <br /> if type( module_options ) ~= &#039;table&#039; then return {} end<br /> local title = mw.title.getCurrentTitle()<br /> local local_ptions = module_options[ title.namespace ] or module_options[ title.nsText ] or {} <br /> for k, v in pairs( local_ptions ) do options[k] = v end<br /> end<br /> <br /> repeat<br /> ok, more = pcall( mw.text.jsonDecode, frame.args[optionsPrefix .. ( n or &#039;&#039; )] )<br /> if ok and type( more ) == &#039;table&#039; then<br /> for k, v in pairs( more ) do options[k] = v end<br /> end<br /> n = ( n or 0 ) + 1<br /> until not ok<br /> <br /> return options<br /> end<br /> , <br /> build_namelist = function ( template_name, sp )<br /> local res = { template_name }<br /> if sp then<br /> if type( sp ) == &#039;string&#039; then sp = { sp } end<br /> for _, p in ipairs( sp ) do table.insert( res, template_name .. &#039;/&#039; .. p ) end<br /> end<br /> return res<br /> end<br /> ,<br /> table_empty = function( t ) -- normally, test if next(t) is nil, but for some perverse reason, non-empty tables returned by loadData return nil...<br /> if type( t ) ~= &#039;table&#039; then return true end<br /> for a, b in pairs( t ) do return false end<br /> return true<br /> end<br /> ,<br /> }<br /> <br /> local function _readTemplateData( templateName ) <br /> local title = mw.title.makeTitle( 0, templateName ) <br /> local templateContent = title and title.exists and title:getContent() -- template&#039;s raw content<br /> local capture = templateContent and mw.ustring.match( templateContent, &#039;&lt;templatedata%s*&gt;(.*)&lt;/templatedata%s*&gt;&#039; ) -- templatedata as text<br /> -- capture = capture and mw.ustring.gsub( capture, &#039;&quot;(%d+)&quot;&#039;, tonumber ) -- convert &quot;1&quot;: {} to 1: {}. frame.args uses numerical indexes for order-based params.<br /> local trailingComma = capture and mw.ustring.find( capture, &#039;,%s*[%]%}]&#039; ) -- look for ,] or ,} : jsonDecode allows it, but it&#039;s verbotten in json<br /> if capture and not trailingComma then return pcall( mw.text.jsonDecode, capture ) end<br /> return false<br /> end<br /> <br /> local function readTemplateData( templateName )<br /> if type( templateName ) == &#039;string&#039; then <br /> templateName = { templateName, templateName .. &#039;/&#039; .. docSubPage }<br /> end<br /> if type( templateName ) == &quot;table&quot; then<br /> for _, name in ipairs( templateName ) do<br /> local td, result = _readTemplateData( name ) <br /> if td then return result end<br /> end<br /> end<br /> return nil<br /> end<br /> <br /> <br /> -- this is the function to be called by other modules. it expects the frame, and then an optional list of subpages, e.g. { &quot;Documentation&quot; }.<br /> -- if second parameter is nil, only tempalte page will be searched for templatedata.<br /> function calculateViolations( frame, subpages )<br /> -- used for parameter type validy test. keyed by TD &#039;type&#039; string. values are function(val) returning bool.<br /> local type_validators = { <br /> [&#039;number&#039;] = function( s ) return mw.language.getContentLanguage():parseFormattedNumber( s ) end<br /> }<br /> function compatible( typ, val )<br /> local func = type_validators[typ]<br /> return type( func ) ~= &#039;function&#039; or util.empty( val ) or func( val )<br /> end<br /> <br /> local t_frame = frame:getParent()<br /> local t_args, template_name = t_frame.args, t_frame:getTitle()<br /> template_name = mw.ustring.gsub( template_name, &#039;/sandbox&#039;, &#039;&#039;, 1 )<br /> local td_source = util.build_namelist( template_name, subpages )<br /> if frame.args[&#039;td_source&#039;] then<br /> table.insert(td_source, frame.args[&#039;td_source&#039;])<br /> end<br /> <br /> local templatedata = readTemplateData( td_source )<br /> local td_params = templatedata and templatedata.params<br /> local all_aliases, all_series = {}, {}<br /> <br /> if not td_params then return { [&#039;no-templatedata&#039;] = { [&#039;&#039;] = &#039;&#039; } } end<br /> -- from this point on, we know templatedata is valid.<br /> <br /> local res = {} -- before returning to caller, we&#039;ll prune empty tables<br /> <br /> -- allow for aliases<br /> for x, p in pairs( td_params ) do for y, alias in ipairs( p.aliases or {} ) do<br /> p[&#039;primary&#039;] = x<br /> td_params[x] = p<br /> all_aliases[alias] = p<br /> if tonumber(alias) then all_aliases[tonumber(alias)] = p end<br /> end end<br /> <br /> -- handle undeclared and deprecated<br /> local already_seen = {}<br /> local series = frame.args[&#039;series&#039;]<br /> for p_name, value in pairs( t_args ) do<br /> local tp_param, noval, numeric, table_name = td_params[p_name] or all_aliases[p_name], util.empty( value ), tonumber( p_name )<br /> local hasval = not noval<br /> <br /> if not tp_param and series then -- 2nd chance. check to see if series<br /> for s_name, p in pairs(td_params) do <br /> if mw.ustring.match( p_name, &#039;^&#039; .. s_name .. &#039;%d+&#039; .. &#039;$&#039;) then <br /> -- mw.log(&#039;found p_name &#039;.. p_name .. &#039; s_name:&#039; .. s_name, &#039; p is:&#039;, p) debugging series support<br /> tp_param = p <br /> end -- don&#039;t bother breaking. td always correct.<br /> end <br /> end<br /> <br /> if not tp_param then -- not in TD: this is called undeclared<br /> -- calculate the relevant table for this undeclared parameter, based on parameter and value types<br /> table_name = <br /> noval and numeric and &#039;empty-undeclared-numeric&#039; or<br /> noval and not numeric and &#039;empty-undeclared&#039; or<br /> hasval and numeric and &#039;undeclared-numeric&#039; or<br /> &#039;undeclared&#039; -- tzvototi nishar.<br /> else -- in td: test for deprecation and mistype. if deprecated, no further tests<br /> table_name = tp_param.deprecated and hasval and &#039;deprecated&#039; <br /> or tp_param.deprecated and noval and &#039;empty-deprecated&#039; <br /> or not compatible( tp_param.type, value ) and &#039;incompatible&#039;<br /> or not series and already_seen[tp_param] and hasval and &#039;duplicate&#039;<br /> <br /> if hasval and table_name ~= &#039;duplicate&#039; then<br /> already_seen[tp_param] = p_name<br /> end<br /> end<br /> <br /> -- report it.<br /> if table_name then<br /> res[table_name] = res[table_name] or {}<br /> if table_name == &#039;duplicate&#039; then<br /> local primary_param = tp_param[&#039;primary&#039;]<br /> local primaryData = res[table_name][primary_param]<br /> if not primaryData then<br /> primaryData = {}<br /> table.insert(primaryData, already_seen[tp_param])<br /> end<br /> table.insert(primaryData, p_name)<br /> res[table_name][primary_param] = primaryData<br /> else<br /> res[table_name][p_name] = value<br /> end<br /> end<br /> end<br /> <br /> -- check for empty/missing parameters declared &quot;required&quot; <br /> for p_name, param in pairs( td_params ) do <br /> if param.required and util.empty( t_args[p_name] ) then<br /> local is_alias<br /> for _, alias in ipairs( param.aliases or {} ) do is_alias = is_alias or not util.empty( t_args[alias] ) end<br /> if not is_alias then<br /> res[&#039;empty-required&#039;] = res[&#039;empty-required&#039;] or {} <br /> res[&#039;empty-required&#039;][p_name] = &#039;&#039; <br /> end<br /> end<br /> end<br /> <br /> mw.logObject(res)<br /> <br /> return res<br /> end<br /> <br /> -- wraps report in hidden frame<br /> function wrapReport(report, template_name, options)<br /> mw.logObject(report)<br /> if util.empty( report ) then return &#039;&#039; end<br /> local naked = mw.title.new( template_name )[&#039;text&#039;]<br /> naked = mw.ustring.gsub(naked, &#039;Infobox&#039;, &#039;infobox&#039;, 1)<br /> <br /> report = ( options[&#039;wrapper-prefix&#039;] or &quot;&lt;div class = &#039;paramvalidator-wrapper&#039;&gt;&lt;span class=&#039;paramvalidator-error&#039;&gt;&quot; )<br /> .. report<br /> .. ( options[&#039;wrapper-suffix&#039;] or &quot;&lt;/span&gt;&lt;/div&gt;&quot; )<br /> <br /> report = mw.ustring.gsub( report, &#039;tname_naked&#039;, naked )<br /> report = mw.ustring.gsub( report, &#039;templatename&#039;, template_name )<br /> <br /> return report<br /> end<br /> <br /> -- this is the &quot;user&quot; version, called with {{#invoke:}} returns a string, as defined by the options parameter<br /> function validateParams( frame )<br /> local options, report, template_name = util.extract_options( frame ), &#039;&#039;, frame:getParent():getTitle()<br /> <br /> local ignore = function( p_name )<br /> for _, pattern in ipairs( options[&#039;ignore&#039;] or {} ) do<br /> if mw.ustring.match( p_name, &#039;^&#039; .. pattern .. &#039;$&#039; ) then return true end<br /> end<br /> return false<br /> end<br /> <br /> local replace_macros = function( error_type, s, param_names )<br /> function concat_and_escape( t , sep )<br /> sep = sep or &#039;, &#039;<br /> local s = table.concat( t, sep )<br /> return ( mw.ustring.gsub( s, &#039;%%&#039;, &#039;%%%%&#039; ) )<br /> end<br /> <br /> if s and ( type( param_names ) == &#039;table&#039; ) then<br /> local k_ar, kv_ar = {}, {}<br /> for k, v in pairs( param_names ) do<br /> table.insert( k_ar, k )<br /> if type(v) == &#039;table&#039; then<br /> v = table.concat(v, &#039;, &#039;)<br /> end<br /> <br /> if error_type == &#039;duplicate&#039; then<br /> table.insert( kv_ar, v)<br /> else<br /> table.insert( kv_ar, k .. &#039;: &#039; .. v)<br /> end<br /> end<br /> <br /> s = mw.ustring.gsub( s, &#039;paramname&#039;, concat_and_escape( k_ar ) )<br /> s = mw.ustring.gsub( s, &#039;paramandvalue&#039;, concat_and_escape( kv_ar, &#039; AND &#039; ) )<br /> <br /> if mw.getCurrentFrame():preprocess( &quot;{{REVISIONID}}&quot; ) ~= &quot;&quot; then<br /> s = mw.ustring.gsub( s, &quot;&lt;div.*&lt;%/div&gt;&quot;, &quot;&quot;, 1 )<br /> end<br /> end<br /> return s<br /> end<br /> <br /> local report_params = function( key, param_names )<br /> local res = replace_macros( key, options[key], param_names )<br /> res = frame:preprocess(res or &#039;&#039;)<br /> report = report .. ( res or &#039;&#039; )<br /> return res<br /> end<br /> <br /> -- no option no work.<br /> if util.table_empty( options ) then return &#039;&#039; end<br /> <br /> -- get the errors.<br /> local violations = calculateViolations( frame, options[&#039;doc-subpage&#039;] )<br /> -- special request of bora: use skip_empty_numeric<br /> if violations[&#039;empty-undeclared-numeric&#039;] then <br /> for i = 1, tonumber( options[&#039;skip-empty-numeric&#039;] ) or 0 do <br /> violations[&#039;empty-undeclared-numeric&#039;][i] = nil <br /> end<br /> end<br /> <br /> -- handle ignore list, and prune empty violations - in that order!<br /> local offenders = 0<br /> for name, tab in pairs( violations ) do <br /> -- remove ignored parameters from all violations<br /> for pname in pairs( tab ) do if ignore( pname ) then tab[pname] = nil end end<br /> -- prune empty violations<br /> if util.table_empty( tab ) then violations[name] = nil end<br /> -- WORK IS DONE. report the errors.<br /> -- if report then count it.<br /> if violations[name] and report_params( name, tab ) then offenders = offenders + 1 end <br /> end<br /> <br /> if offenders &gt; 1 then report_params( &#039;multiple&#039; ) end<br /> if offenders ~= 0 then report_params( &#039;any&#039; ) end -- could have tested for empty( report ), but since we count them anyway...<br /> return wrapReport(report, template_name, options)<br /> end<br /> <br /> return {<br /> [&#039;validateparams&#039;] = validateParams,<br /> [&#039;calculateViolations&#039;] = calculateViolations,<br /> [&#039;wrapReport&#039;] = wrapReport<br /> }</div> SilverLocust