Permanently protected module
From Wikipedia, the free encyclopedia


local util = {

	empty = function( s ) 

		return s == nil  or type( s ) == 'string' and mw.text.trim( s ) == ''   

	end

	, 

	extract_options = function ( frame, optionsPrefix )

		optionsPrefix = optionsPrefix or 'options' 



		local options, n, more = {}

		if frame.args'module_options' then

			local module_options = mw.loadData( frame.args'module_options' ) 

			if type( module_options ) ~= 'table' then return {} end

			local title = mw.title.getCurrentTitle()

			local local_ptions = module_options title.namespace  or module_options title.nsText  or {} 

			for k, v in pairs( local_ptions ) do optionsk = v end

		end

		

		repeat

			ok, more = pcall( mw.text.jsonDecode, frame.argsoptionsPrefix .. ( n or '' )] )

			if ok and type( more ) == 'table' then

				for k, v in pairs( more ) do optionsk = v end

			end

			n = ( n or 0 ) + 1

		until not ok



		return options

	end

	, 

	build_namelist = function ( template_name, sp )

		local res = { template_name }

		if sp then

			if type( sp ) == 'string' then sp = { sp } end

			for _, p in ipairs( sp ) do table.insert( res, template_name .. '/' .. p ) end

		end

		return res

	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...

		if type( t ) ~= 'table' then return true end

		for a, b in pairs( t ) do return false end

		return true

	end

	,

}



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%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.

	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 false

end



local function readTemplateData( templateName )

	if type( templateName ) == 'string' then 

		templateName = { templateName, templateName .. '/' .. docSubPage }

	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 nil

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 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 = { 

		'number' = function( s ) return mw.language.getContentLanguage():parseFormattedNumber( s ) end

	}

	function compatible( typ, val )

		local func = type_validatorstyp

		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 { 'no-templatedata' = { '' = '' } } 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_paramsx = p

		all_aliasesalias = p

		if tonumber(alias) then all_aliasestonumber(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_paramsp_name or all_aliasesp_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_seentp_param and hasval and 'duplicate'



			if hasval and table_name ~= 'duplicate' then

				already_seentp_param = p_name

			end

		end

		

		-- report it.

		if table_name then

			restable_name = restable_name or {}

			if table_name == 'duplicate' then

				local primary_param = tp_param'primary'

				local primaryData = restable_name][primary_param

				if not primaryData then

					primaryData = {}

					table.insert(primaryData, already_seentp_param])

				end

				table.insert(primaryData, p_name)

				restable_name][primary_param = primaryData

			else

				restable_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_argsp_name ) then

			local is_alias

			for _, alias in ipairs( param.aliases or {} ) do is_alias = is_alias or not util.empty( t_argsalias ) 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 res

end



-- wraps report in hidden frame

function 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 "<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 )



	return report

end



-- this is the "user" version, called with {{#invoke:}} returns a string, as defined by the options parameter

function 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( "{{REVISIONID}}" ) ~= "" then

				s = mw.ustring.gsub( s, "<div.*<%/div>", "", 1 )

			end

		end

		return s

	end



	local report_params = function( key, param_names )

		local res = replace_macros( key, optionskey], 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 tabpname = nil end end

		-- prune empty violations

		if util.table_empty( tab ) then violationsname = nil end

	-- WORK IS DONE. report the errors.

	-- if report then count it.

		if violationsname 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 {

	'validateparams' = validateParams,

	'calculateViolations' = calculateViolations,

	'wrapReport' = wrapReport

}
Permanently protected module
From Wikipedia, the free encyclopedia


local util = {

	empty = function( s ) 

		return s == nil  or type( s ) == 'string' and mw.text.trim( s ) == ''   

	end

	, 

	extract_options = function ( frame, optionsPrefix )

		optionsPrefix = optionsPrefix or 'options' 



		local options, n, more = {}

		if frame.args'module_options' then

			local module_options = mw.loadData( frame.args'module_options' ) 

			if type( module_options ) ~= 'table' then return {} end

			local title = mw.title.getCurrentTitle()

			local local_ptions = module_options title.namespace  or module_options title.nsText  or {} 

			for k, v in pairs( local_ptions ) do optionsk = v end

		end

		

		repeat

			ok, more = pcall( mw.text.jsonDecode, frame.argsoptionsPrefix .. ( n or '' )] )

			if ok and type( more ) == 'table' then

				for k, v in pairs( more ) do optionsk = v end

			end

			n = ( n or 0 ) + 1

		until not ok



		return options

	end

	, 

	build_namelist = function ( template_name, sp )

		local res = { template_name }

		if sp then

			if type( sp ) == 'string' then sp = { sp } end

			for _, p in ipairs( sp ) do table.insert( res, template_name .. '/' .. p ) end

		end

		return res

	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...

		if type( t ) ~= 'table' then return true end

		for a, b in pairs( t ) do return false end

		return true

	end

	,

}



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%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.

	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 false

end



local function readTemplateData( templateName )

	if type( templateName ) == 'string' then 

		templateName = { templateName, templateName .. '/' .. docSubPage }

	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 nil

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 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 = { 

		'number' = function( s ) return mw.language.getContentLanguage():parseFormattedNumber( s ) end

	}

	function compatible( typ, val )

		local func = type_validatorstyp

		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 { 'no-templatedata' = { '' = '' } } 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_paramsx = p

		all_aliasesalias = p

		if tonumber(alias) then all_aliasestonumber(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_paramsp_name or all_aliasesp_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_seentp_param and hasval and 'duplicate'



			if hasval and table_name ~= 'duplicate' then

				already_seentp_param = p_name

			end

		end

		

		-- report it.

		if table_name then

			restable_name = restable_name or {}

			if table_name == 'duplicate' then

				local primary_param = tp_param'primary'

				local primaryData = restable_name][primary_param

				if not primaryData then

					primaryData = {}

					table.insert(primaryData, already_seentp_param])

				end

				table.insert(primaryData, p_name)

				restable_name][primary_param = primaryData

			else

				restable_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_argsp_name ) then

			local is_alias

			for _, alias in ipairs( param.aliases or {} ) do is_alias = is_alias or not util.empty( t_argsalias ) 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 res

end



-- wraps report in hidden frame

function 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 "<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 )



	return report

end



-- this is the "user" version, called with {{#invoke:}} returns a string, as defined by the options parameter

function 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( "{{REVISIONID}}" ) ~= "" then

				s = mw.ustring.gsub( s, "<div.*<%/div>", "", 1 )

			end

		end

		return s

	end



	local report_params = function( key, param_names )

		local res = replace_macros( key, optionskey], 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 tabpname = nil end end

		-- prune empty violations

		if util.table_empty( tab ) then violationsname = nil end

	-- WORK IS DONE. report the errors.

	-- if report then count it.

		if violationsname 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 {

	'validateparams' = validateParams,

	'calculateViolations' = calculateViolations,

	'wrapReport' = wrapReport

}

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook