suite = "TemplatePar",
 item = 15393417,
 globals = { DateTime = 20652535,
 FileMedia = 24765326,
 Multilingual = 47541920,
 TemplUtl = 52364930,
 URLutil = 10859193 } }
--[=[
Template parameter utility
]=]

local Local = { frame = false }
local Failsafe = TemplatePar
local GlobalMod = Local


-- Module globals
Local.messagePrefix = "lua-module-TemplatePar-"
Local.L10nDef = {}
Local.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",
 unavailable = "Error in template * parameter name missing",
 undefined = "Error in template * mandatory parameter missing",
 unknown = "Error in template * unknown parameter name",
 unknownRule = "&#35;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,
 [ "ref" ] = string.format,
 [ "+" ] = "%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 
 -- 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
 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
 end
 if not lucky then
 if not suited and
 type( alt ) == "number" and
 alt > 0 then
 suited = string.format
 suited = mw.wikibase.getSitelink( suited )
 GlobalMod.globalModules[ access ] = suited or true
 end
 if type( suited ) == "string" then
 storage = suited
 finer
 lucky, r = pcall
 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
 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
 end
 if mw.ustring.match then
 r = true
 end
 return r
end -- containsCJK


local function facility
 -- 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 then
 if FileMedia.isFile( attempt ) then
 r = false
 else
 r = "notFound"
 end
 else
 r = "invalid"
 end
 elseif FileMedia.isType 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
 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
 -- 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
 if r == "-" then
 r = "invalid"
 else
 r = false
 end
 else
 r = "invalid"
 end
 end
 return r
end -- faculty


local function failure
 -- 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
 end
 return r
end -- failure


local function fair
 -- 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
end -- fair


local function familiar
 -- 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
 if accept:find then
 local group = mw.text.split
 r = false
 for i = 1, #group do
 if not MultiL.isLang then
 r = "invalid"
 break -- for i
 end
 end -- for i
 elseif MultiL.isLang then
 r = false
 else
 r = "invalid"
 end
 else
 r = "missing"
 end
 elseif accept:find then
 r = "empty"
 else
 r = false
 end
 return r
end -- familiar


local function far
 -- 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 then
 r = "empty"
 else
 r = false
 end
 return r
end -- far


local function fast
 -- 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 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 then
 r = "invalid"
 if accept:sub == "datetime/y" then
 if d.year then
 r = false
 if accept:sub == "datetime/ym" then
 if d.month then
 if accept:sub
 == "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
 -- 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
 else
 r = s
 end
 return r
end -- fault


local function feasible
 -- 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
 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
 end
 if abbr then
 r = show
 else
 r = failure
 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
 n = true
 elseif s:match( "^datetime/?y?m?d?%+?$" ) then
 r = fast
 n = true
 elseif s:match( "^image%+?:?$" ) or
 s:match( "^file%+?:?$" ) then
 r = facility
 n = true
 elseif s:match( "langs?W?%+?" ) then
 r = familiar
 n = true
 elseif s:match( "url%+?" ) then
 r = far
 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
 else
 show = s
 end
 if abbr then
 r = show
 else
 r = failure
 end
 end
 end
 end
 if scan then
 local legal, got = pcall
 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
 end
 if abbr then
 r = show
 else
 r = failure
 end
 end
 end
 else
 r = failure( "badPattern",
 string.format,
 options )
 end
 end
 return r
end -- feasible


local function fed
 -- 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
 -- 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
 -- 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
 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
 for i = #r, 1, -1 do
 s = r[ i ]
 if #s == 0 then
 table.remove
 end
 end -- for i, -1
 else
 r = { }
 end
 return r
end -- fill


local function finalize
 -- 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 = ""
 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" )
 if learn then
 local max = 1000000000
 local id = math.floor( os.clock * max )
 local sign = string.format
 local btn = mw.html.create( "span" )
 local top = mw.html.create( "div" )
 e:attr
 btn:css
 sign = string.format( "%s",
 sign, tostring( btn ) )
 top:wikitext
 mw.addWarning( tostring( top ) )
 end
 show = tostring( e )
 end
 i = show:find
 if i then
 -- No gsub since r might contain "%3" (e.g. URL)
 r = string.format( "%s%s%s",
 show:sub,
 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
 local spaces = string.format
 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
 end
 end
 cats = mw.text.split
 for i = 1, #cats do
 s = mw.text.trim( cats[ i ] )
 if #s > 0 then
 r = string.format
 end
 end -- for i
 end
 end
 end
 return r
end -- finalize


local function finder
 -- 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
 -- 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 then
 r = fault
 end
 end -- for k, v
 if lack then
 r = failure
 elseif r then
 r = failure( "unknown",
 string.format,
 options )
 else -- all names valid
 local i, s
 for i = 1, #duty do
 s = duty[ i ]
 if not fed then
 r = fault
 end
 end -- for i
 if r then
 r = failure
 else -- all mandatory present
 for i = 1, #duty do
 s = duty[ i ]
 if not got[ s ] then
 r = fault
 end
 end -- for i
 if r then
 r = failure
 end
 end
 end
 return r
end -- fix


local function flat
 -- 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
 end
 end
 r[ k ] = v
 end -- for k, v
 if e then
 r = failure
 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 then
 e = fault
 end
 end -- for i
 if e then
 r = failure
 else
 r = { }
 for i = 1, #base do
 table.insert
 end -- for i
 for i = 1, #extend do
 table.insert
 end -- for i
 end
 end
 end
 if options.low and type( r ) == "table" then
 r = flat
 end
 return r
end -- fold


local function form
 -- 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
 if type( got ) == "table" then
 r = fix
 else
 r = got
 end
 end
 return finalize
end -- form


local function format
 -- 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
 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,
 options )
 end
 end
 if #analyze < options.min and not r then
 show = " <" .. options.min
 if options.say then
 show = string.format
 end
 r = failure
 end
 else
 r = failure
 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
 end
 r = failure
 end
 else
 r = failure
 end
 end
 return r
end -- format


local function formatted
 -- 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
 end
 return r
end -- formatted


local function furnish
 -- 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
 local r = form
 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
 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
 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
 end
 end
 if r then
 r = finalize
 else
 s = frame.args[ 1 ] or ""
 r = tonumber( s )
 if ( r ) then
 s = r
 end
 if action == "valid" then
 r = TemplatePar.valid
 elseif action == "assert" then
 r = TemplatePar.assert
 end
 end
 end
 return r or ""
end -- furnish


TemplatePar.assert = function 
 -- 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
 if ( r ) then
 if ( type( append ) == "string" ) then
 if ( append ~= "" ) then
 r = string.format
 end
 else
 error
 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
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
end -- TemplatePar.downcase


TemplatePar.valid = function 
 -- 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
 else
 r = failure
 end
 return finalize
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
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
 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
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
 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
 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
 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
 r = feasible
 if r then
 if lack then
 if errMiss then
 s = "%s, &quot;%s&quot;"
 errMiss = string.format
 else
 errMiss = string.format
 end
 elseif not errMiss then
 errValues = fault
 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
 else
 r = failure
 end
 r = finalize
 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
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 
 setmetatable
 return Failsafe
 end } )