Modul:TemplatePar

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2023年03月20日

 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
Abgerufen von „https://de.wikipedia.org/w/index.php?title=Modul:TemplatePar&oldid=232032714"