Module:TemplatePar/sandbox

--[=[ TemplatePar 2015-02-14 Template parameter utility ]=]
 * assert
 * check
 * count
 * countNotEmpty
 * downcase
 * match
 * valid
 * verify
 * TemplatePar

-- Module globals local TemplatePar = { } local MessagePrefix = "lua-module-TemplatePar-" local L10nDef = {} L10nDef.en = { badPattern = "&#35;invoke:TemplatePar pattern syntax error", dupOpt	 = "&#35;invoke:TemplatePar repeated optional parameter", dupRule	 = "&#35;invoke:TemplatePar conflict key/pattern", empty	  = "Error in template * undefined value for mandatory", invalid	 = "Error in template * invalid parameter", invalidPar = "&#35;invoke:TemplatePar invalid parameter", minmax	 = "&#35;invoke:TemplatePar min > max", missing	 = "&#35;invoke:TemplatePar missing library", multiSpell = "Error in template * multiple spelling of parameter", noMSGnoCAT = "&#35;invoke:TemplatePar neither message nor category", noname	 = "&#35;invoke:TemplatePar missing parameter name", notFound	= "Error in template * missing page", tooLong	 = "Error in template * parameter too long", tooShort	= "Error in template * parameter too short", undefined  = "Error in template * mandatory parameter missing", unknown	 = "Error in template * unknown parameter name", unknownRule = "&#35;invoke:TemplatePar unknown rule" } L10nDef.de = { badPattern = "&#35;invoke:TemplatePar Syntaxfehler des pattern", dupOpt	 = "&#35;invoke:TemplatePar Optionsparameter wiederholt", dupRule	 = "&#35;invoke:TemplatePar Konflikt key/pattern", empty	  = "Fehler bei Vorlage * Pflichtparameter ohne Wert", invalid	 = "Fehler bei Vorlage * Parameter ungültig", invalidPar = "&#35;invoke:TemplatePar Ungültiger Parameter", minmax	 = "&#35;invoke:TemplatePar min > max", multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen", noMSGnoCAT = "&#35;invoke:TemplatePar weder Meldung noch Kategorie", noname	 = "&#35;invoke:TemplatePar Parameter nicht angegeben", notFound	= "Fehler bei Vorlage * Seite fehlt", tooLong	 = "Fehler bei Vorlage * Parameter zu lang", tooShort	= "Fehler bei Vorlage * Parameter zu kurz", undefined  = "Fehler bei Vorlage * Pflichtparameter fehlt", unknown	 = "Fehler bei Vorlage * Parametername unbekannt", unknownRule = "&#35;invoke:TemplatePar Unbekannte Regel" } local Patterns = { [ "ASCII" ]	= "^[ -~]*$", [ "ASCII+" ]  = "^[ -~]+$", [ "ASCII+1" ] = "^[!-~]+$", [ "n" ]		= "^[%-]?[0-9]*$", [ "n>0" ]	 = "^[0-9]*[1-9][0-9]*$", [ "N+" ]	  = "^[%-]?[1-9][0-9]*$", [ "N>0" ]	 = "^[1-9][0-9]*$", [ "x" ]		= "^[0-9A-Fa-f]*$", [ "x+" ]	  = "^[0-9A-Fa-f]+$", [ "X" ]		= "^[0-9A-F]*$", [ "X+" ]	  = "^[0-9A-F]+$", [ "0,0" ]	 = "^[%-]?[0-9]*,?[0-9]*$",	[ "0,0+" ]	 = "^[%-]?[0-9]+,[0-9]+$",	[ "0,0+?" ]	= "^[%-]?[0-9]+,?[0-9]*$",	[ "0.0" ]	  = "^[%-]?[0-9]*[%.]?[0-9]*$",	[ "0.0+" ]	 = "^[%-]?[0-9]+%.[0-9]+$",	[ "0.0+?" ]	= "^[%-]?[0-9]+[%.]?[0-9]*$",	[ ".0+" ]	  = "^[%-]?[0-9]*[%.]?[0-9]+$",	[ "ID" ]	   = "^[A-Za-z]?[A-Za-z_0-9]*$", [ "ID+" ]	 = "^[A-Za-z][A-Za-z_0-9]*$", [ "ABC" ]	 = "^[A-Z]*$", [ "ABC+" ]	 = "^[A-Z]+$", [ "Abc" ]	 = "^[A-Z]*[a-z]*$", [ "Abc+" ]	 = "^[A-Z][a-z]+$", [ "abc" ]	 = "^[a-z]*$", [ "abc+" ]	 = "^[a-z]+$", [ "aBc+" ]	 = "^[a-z]+[A-Z][A-Za-z]*$", [ "w" ]		= "^%S*$", [ "w+" ]	  = "^%S+$", [ "base64" ]  = "^[A-Za-z0-9%+/]*$", [ "base64+" ] = "^[A-Za-z0-9%+/]+$", [ "aa" ]	  = "[%a%a].*[%a%a]", [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",									1, 31, 127 ), [ "+" ]		= "%S" } local patternCJK = false

local function containsCJK( s ) -- Is any CJK character present? -- Precondition: --	 s -- string -- Postcondition: --	 Return false iff no CJK present -- Uses: --	 >< patternCJK --	 mw.ustring.char --	 mw.ustring.match local r = false patternCJK = patternCJK or mw.ustring.char(91,									  13312, 45,  40959,									  131072, 45, 178207,									  93 ) if mw.ustring.match( s, patternCJK ) then r = true end return r end -- containsCJK

local function facility( accept, attempt ) -- Check string as possible file name or other source page -- Precondition: --	 accept  -- string; requirement --						 file --						 file+ --						 file: --						 file:+ --						 image --						 image+ --						 image: --						 image:+ --	 attempt -- string; to be tested -- Postcondition: --	 Return error keyword, or false -- Uses: --	 Module:FileMedia --	 FileMedia.isType local r	if attempt and attempt ~= "" then local lucky, FileMedia = pcall( require, "Module:FileMedia" ) if type( FileMedia ) == "table" then FileMedia = FileMedia.FileMedia local s, live = accept:match( "^([a-z]+)(:?)%+?$" ) if live then if FileMedia.isType( attempt, s ) then if FileMedia.isFile( attempt ) then r = false else r = "notFound" end else r = "invalid" end elseif FileMedia.isType( attempt, s ) then r = false else r = "invalid" end else r = "missing" end elseif accept:match( "%+$" ) then r = "empty" else r = false end return r end -- facility

local function factory( say ) -- Retrieve localized message string in content language -- Precondition: --	 say -- string; message ID	-- Postcondition: --	 Return some message string -- Uses: --	 > MessagePrefix --	 > L10nDef --	 mw.language.getContentLanguage --	 mw.message.new local c = mw.language.getContentLanguage:getCode local m = mw.message.new( MessagePrefix .. say ) local r = false if m:isBlank then local l10n = L10nDef[ c ] or L10nDef[ "en" ] r = l10n[ say ] else m:inLanguage( c ) r = m:plain end r = r or string.format( "(((%s)))", say ) return r end -- factory

local function failsafe( story, scan ) -- Test for match (possibly user-defined with syntax error) -- Precondition: --	 story -- string; parameter value --	 scan  -- string; pattern -- Postcondition: --	 Return nil, if not matching, else non-nil -- Uses: --	 mw.ustring.match return mw.ustring.match( story, scan ) end -- failsafe

local function failure( spec, suspect, options ) -- Submit localized error message -- Precondition: --	 spec	 -- string; message ID	--	 suspect -- string or nil; additional information --	 options -- table or nil; optional details --				 options.template -- Postcondition: --	 Return string -- Uses: --	 factory local r = factory( spec ) if type( options ) == "table" then if type( options.template ) == "string" then if #options.template > 0 then r = string.format( "%s (%s)", r, options.template ) end end end if suspect then r = string.format( "%s: %s", r, suspect ) end return r end -- failure

local function fault( store, key ) -- Add key to collection string and insert separator -- Precondition: --	 store -- string or nil or false; collection string --	 key	-- string or number; to be appended -- Postcondition: --	 Return string; extended local r	local s	if type( key ) == "number" then s = tostring( key ) else s = key end if store then r = string.format( "%s; %s", store, s ) else r = s	end return r end -- fault

local function feasible( analyze, options, abbr ) -- Check content of a value -- Precondition: --	 analyze -- string to be analyzed --	 options -- table or nil; optional details --				 options.pattern --				 options.key --				 options.say --	 abbr	 -- true: abbreviated error message -- Postcondition: --	 Return string with error message as configured; --			false if valid or no answer permitted -- Uses: --	 > Patterns --	 failure --	 mw.text.trim --	 facility --	 failsafe --	 containsCJK local r	= false local s	= false local show = nil local scan = false if type( options.pattern ) == "string" then if options.key then r = failure( "dupRule", false, options ) else scan = options.pattern end else if type( options.key ) == "string" then s = mw.text.trim( options.key ) else s = "+" end if s ~= "*" then scan = Patterns[ s ] end if type( scan ) == "string" then if s == "n" or s == "0,0" or s == "0.0" then if not analyze:match( "[0-9]" ) and not analyze:match( "^%s*$" ) then scan = false if options.say then show = string.format( "'%s'", options.say ) end if abbr then r = show else r = failure( "invalid", show, options ) end end end elseif s ~= "*" then local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" ) if op then n = tonumber( n ) if n then local i = tonumber( analyze ) if i then if op == "<" then i = ( i < n ) elseif op == "<=" then i = ( i <= n ) elseif op == ">" then i = ( i > n ) elseif op == ">=" then i = ( i >= n ) elseif op == "==" then i = ( i == n ) elseif op == "!=" then i = ( i ~= n ) else n = false end end if not i then r = "invalid" end elseif plus then r = "undefined" end elseif s:match( "^image%+?:?$" ) or				   s:match( "^file%+?:?$" ) then r = facility( s, analyze ) n = true elseif s:match( "langW?%+?" ) then n = "lang" -- lang lang+ -- langW langW+ end if not n and not r then r = "unknownRule" end if r then if options.say then show = string.format( "'%s' %s", options.say, s ) else show = s				end if abbr then r = show else r = failure( r, show, options ) end end end end if scan then local legal, got = pcall( failsafe, analyze, scan ) if legal then if not got then if s == "aa" then got = containsCJK( analyze ) end if not got then if options.say then show = string.format( "'%s'", options.say ) end if abbr then r = show else r = failure( "invalid", show, options ) end end end else r = failure( "badPattern",						 string.format( "%s *** %s", scan, got ),						 options ) end end return r end -- feasible

local function fed( haystack, needle ) -- Find needle in haystack map -- Precondition: --	 haystack -- table; map of key values --	 needle	-- any; identifier -- Postcondition: --	 Return true iff found local k, v	for k, v in pairs( haystack ) do		if k == needle then return true end end -- for k, v	return false end -- fed

local function fetch( light, options ) -- Return regular table with all parameters -- Precondition: --	 light	-- true: template transclusion; false: #invoke --	 options -- table; optional details --				 options.low -- Postcondition: --	 Return table; whitespace-only values as false -- Uses: --	 TemplatePar.downcase --	 mw.getCurrentFrame --	 frame:getParent local g, k, v	local r = { } if options.low then g = TemplatePar.downcase( options ) else g = mw.getCurrentFrame if light then g = g:getParent end g = g.args end if type( g ) == "table" then r = { } for k, v in pairs( g ) do			if type( v ) == "string" then if v:match( "^%s*$" ) then v = false end else v = false end if type( k ) == "number" then k = tostring( k ) end r[ k ] = v		end -- for k, v	else r = g	end return r end -- fetch

local function figure( append, options ) -- Extend options by rule from #invoke strings -- Precondition: --	 append  -- string or nil; requested rule --	 options --  table; details --				 ++ .key --				 ++ .pattern -- Postcondition: --	 Return sequence table local r = options if type( append ) == "string" then local story = mw.text.trim( append ) local sub  = story:match( "^/(.*%S)/$" ) if type( sub ) == "string" then sub			 = sub:gsub( "%%!", "|" ) sub			 = sub:gsub( "%%%(%(", "" ) options.pattern = sub options.key	 = nil else options.key	 = story options.pattern = nil end end return r end -- figure

local function fill( specified ) -- Split requirement string separated by '=' -- Precondition: --	 specified -- string or nil; requested parameter set -- Postcondition: --	 Return sequence table -- Uses: --	 mw.text.split local r	if specified then local i, s		r = mw.text.split( specified, "%s*=%s*" ) for i = #r, 1, -1 do			s = r[ i ] if #s == 0 then table.remove( r, i ) end end -- for i, -1 else r = { } end return r end -- fill

local function finalize( submit, options, frame ) -- Finalize message -- Precondition: --	 submit  -- string or false or nil; non-empty error message --	 options -- table or nil; optional details --				 options.format --				 options.preview --				 options.cat --				 options.template --	 frame	-- object, or false -- Postcondition: --	 Return string or false -- Uses: --	 factory local r = false if submit then local opt, s		local lazy = false local show = false if type( options ) == "table" then opt = options show = opt.format lazy = ( show == "" or  show == "0"  or  show == "-" ) s	= opt.preview if type( s ) == "string" and s ~= "" and  s ~= "0"  and  s ~= "-" then if lazy then show = "" lazy = false end frame = frame or mw.getCurrentFrame if frame:preprocess( "" ) == "" then if s == "1" then show = "*" else show = s					end end end else opt = { } end if lazy then if not opt.cat then r = string.format( "%s %s",								  submit,  factory( "noMSGnoCAT" ) ) end else r = submit end if r and  not lazy then local i			if not show or  show == "*" then show = "@@@ " end i = show:find( "@@@", 1, true ) if i then -- No gsub since r might contain "%3" (e.g. URL) r = string.format( "%s%s%s",								  show:sub( 1,  i - 1 ),								   r,								   show:sub( i + 3 ) ) else r = show end end s = opt.cat if type( s ) == "string" then if opt.errNS then local ns = mw.title.getCurrentTitle.namespace local st = type( opt.errNS ) if st == "string" then local space = string.format( ".*%%s%d%%s.*", ns ) local spaces = string.format( " %s ", opt.errNS ) if spaces:match( space ) then opt.errNS = false end elseif st == "table" then for i = 1, #opt.errNS do						if opt.errNS[ i ] == ns then opt.errNS = false break	-- for i						end end -- for i				end end if opt.errNS then r = "" else r = r or "" if s:find( "@@@" ) then if type( opt.template ) == "string" then s = s:gsub( "@@@", opt.template ) end end local i				local cats = mw.text.split( s, "%s*#%s*" ) for i = 1, #cats do					s = mw.text.trim( cats[ i ] ) if #s > 0 then r = string.format( "%s", r, s ) end end -- for i			end end end return r end -- finalize

local function finder( haystack, needle ) -- Find needle in haystack sequence -- Precondition: --	 haystack -- table; sequence of key names, downcased if low --	 needle	-- any; key name -- Postcondition: --	 Return true iff found local i	for i = 1, #haystack do		if haystack[ i ] == needle then return true end end -- for i	return false end -- finder

local function fix( valid, duty, got, options ) -- Perform parameter analysis -- Precondition: --	 valid	-- table; unique sequence of known parameters --	 duty	 -- table; sequence of mandatory parameters --	 got	 -- table; sequence of current parameters --	 options -- table or nil; optional details -- Postcondition: --	 Return string as configured; empty if valid -- Uses: --	 finder --	 fault --	 failure --	 fed local k, v	local r = false for k, v in pairs( got ) do		if not finder( valid, k ) then r = fault( r, k ) end end -- for k, v	if r then r = failure( "unknown",					 string.format( "'%s'", r ),					 options ) else -- all names valid local i, s		for i = 1, #duty do			s = duty[ i ] if not fed( got, s ) then r = fault( r, s ) end end -- for i		if r then r = failure( "undefined", r, options ) else -- all mandatory present for i = 1, #duty do				s = duty[ i ] if not got[ s ] then r = fault( r, s ) end end -- for i			if r then r = failure( "empty", r, options ) end end end return r end -- fix

local function flat( collection, options ) -- Return all table elements with downcased string -- Precondition: --	 collection -- table; k=v pairs --	 options	 -- table or nil; optional messaging details -- Postcondition: --	 Return table, may be empty; or string with error message. -- Uses: --	 mw.ustring.lower --	 fault --	 failure local k, v	local r = { } local e = false for k, v in pairs( collection ) do		if type ( k ) == "string" then k = mw.ustring.lower( k ) if r[ k ] then e = fault( e, k ) end end r[ k ] = v	end -- for k, v	if e then r = failure( "multiSpell", e, options ) end return r end -- flat

local function fold( options ) -- Merge two tables, create new sequence if both not empty -- Precondition: --	 options -- table; details --				 options.mandatory  sequence to keep unchanged --				 options.optional	sequence to be appended --				 options.low		 downcased expected -- Postcondition: --	 Return merged table, or message string if error -- Uses: --	 finder --	 fault --	 failure --	 flat local i, e, r, s	local base  = options.mandatory local extend = options.optional if #base == 0 then if #extend == 0 then r = { } else r = extend end else if #extend == 0 then r = base else e = false for i = 1, #extend do				s = extend[ i ] if finder( base, s ) then e = fault( e, s ) end end -- for i			if e then r = failure( "dupOpt", e, options ) else r = { } for i = 1, #base do					table.insert( r, base[ i ] ) end -- for i				for i = 1, #extend do					table.insert( r, extend[ i ] ) end -- for i			end end end if options.low and  type( r ) == "table" then r = flat( r, options ) end return r end -- fold

local function form( light, options, frame ) -- Run parameter analysis on current environment -- Precondition: --	 light	-- true: template transclusion; false: #invoke --	 options -- table or nil; optional details --				 options.mandatory --				 options.optional --	 frame	-- object, or false -- Postcondition: --	 Return string with error message as configured; --			false if valid -- Uses: --	 fold --	 fetch --	 fix --	 finalize local duty, r	if type( options ) == "table" then if type( options.mandatory ) ~= "table" then options.mandatory = { } end duty = options.mandatory if type( options.optional ) ~= "table" then options.optional = { } end r = fold( options ) else options = { } duty	= { } r	  = { } end if type( r ) == "table" then local got = fetch( light, options ) if type( got ) == "table" then r = fix( r, duty, got, options ) else r = got end end return finalize( r, options, frame ) end -- form

local function format( analyze, options ) -- Check validity of a value -- Precondition: --	 analyze -- string to be analyzed --	 options -- table or nil; optional details --				 options.say --				 options.min --				 options.max -- Postcondition: --	 Return string with error message as configured; --			false if valid or no answer permitted -- Uses: --	 feasible --	 failure local r = feasible( analyze, options, false ) local show if options.min and  not r then if type( options.min ) == "number" then if type( options.max ) == "number" then if options.max < options.min then r = failure( "minmax",								 string.format( "%d > %d", options.min, options.max ),								 options ) end end if #analyze < options.min and  not r then show = " <" .. options.min if options.say then show = string.format( "%s '%s'", show, options.say ) end r = failure( "tooShort", show, options ) end else r = failure( "invalidPar", "min", options ) end end if options.max and  not r then if type( options.max ) == "number" then if #analyze > options.max then show = " >" .. options.max if options.say then show = string.format( "%s '%s'", show, options.say ) end r = failure( "tooLong", show, options ) end else r = failure( "invalidPar", "max", options ) end end return r end -- format

local function formatted( assignment, access, options ) -- Check validity of one particular parameter in a collection -- Precondition: --	 assignment -- collection --	 access	 -- id of parameter in collection --	 options	 -- table or nil; optional details -- Postcondition: --	 Return string with error message as configured; --			false if valid or no answer permitted -- Uses: --	 mw.text.trim --	 format --	 failure local r = false if type( assignment ) == "table" then local story = assignment.args[ access ] or "" if type( access ) == "number" then story = mw.text.trim( story ) end if type( options ) ~= "table" then options = { } end options.say = access r = format( story, options ) end return r end -- formatted

local function furnish( frame, action ) -- Prepare #invoke evaluation of .assert or .valid -- Precondition: --	 frame	-- object; #invoke environment --	 action  -- "assert" or "valid" -- Postcondition: --	 Return string with error message or "" -- Uses: --	 form --	 failure --	 finalize --	 TemplatePar.valid --	 TemplatePar.assert local options = { mandatory = { "1" }, optional = { "2", "cat", "errNS", "low", "max", "min", "format", "preview", "template" }, template = string.format( "&#35;invoke:%s|%s|",												 "TemplatePar",												 action ) }	local r	  = form( false, options, frame ) if not r then local s		options = { cat	 = frame.args.cat, errNS	= frame.args.errNS, low	 = frame.args.low, format  = frame.args.format, preview = frame.args.preview, template = frame.args.template }		options = figure( frame.args[ 2 ], options ) if type( frame.args.min ) == "string" then s = frame.args.min:match( "^%s*([0-9]+)%s*$" ) if s then options.min = tonumber( s ) else r = failure( "invalidPar",							 "min=" .. frame.args.min,							 options ) end end if type( frame.args.max ) == "string" then s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" ) if s then options.max = tonumber( s ) else r = failure( "invalidPar",							 "max=" .. frame.args.max,							 options ) end end if r then r = finalize( r, options, frame ) else s = frame.args[ 1 ] or "" r = tonumber( s ) if ( r ) then s = r			end if action == "valid" then r = TemplatePar.valid( s, options, frame ) elseif action == "assert" then r = TemplatePar.assert( s, "", options ) end end end return r or "" end -- furnish

TemplatePar.assert = function ( analyze, append, options ) -- Perform parameter analysis on a single string -- Precondition: --	 analyze -- string to be analyzed --	 append  -- string: append error message, prepending

--				 false or nil: throw error with message --	 options -- table; optional details -- Postcondition: --	 Return string with error message as configured; --			false if valid -- Uses: --	 format local r = format( analyze, options ) if ( r ) then if ( type( append ) == "string" ) then if ( append ~= "" ) then r = string.format( "%s %s", append, r ) end else error( r, 0 ) end end return r end -- TemplatePar.assert

TemplatePar.check = function ( options ) -- Run parameter analysis on current template environment -- Precondition: --	 options -- table or nil; optional details --				 options.mandatory --				 options.optional -- Postcondition: --	 Return string with error message as configured; --			false if valid -- Uses: --	 form return form( true, options, false ) end -- TemplatePar.check

TemplatePar.count = function -- Return number of template parameters -- Postcondition: --	 Return number, starting at 0 -- Uses: --	 mw.getCurrentFrame --	 frame:getParent local k, v	local r = 0 local t = mw.getCurrentFrame:getParent local o = t.args for k, v in pairs( o ) do		r = r + 1 end -- for k, v	return r end -- TemplatePar.count

TemplatePar.countNotEmpty = function -- Return number of template parameters with more than whitespace -- Postcondition: --	 Return number, starting at 0 -- Uses: --	 mw.getCurrentFrame --	 frame:getParent local k, v	local r = 0 local t = mw.getCurrentFrame:getParent local o = t.args for k, v in pairs( o ) do		if not v:match( "^%s*$" ) then r = r + 1 end end -- for k, v	return r end -- TemplatePar.countNotEmpty

TemplatePar.downcase = function ( options ) -- Return all template parameters with downcased name -- Precondition: --	 options -- table or nil; optional messaging details -- Postcondition: --	 Return table, may be empty; or string with error message. -- Uses: --	 mw.getCurrentFrame --	 frame:getParent --	 flat local t = mw.getCurrentFrame:getParent return flat( t.args, options ) end -- TemplatePar.downcase

TemplatePar.valid = function ( access, options, frame ) -- Check validity of one particular template parameter -- Precondition: --	 access  -- id of parameter in template transclusion --				 string or number --	 options -- table or nil; optional details --	 frame	-- object; #invoke environment -- Postcondition: --	 Return string with error message as configured; --			false if valid or no answer permitted -- Uses: --	 mw.text.trim --	 TemplatePar.downcase --	 frame:getParent --	 formatted --	 failure --	 finalize local r = type( access ) if r == "string" then r = mw.text.trim( access ) if #r == 0 then r = false end elseif r == "number" then r = access else r = false end if r then local params if type( options ) ~= "table" then options = { } end if options.low then params = TemplatePar.downcase( options ) else params = frame:getParent end r = formatted( params, access, options ) else r = failure( "noname", false, options ) end return finalize( r, options, frame ) end -- TemplatePar.valid

TemplatePar.verify = function ( options ) -- Perform #invoke parameter analysis -- Precondition: --	 options -- table or nil; optional details -- Postcondition: --	 Return string with error message as configured; --			false if valid -- Uses: --	 form return form( false, options, false ) end -- TemplatePar.verify

-- Provide external access local p = {}

function p.assert( frame ) -- Perform parameter analysis on some single string -- Precondition: --	 frame -- object; #invoke environment -- Postcondition: --	 Return string with error message or "" -- Uses: --	 furnish return furnish( frame, "assert" ) end -- .assert

function p.check( frame ) -- Check validity of template parameters -- Precondition: --	 frame -- object; #invoke environment -- Postcondition: --	 Return string with error message or "" -- Uses: --	 form --	 fill local options = { optional = { "all", "opt", "cat", "errNS", "low", "format", "preview", "template" }, template = "&#35;invoke:TemplatePar|check|" }	local r = form( false, options, frame ) if not r then options = { mandatory = fill( frame.args.all ), optional = fill( frame.args.opt ), cat	  = frame.args.cat, errNS	 = frame.args.errNS, low	  = frame.args.low, format	= frame.args.format, preview  = frame.args.preview, template = frame.args.template }		r	  = form( true, options, frame ) end return r or "" end -- .check

function p.count( frame ) -- Count number of template parameters -- Postcondition: --	 Return string with digits including "0" -- Uses: --	 TemplatePar.count return tostring( TemplatePar.count ) end -- .count

function p.countNotEmpty( frame ) -- Count number of template parameters which are not empty -- Postcondition: --	 Return string with digits including "0" -- Uses: --	 TemplatePar.countNotEmpty return tostring( TemplatePar.countNotEmpty ) end -- .countNotEmpty

function p.match( frame ) -- Combined analysis of parameters and their values -- Postcondition: --	 Return string with error message or "" -- Uses: --	 mw.text.trim --	 mw.ustring.lower --	 failure --	 form --	 TemplatePar.downcase --	 figure --	 feasible --	 fault --	 finalize local r = false local options = { cat	 = frame.args.cat, errNS	= frame.args.errNS, low	 = frame.args.low, format  = frame.args.format, preview = frame.args.preview, template = frame.args.template }	local k, v, s	local params = { } for k, v in pairs( frame.args ) do		if type( k ) == "number" then s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" ) if s then s = mw.text.trim( s ) if s == "" then s = false end end if s then if options.low then s = mw.ustring.lower( s ) end if params[ s ] then s = params[ s ] s[ #s + 1 ] = v				else params[ s ] = { v } end else r = failure( "invalidPar", tostring( k ),  options ) break -- for k, v			end end end -- for k, v	if not r then s = { } for k, v in pairs( params ) do			s[ #s + 1 ] = k		end -- for k, v		options.optional = s		r = form( true, options, frame ) end if not r then local errMiss, errValues, lack, rule local targs = frame:getParent.args options.optional = nil if options.low then targs = TemplatePar.downcase else targs = frame:getParent.args end errMiss  = false errValues = false for k, v in pairs( params ) do			options.say = k			errValue	= false s = targs[ k ] if s then if s == "" then lack = true else lack = false end else s	= "" lack = true end for r, rule in pairs( v ) do				options = figure( rule, options ) r	  = feasible( s, options, true ) if r then if lack then if errMiss then errMiss = string.format( "%s, '%s'",													 errMiss, k ) else errMiss = string.format( "'%s'", k ) end elseif not errMiss then errValues = fault( errValues, r ) end break -- for r, rule end end -- for s, rule end -- for k, v		r = ( errMiss or errValues ) if r then if errMiss then r = failure( "undefined", errMiss, options ) else r = failure( "invalid", errValues, options ) end r = finalize( r, options, frame ) end end return r or "" end -- .match

function p.valid( frame ) -- Check validity of one particular template parameter -- Precondition: --	 frame -- object; #invoke environment -- Postcondition: --	 Return string with error message or "" -- Uses: --	 furnish return furnish( frame, "valid" ) end -- .valid

function p.TemplatePar -- Retrieve function access for modules -- Postcondition: --	 Return table with functions return TemplatePar end -- .TemplatePar

return p