Jump to content
Wikipedia The Free Encyclopedia

Module:Parameter validation

From Wikipedia, the free encyclopedia
Module documentation[view] [edit] [history] [purge]
Warning This Lua module is used on approximately 157,000 pages .
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
This module depends on the following other modules:

This module helps you find problematic usages of a template's parameters. Compared to Module:Check for unknown parameters, you do not need to create long lists of parameters which should be categorised; it will automatically read them from the template's TemplateData. This means you don't need to define the parameter configuration in multiple places, which will inevitably lead to it being out of sync.

As well as that, it supports a few other types of errors, such as defining the same parameter multiple times, or using deprecated parameters (useful to do so you can replace usages before removing support for a parameter entirely).

Basic usage

Add this to the bottom of a template:

{{#invoke:Parameter validation|validateparams|module_options = Module:Parameter validation/default config}}

This will use the default config defined in Module:Parameter validation/default config. The module will begin to populate the following categories:

  • Category:Pages using TEMPLATE_NAME with unknown parameters
  • Category:Pages using TEMPLATE_NAME with deprecated parameters
  • Category:Pages using TEMPLATE_NAME with duplicate parameters

For example, for {{Infobox station }}, it will populate:

Note that:

  • Unknown parameters means a page calling the template used a parameter that does not exist in the template documentation's TemplateData section
  • Deprecated parameters means a page calling the template used a parameter which, in the template's TemplateData, has the 'deprecated' option ticked
  • Duplicate parameters means a page calling the template and set a value for a parameter and one or more of its aliases

Use in protected templates

Note that because templates' TemplateData code typically lives in unprotected /doc pages, protected templates that invoke this module can be made to incorrectly categorize pages by editors who do not have user rights sufficient to edit the template page itself.

Full documentation

This module is based on idea and original code of User:IKhitron.

The source of this module, along with the original documentation, can be found at he:Module:ParamValidator.

The above documentation is transcluded from Module:Parameter validation/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Subpages of this module.

 localutil={
 empty=function(s)
 returns==nilortype(s)=='string'andmw.text.trim(s)==''
 end
 ,
 extract_options=function(frame,optionsPrefix)
 optionsPrefix=optionsPrefixor'options'

 localoptions,n,more={}
 ifframe.args['module_options']then
 localmodule_options=mw.loadData(frame.args['module_options'])
 iftype(module_options)~='table'thenreturn{}end
 localtitle=mw.title.getCurrentTitle()
 locallocal_ptions=module_options[title.namespace]ormodule_options[title.nsText]or{}
 fork,vinpairs(local_ptions)dooptions[k]=vend
 end

 repeat
 ok,more=pcall(mw.text.jsonDecode,frame.args[optionsPrefix..(nor'')])
 ifokandtype(more)=='table'then
 fork,vinpairs(more)dooptions[k]=vend
 end
 n=(nor0)+1
 untilnotok

 returnoptions
 end
 ,
 build_namelist=function(template_name,sp)
 localres={template_name}
 ifspthen
 iftype(sp)=='string'thensp={sp}end
 for_,pinipairs(sp)dotable.insert(res,template_name..'/'..p)end
 end
 returnres
 end
 ,
 table_empty=function(t)-- normally, test if next(t) is nil, but for some perverse reason, non-empty tables returned by loadData return nil...
 iftype(t)~='table'thenreturntrueend
 fora,binpairs(t)doreturnfalseend
 returntrue
 end
 ,
 }

 localfunction_readTemplateData(templateName)
 localtitle=mw.title.makeTitle(0,templateName)
 localtemplateContent=titleandtitle.existsandtitle:getContent()-- template's raw content
 localcapture=templateContentandmw.ustring.match(templateContent,'<templatedata%s*>(.*)</templatedata%s*>')-- 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.
 localtrailingComma=captureandmw.ustring.find(capture,',%s*[%]%}]')-- look for ,] or ,} : jsonDecode allows it, but it's verbotten in json
 ifcaptureandnottrailingCommathenreturnpcall(mw.text.jsonDecode,capture)end
 returnfalse
 end

 localfunctionreadTemplateData(templateName)
 iftype(templateName)=='string'then
 templateName={templateName,templateName..'/'..docSubPage}
 end
 iftype(templateName)=="table"then
 for_,nameinipairs(templateName)do
 localtd,result=_readTemplateData(name)
 iftdthenreturnresultend
 end
 end
 returnnil
 end


 -- this is the function to be called by other modules. it expects the frame, and then an optional list of subpages, e.g. { "Documentation" }.
 -- if second parameter is nil, only template page will be searched for templatedata.
 functioncalculateViolations(frame,subpages)
 -- used for parameter type validy test. keyed by TD 'type' string. values are function(val) returning bool.
 localtype_validators={
 ['number']=function(s)returnmw.language.getContentLanguage():parseFormattedNumber(s)end
 }
 functioncompatible(typ,val)
 localfunc=type_validators[typ]
 returntype(func)~='function'orutil.empty(val)orfunc(val)
 end

 localt_frame=frame:getParent()
 localt_args,template_name=t_frame.args,t_frame:getTitle()
 template_name=mw.ustring.gsub(template_name,'/sandbox','',1)
 localtd_source=util.build_namelist(template_name,subpages)
 ifframe.args['td_source']then
 table.insert(td_source,frame.args['td_source'])
 end

 localtemplatedata=readTemplateData(td_source)
 localtd_params=templatedataandtemplatedata.params
 localall_aliases,all_series={},{}

 ifnottd_paramsthenreturn{['no-templatedata']={['']=''}}end
 -- from this point on, we know templatedata is valid.

 localres={}-- before returning to caller, we'll prune empty tables

 -- allow for aliases
 forx,pinpairs(td_params)dofory,aliasinipairs(p.aliasesor{})do
 p['primary']=x
 td_params[x]=p
 all_aliases[alias]=p
 iftonumber(alias)thenall_aliases[tonumber(alias)]=pend
 endend

 -- handle undeclared and deprecated
 localalready_seen={}
 localseries=frame.args['series']
 forp_name,valueinpairs(t_args)do
 localtp_param,noval,numeric,table_name=td_params[p_name]orall_aliases[p_name],util.empty(value),tonumber(p_name)
 localhasval=notnoval

 ifnottp_paramandseriesthen-- 2nd chance. check to see if series
 fors_name,pinpairs(td_params)do
 ifmw.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

 ifnottp_paramthen-- not in TD: this is called undeclared
 -- calculate the relevant table for this undeclared parameter, based on parameter and value types
 table_name=
 novalandnumericand'empty-undeclared-numeric'or
 novalandnotnumericand'empty-undeclared'or
 hasvalandnumericand'undeclared-numeric'or
 'undeclared'-- tzvototi nishar.
 else-- in td: test for deprecation and mistype. if deprecated, no further tests
 table_name=tp_param.deprecatedandhasvaland'deprecated'
 ortp_param.deprecatedandnovaland'empty-deprecated'
 ornotcompatible(tp_param.type,value)and'incompatible'
 ornotseriesandalready_seen[tp_param]andhasvaland'duplicate'

 ifhasvalandtable_name~='duplicate'then
 already_seen[tp_param]=p_name
 end
 end

 -- report it.
 iftable_namethen
 res[table_name]=res[table_name]or{}
 iftable_name=='duplicate'then
 localprimary_param=tp_param['primary']
 localprimaryData=res[table_name][primary_param]
 ifnotprimaryDatathen
 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" 
 forp_name,paraminpairs(td_params)do
 ifparam.requiredandutil.empty(t_args[p_name])then
 localis_alias
 for_,aliasinipairs(param.aliasesor{})dois_alias=is_aliasornotutil.empty(t_args[alias])end
 ifnotis_aliasthen
 res['empty-required']=res['empty-required']or{}
 res['empty-required'][p_name]=''
 end
 end
 end

 mw.logObject(res)

 returnres
 end

 -- wraps report in hidden frame
 functionwrapReport(report,template_name,options)
 mw.logObject(report)
 ifutil.empty(report)thenreturn''end
 localnaked=mw.title.new(template_name)['text']
 naked=mw.ustring.gsub(naked,'Infobox','infobox',1)

 report=(options['wrapper-prefix']or"<div class = 'paramvalidator-wrapper'><span class='paramvalidator-error'>")
 ..report
 ..(options['wrapper-suffix']or"</span></div>")

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

 returnreport
 end

 -- this is the "user" version, called with {{#invoke:}} returns a string, as defined by the options parameter
 functionvalidateParams(frame)
 localoptions,report,template_name=util.extract_options(frame),'',frame:getParent():getTitle()

 localignore=function(p_name)
 for_,patterninipairs(options['ignore']or{})do
 ifmw.ustring.match(p_name,'^'..pattern..'$')thenreturntrueend
 end
 returnfalse
 end

 localreplace_macros=function(error_type,s,param_names)
 functionconcat_and_escape(t,sep)
 sep=sepor', '
 locals=table.concat(t,sep)
 return(mw.ustring.gsub(s,'%%','%%%%'))
 end

 ifsand(type(param_names)=='table')then
 localk_ar,kv_ar={},{}
 fork,vinpairs(param_names)do
 table.insert(k_ar,k)
 iftype(v)=='table'then
 v=table.concat(v,', ')
 end

 iferror_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 '))

 ifmw.getCurrentFrame():preprocess("{{REVISIONID}}")~=""then
 s=mw.ustring.gsub(s,"<div.*<%/div>","",1)
 end
 end
 returns
 end

 localreport_params=function(key,param_names)
 localres=replace_macros(key,options[key],param_names)
 res=frame:preprocess(resor'')
 report=report..(resor'')
 returnres
 end

 -- no option no work.
 ifutil.table_empty(options)thenreturn''end

 -- get the errors.
 localviolations=calculateViolations(frame,options['doc-subpage'])
 -- special request of bora: use skip_empty_numeric
 ifviolations['empty-undeclared-numeric']then
 fori=1,tonumber(options['skip-empty-numeric'])or0do
 violations['empty-undeclared-numeric'][i]=nil
 end
 end

 -- handle ignore list, and prune empty violations - in that order!
 localoffenders=0
 forname,tabinpairs(violations)do
 -- remove ignored parameters from all violations
 forpnameinpairs(tab)doifignore(pname)thentab[pname]=nilendend
 -- prune empty violations
 ifutil.table_empty(tab)thenviolations[name]=nilend
 -- WORK IS DONE. report the errors.
 -- if report then count it.
 ifviolations[name]andreport_params(name,tab)thenoffenders=offenders+1end
 end

 ifoffenders>1thenreport_params('multiple')end
 ifoffenders~=0thenreport_params('any')end-- could have tested for empty( report ), but since we count them anyway...
 returnwrapReport(report,template_name,options)
 end

 return{
 ['validateparams']=validateParams,
 ['calculateViolations']=calculateViolations,
 ['wrapReport']=wrapReport
 }

AltStyle によって変換されたページ (->オリジナル) /