Bước tới nội dung

Mô đun:TemplatePar

Bách khoa toàn thư mở Wikipedia
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  = TemplateParlocal GlobalMod = Local-- Module globalsLocal.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 = falselocal 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 falseend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 falseend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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 rend -- 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.frameend -- 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 rend -- Failsafe.failsafe()-- Provide external accesslocal 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.failsafefunction p.TemplatePar()    -- Retrieve function access for modules    -- Postcondition:    --     Return table with functions    return TemplateParend -- p.TemplatePar()setmetatable( p,  { __call = function ( func, ... )                                 setmetatable( p, nil )                                 return Failsafe                             end } )return p