Módulo:TemplatePar

Revisão em 01h31min de 14 de maio de 2024 por Jaewoo (discussão | contribs) (Criou a página com "local TemplatePar = { serial = "2023-03-20", suite = "TemplatePar", item = 15393417, globals = { DateTime = 20652535, FileMedia = 24765326, Multilingual = 47541920, TemplUtl = 52364930, URLutil = 10859193 } } --[=[ Template parameter utility * asser...")
(dif) ← Revisão anterior | Revisão atual (dif) | Revisão seguinte → (dif)

A documentação para este módulo pode ser criada na página Módulo:TemplatePar/doc

local TemplatePar = { serial  = "2023-03-20",
																						suite   = "TemplatePar",
																						item    = 15393417,
																						globals = { DateTime     = 20652535,
																																		FileMedia    = 24765326,
																																		Multilingual = 47541920,
																																		TemplUtl     = 52364930,
																																		URLutil      = 10859193 } }
--[=[
Template parameter utility
* assert
* check
* count
* countNotEmpty
* downcase()
* duplicates
* match
* valid
* verify()
* TemplatePar()
* failsafe()
]=]
local Local     = { frame = false }
local Failsafe  = TemplatePar
local GlobalMod = Local
-- Module globals
Local.messagePrefix = "lua-module-TemplatePar-"
Local.L10nDef = {}
Local.L10nDef.en = {
				badPattern  = "#invoke:TemplatePar pattern syntax error",
				dupOpt      = "#invoke:TemplatePar repeated optional parameter",
				dupRule     = "#invoke:TemplatePar conflict key/pattern",
				empty       = "Error in template * undefined value for mandatory",
				invalid     = "Error in template * invalid parameter",
				invalidPar  = "#invoke:TemplatePar invalid parameter",
				minmax      = "#invoke:TemplatePar min > max",
				missing     = "#invoke:TemplatePar missing library",
				multiSpell  = "Error in template * multiple spelling of parameter",
				noMSGnoCAT  = "#invoke:TemplatePar neither message nor category",
				noname      = "#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",
				unavailable = "Error in template * parameter name missing",
				undefined   = "Error in template * mandatory parameter missing",
				unknown     = "Error in template * unknown parameter name",
				unknownRule = "#invoke:TemplatePar unknown rule"
}
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 ),
				[ "ref" ]      = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c",
																																				127, 34, "%-", "%-", "%-", "%x+",
																																				"%-", 34, 127 ),
				[ "+" ]        = "%S"
}
Local.boolean = { ["1"]     = true,
																		["true"]  = true,
																		y         = true,
																		yes       = true,
																		on        = true,
																		["0"]     = true,
																		["false"] = true,
																		["-"]     = true,
																		n         = true,
																		no        = true,
																		off       = true }
Local.patternCJK = false
local foreignModule = function ( access, advanced, append, alt, alert )
				-- Fetch global module
				-- Precondition:
				--     access    -- string, with name of base module
				--     advanced  -- true, for require(); else mw.loadData()
				--     append    -- string, with subpage part, if any; or false
				--     alt       -- number, of wikidata item of root; or false
				--     alert     -- true, for throwing error on data problem
				-- Postcondition:
				--     Returns whatever, probably table
				-- 2020-01-01
				local storage = access
				local finer = function ()
																						if append then
																										storage = string.format( "%s/%s",
																																																			storage,
																																																			append )
																						end
																		end
				local fun, lucky, r, suited
				if advanced then
								fun = require
				else
								fun = mw.loadData
				end
				GlobalMod.globalModules = GlobalMod.globalModules or { }
				suited = GlobalMod.globalModules[ access ]
				if not suited then
								finer()
								lucky, r = pcall( fun,  "Module:" .. storage )
				end
				if not lucky then
								if not suited  and
											type( alt ) == "number"  and
											alt > 0 then
												suited = string.format( "Q%d", alt )
												suited = mw.wikibase.getSitelink( suited )
												GlobalMod.globalModules[ access ] = suited or true
								end
								if type( suited ) == "string" then
												storage = suited
												finer()
												lucky, r = pcall( fun, storage )
								end
								if not lucky and alert then
												error( "Missing or invalid page: " .. storage )
								end
				end
				return r
end -- foreignModule()
local function Foreign( access  )
				-- Access standardized library
				-- Precondition:
				--     access  -- string, with name of base module
				-- Postcondition:
				--     Return library table, or not
				-- Uses:
				local r
				if Local[ access ] then
								r = Local[ access ]
				else
								local bib = foreignModule( access,
																																			true,
																																			false,
																																			TemplatePar.globals[ access ],
																																			false )
								if type( bib ) == "table"   and
											type( bib[ access ] ) == "function" then
												bib = bib[ access ]()
												if type( bib ) == "table" then
																r               = bib
																Local[ access ] = bib
												end
								end
				end
				return r
end -- Foreign()
local function containsCJK( analyse )
				-- Is any CJK character present?
				-- Precondition:
				--     analyse  -- string
				-- Postcondition:
				--     Return false iff no CJK present
				-- Uses:
				--     >< Local.patternCJK
				--     mw.ustring.char()
				--     mw.ustring.match()
				local r = false
				if not Local.patternCJK then
								Local.patternCJK = mw.ustring.char( 91,
																																							13312, 45,  40959,
																																						131072, 45, 178207,
																																						93 )
				end
				if mw.ustring.match( analyse, Local.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
				--     Foreign()
				--     FileMedia.isFile()
				--     FileMedia.isType()
				local r
				if attempt and attempt ~= "" then
								local FileMedia = Foreign( "FileMedia" )
								if FileMedia  and  type( FileMedia.isFile ) == "function"
																						and  type( FileMedia.isType ) == "function" then
												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:
				--     >  Local.messagePrefix
				--     >  Local.L10nDef
				--     mw.message.new()
				--     mw.language.getContentLanguage()
				--     Module:Multilingual
				--     Foreign()
				--     TemplatePar.framing()
				--     Multilingual.tabData()
				local m = mw.message.new( Local.messagePrefix .. say )
				local r = false
				if m:isBlank() then
								local c = mw.language.getContentLanguage():getCode()
								local l10n = Local.L10nDef[ c ]
								if l10n then
												r = l10n[ say ]
								else
												local MultiL = Foreign( "Multilingual" )
												if MultiL  and  type( MultiL.tabData ) == "function" then
																local lang
																r, lang = MultiL.tabData( "I18n/Module:TemplatePar",
																																										say,
																																										false,
																																										TemplatePar.framing() )
												end
								end
								if not r then
												r = Local.L10nDef.en[ say ]
								end
				else
								m:inLanguage( c )
								r = m:plain()
				end
				if not r then
								r = string.format( "(((%s)))", say )
				end
				return r
end -- factory()
local function faculty( accept, attempt )
				-- Check string as possible boolean
				-- Precondition:
				--     accept   -- string; requirement
				--                         boolean
				--                         boolean+
				--     attempt  -- string; to be tested
				-- Postcondition:
				--     Return error keyword, or false
				-- Uses:
				--     Module:TemplUtl
				--     Foreign()
				--     TemplUtl.faculty()
				local r
				r = mw.text.trim( attempt ):lower()
				if r == "" then
								if accept == "boolean+" then
												r = "empty"
								else
												r = false
								end
				elseif Local.boolean[ r ]  or   r:match( "^[01%-]+$" ) then
								r = false
				else
								local TemplUtl = Foreign( "TemplUtl" )
								if TemplUtl  and  type( TemplUtl.faculty ) == "function" then
												r = TemplUtl.faculty( r, "-" )
												if r == "-" then
																r = "invalid"
												else
																r = false
												end
								else
												r = "invalid"
								end
				end
				return r
end -- faculty()
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 fair( 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 -- fair()
local function familiar( accept, attempt )
				-- Check string as possible language name or list
				-- Precondition:
				--     accept   -- string; requirement
				--                         lang
				--                         langs
				--                         langW
				--                         langsW
				--                         lang+
				--                         langs+
				--                         langW+
				--                         langsW+
				--     attempt  -- string; to be tested
				-- Postcondition:
				--     Return error keyword, or false
				-- Uses:
				--     Module:Multilingual
				--     Foreign()
				--     Multilingual.isLang()
				local r
				if attempt and attempt ~= "" then
								local MultiL = Foreign( "Multilingual" )
								if MultiL  and  type( MultiL.isLang ) == "function" then
												local lazy = accept:find( "W", 1, true )
												if accept:find( "s", 1, true ) then
																local group = mw.text.split( attempt, "%s+" )
																r = false
																for i = 1, #group do
																				if not MultiL.isLang( group[ i ], lazy ) then
																								r = "invalid"
																								break -- for i
																				end
																end -- for i
												elseif MultiL.isLang( attempt, lazy ) then
																r = false
												else
																r = "invalid"
												end
								else
												r = "missing"
								end
				elseif accept:find( "+", 1, true ) then
								r = "empty"
				else
								r = false
				end
				return r
end -- familiar()
local function far( accept, attempt )
				-- Check string as possible URL
				-- Precondition:
				--     accept   -- string; requirement
				--                         url
				--                         url+
				--     attempt  -- string; to be tested
				-- Postcondition:
				--     Return error keyword, or false
				-- Uses:
				--     Module:URLutil
				--     Foreign()
				--     URLutil.isWebURL()
				local r
				if attempt and attempt ~= "" then
								local URLutil = Foreign( "URLutil" )
								if URLutil  and  type( URLutil.isWebURL ) == "function" then
												if URLutil.isWebURL( attempt ) then
																r = false
												else
																r = "invalid"
												end
								else
												r = "missing"
								end
				elseif accept:find( "+", 1, true ) then
								r = "empty"
				else
								r = false
				end
				return r
end -- far()
local function fast( accept, attempt )
				-- Check string as possible date or time
				-- Precondition:
				--     accept   -- string; requirement
				--                         datetime
				--                         datetime+
				--                         datetime/y
				--                         datetime/y+
				--                         datetime/ym
				--                         datetime/ym+
				--                         datetime/ymd
				--                         datetime/ymd+
				--     attempt  -- string; to be tested
				-- Postcondition:
				--     Return error keyword, or false
				-- Uses:
				--     Module:DateTime
				--     Foreign()
				--     DateTime.DateTime()
				local r
				r = mw.text.trim( attempt )
				if r == "" then
								if accept:find( "+", 1, true ) then
												r = "empty"
								else
												r = false
								end
				else
								local DateTime = Foreign( "DateTime" )
								if type( DateTime ) == "table" then
												local d = DateTime( attempt )
												if type( d ) == "table" then
																if accept:find( "/", 1, true ) then
																				r = "invalid"
																				if accept:sub( 1, 10 ) == "datetime/y" then
																								if d.year then
																												r = false
																												if accept:sub( 1, 11 ) == "datetime/ym" then
																																if d.month then
																																				if accept:sub( 1, 12 )
																																																			== "datetime/ymd" then
																																								if not d.dom then
																																												r = "invalid"
																																								end
																																				end
																																else
																																				r = "invalid"
																																end
																												end
																								end
																				end
																else
																				r = false
																end
												else
																r = "invalid"
												end
								else
												r = "invalid"
								end
				end
				return r
end -- fast()
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:
				--     >  Local.patterns
				--     failure()
				--     mw.text.trim()
				--     faculty()
				--     fast()
				--     facility()
				--     familiar()
				--     far()
				--     fair()
				--     containsCJK()
				local r     = false
				local s     = false
				local show  = nil
				local scan  = false
				local stuff = mw.text.trim( analyze )
				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 = Local.patterns[ s ]
								end
								if type( scan ) == "string" then
												if s == "n" or s == "0,0" or s == "0.0" then
																if not stuff:match( "[0-9]" )  and
																			not stuff:match( "^%s*$" ) then
																				scan = false
																				if options.say then
																								show = string.format( "&quot;%s&quot;", 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( stuff )
																				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( "^boolean%+?$" ) then
																r = faculty( s, stuff )
																n = true
												elseif s:match( "^datetime/?y?m?d?%+?$" ) then
																r = fast( s, stuff )
																n = true
												elseif s:match( "^image%+?:?$" )  or
																			s:match( "^file%+?:?$" ) then
																r = facility( s, stuff )
																n = true
												elseif s:match( "langs?W?%+?" ) then
																r = familiar( s, stuff )
																n = true
												elseif s:match( "url%+?" ) then
																r = far( s, stuff )
																n = true
												end
-- datetime+
-- iso8631+
-- line+
												if not n and not r then
																r = "unknownRule"
												end
												if r then
																if options.say then
																				show = string.format( "&quot;%s&quot; %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( fair, stuff, scan )
								if legal then
												if not got then
																if s == "aa" then
																				got = containsCJK( stuff )
																end
																if not got then
																				if options.say then
																								show = string.format( "&quot;%s&quot;", 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, r
				for k, v in pairs( haystack ) do
								if k == needle then
												r = true
								end
				end -- for k, v
				return r or 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()
				--     TemplatePar.framing()
				--     frame:getParent()
				local g, k, v
				local r = { }
				if options.low then
								g = TemplatePar.downcase( options )
				else
								g = TemplatePar.framing()
								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( "%%!", "|" )
																																	:gsub( "%%%(%(", "{{" )
																																	:gsub( "%%%)%)", "}}" )
																																	:gsub( "\\n", string.char( 10 ) )
												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 )
				-- 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
				-- Postcondition:
				--     Return string or false
				-- Uses:
				--     TemplatePar.framing()
				--     factory()
				local r = false
				if submit then
								local lazy  = false
								local learn = false
								local show  = false
								local opt, s
								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
																local sniffer = "{{REVISIONID}}"
																if lazy then
																				show = ""
																				lazy = false
																end
																if TemplatePar.framing():preprocess( sniffer ) == "" then
																				if s == "1" then
																								show = "*"
																				else
																								show = s
																				end
																				learn = true
																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
																local e = mw.html.create( "span" )
																																	:attr( "class", "error" )
																																	:wikitext( "@@@" )
																if learn then
																				local max  = 1000000000
																				local id   = math.floor( os.clock() * max )
																				local sign = string.format( "error_%d", id )
																				local btn  = mw.html.create( "span" )
																				local top  = mw.html.create( "div" )
																				e:attr( "id", sign )
																				btn:css( { ["background"]      = "#FFFF00",
																															["border"]          = "#FF0000 3px solid",
																															["font-weight"]     = "bold",
																															["padding"]         = "2px",
																															["text-decoration"] = "none" } )
																							:wikitext( "&gt;&gt;&gt;" )
																				sign = string.format( "[[#%s|%s]]",
																																										sign,  tostring( btn ) )
																				top:wikitext( sign, "&#160;", submit )
																				mw.addWarning( tostring( top ) )
																end
																show = tostring( e )
												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
								if learn and r then
												-- r = fatal( r )
								end
								s = opt.cat
								if type( s ) == "string" then
												local link
												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
																								link = true
																				end
																elseif st == "table" then
																				for i = 1, #opt.errNS do
																								if opt.errNS[ i ] == ns then
																												link = true
																												break    -- for i
																								end
																				end -- for i
																end
												else
																link = true
												end
												if link then
																local cats, i
																if not r then
																			r = ""
																end
																if s:find( "@@@" ) then
																				if type( opt.template ) == "string" then
																								s = s:gsub( "@@@", opt.template )
																				end
																end
																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[[Category:%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 r = false
				local lack
				for k, v in pairs( got ) do
								if k == "" then
												lack = true
												break    -- for k, v
								elseif not finder( valid, k ) then
												r = fault( r, k )
								end
				end -- for k, v
				if lack then
								r = failure( "unavailable", false, options )
				elseif r then
								r = failure( "unknown",
																					string.format( "&quot;%s&quot;", 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; #invoke environment, or false
				-- Postcondition:
				--     Return string with error message as configured;
				--            false if valid
				-- Uses:
				--     TemplatePar.framing()
				--     fold()
				--     fetch()
				--     fix()
				--     finalize()
				local duty, r
				if frame then
								TemplatePar.framing( frame )
				end
				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 )
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 &quot;%s&quot;", 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 &quot;%s&quot;", 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 )
								else
												s = frame.args[ 1 ] or ""
												r = tonumber( s )
												if ( r ) then
																s = r
												end
												if action == "valid" then
																r = TemplatePar.valid( s, options )
												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 <br />
				--                 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<br /> %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 )
				-- Check validity of one particular template parameter
				-- Precondition:
				--     access   -- id of parameter in template transclusion
				--                 string or number
				--     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()
				--     TemplatePar.downcase()
				--     TemplatePar.framing()
				--     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 = TemplatePar.framing():getParent()
								end
								r = formatted( params, access, options )
				else
								r = failure( "noname", false, options )
				end
				return finalize( r, options )
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()
TemplatePar.framing = function( frame )
				-- Ensure availability of frame object
				-- Precondition:
				--     frame  -- object; #invoke environment, or false
				-- Postcondition:
				--     Return frame object
				-- Uses:
				--     >< Local.frame
				if not Local.frame then
								if type( frame ) == "table"  and
											type( frame.args ) == "table"  and
											type( frame.getParent ) == "function"  and
											type( frame:getParent() ) == "table"  and
											type( frame:getParent().getParent ) == "function"  and
											type( frame:getParent():getParent() ) == "nil" then
												Local.frame = frame
								else
												Local.frame = mw.getCurrentFrame()
								end
				end
				return Local.frame
end -- TemplatePar.framing()
Failsafe.failsafe = function ( atleast )
				-- Retrieve versioning and check for compliance
				-- Precondition:
				--     atleast  -- string, with required version
				--                         or wikidata|item|~|@ or false
				-- Postcondition:
				--     Returns  string  -- with queried version/item, also if problem
				--              false   -- if appropriate
				-- 2020-08-17
				local since = atleast
				local last    = ( since == "~" )
				local linked  = ( since == "@" )
				local link    = ( since == "item" )
				local r
				if last  or  link  or  linked  or  since == "wikidata" then
								local item = Failsafe.item
								since = false
								if type( item ) == "number"  and  item > 0 then
												local suited = string.format( "Q%d", item )
												if link then
																r = suited
												else
																local entity = mw.wikibase.getEntity( suited )
																if type( entity ) == "table" then
																				local seek = Failsafe.serialProperty or "P348"
																				local vsn  = entity:formatPropertyValues( seek )
																				if type( vsn ) == "table"  and
																							type( vsn.value ) == "string"  and
																							vsn.value ~= "" then
																								if last  and  vsn.value == Failsafe.serial then
																												r = false
																								elseif linked then
																												if mw.title.getCurrentTitle().prefixedText
																															==  mw.wikibase.getSitelink( suited ) then
																																r = false
																												else
																																r = suited
																												end
																								else
																												r = vsn.value
																								end
																				end
																end
												end
								end
				end
				if type( r ) == "nil" then
								if not since  or  since <= Failsafe.serial then
												r = Failsafe.serial
								else
												r = false
								end
				end
				return r
end -- Failsafe.failsafe()
-- 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 -- p.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 -- p.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 -- p.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 -- p.countNotEmpty()
function p.match( frame )
				-- Combined analysis of parameters and their values
				-- Precondition:
				--     frame  -- object; #invoke environment
				-- Postcondition:
				--     Return string with error message or ""
				-- Uses:
				--     TemplatePar.framing()
				--     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 = { }
				TemplatePar.framing( frame )
				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
												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
																												s       = "%s, &quot;%s&quot;"
																												errMiss = string.format( s, errMiss, k )
																								else
																												errMiss = string.format( "&quot;%s&quot;",
																																																					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 )
								end
				end
				return r or ""
end -- p.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 -- p.valid()
p.failsafe = function ( frame )
				-- Versioning interface
				local s = type( frame )
				local since
				if s == "table" then
								since = frame.args[ 1 ]
				elseif s == "string" then
								since = frame
				end
				if since then
								since = mw.text.trim( since )
								if since == "" then
												since = false
								end
				end
				return Failsafe.failsafe( since )  or  ""
end -- p.failsafe
function p.TemplatePar()
				-- Retrieve function access for modules
				-- Postcondition:
				--     Return table with functions
				return TemplatePar
end -- p.TemplatePar()
setmetatable( p,  { __call = function ( func, ... )
																																	setmetatable( p, nil )
																																	return Failsafe
																													end } )
return p