Jump to content

Modulo:TemplatePar

Manipud iti Wikipedia, ti nawaya nga ensiklopedia
Dokumentasion ti modulo

#REDIRECTDokumentasion para kadagiti agprogprograma iti plantilia
Daytoy a panid ket naalumaymay a baw-ing.

Kitaen pay

[urnosen ti taudan]

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 = "&#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( "^[^#<>%%[%%]|{}%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