Permanently protected module
From Wikipedia, the free encyclopedia


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
Permanently protected module
From Wikipedia, the free encyclopedia


local TemplatePar = { serial  = "2023-03-20",

                      suite   = "TemplatePar",

                      item    = 15393417,

                      globals = { DateTime     = 20652535,

                                  FileMedia    = 24765326,

                                  Multilingual = 47541920,

                                  TemplUtl     = 52364930,

                                  URLutil      = 10859193 } }

--[=[

Template parameter utility

* assert

* check

* count

* countNotEmpty

* downcase()

* duplicates

* match

* valid

* verify()

* TemplatePar()

* failsafe()

]=]





local Local     = { frame = false }

local Failsafe  = TemplatePar

local GlobalMod = Local







-- Module globals

Local.messagePrefix = "lua-module-TemplatePar-"

Local.L10nDef = {}

Local.L10nDef.en = {

    badPattern  = "&#35;invoke:TemplatePar pattern syntax error",

    dupOpt      = "&#35;invoke:TemplatePar repeated optional parameter",

    dupRule     = "&#35;invoke:TemplatePar conflict key/pattern",

    empty       = "Error in template * undefined value for mandatory",

    invalid     = "Error in template * invalid parameter",

    invalidPar  = "&#35;invoke:TemplatePar invalid parameter",

    minmax      = "&#35;invoke:TemplatePar min > max",

    missing     = "&#35;invoke:TemplatePar missing library",

    multiSpell  = "Error in template * multiple spelling of parameter",

    noMSGnoCAT  = "&#35;invoke:TemplatePar neither message nor category",

    noname      = "&#35;invoke:TemplatePar missing parameter name",

    notFound    = "Error in template * missing page",

    tooLong     = "Error in template * parameter too long",

    tooShort    = "Error in template * parameter too short",

    unavailable = "Error in template * parameter name missing",

    undefined   = "Error in template * mandatory parameter missing",

    unknown     = "Error in template * unknown parameter name",

    unknownRule = "&#35;invoke:TemplatePar unknown rule"

}

Local.patterns = {

     "ASCII"     = "^[ -~]*$",

     "ASCII+"    = "^[ -~]+$",

     "ASCII+1"   = "^[!-~]+$",

     "n"         = "^[%-]?[0-9]*$",

     "n>0"       = "^[0-9]*[1-9][0-9]*$",

     "N+"        = "^[%-]?[1-9][0-9]*$",

     "N>0"       = "^[1-9][0-9]*$",

     "x"         = "^[0-9A-Fa-f]*$",

     "x+"        = "^[0-9A-Fa-f]+$",

     "X"         = "^[0-9A-F]*$",

     "X+"        = "^[0-9A-F]+$",

     "0,0"       = "^[%-]?[0-9]*,?[0-9]*$",

     "0,0+"      = "^[%-]?[0-9]+,[0-9]+$",

     "0,0+?"     = "^[%-]?[0-9]+,?[0-9]*$",

     "0.0"       = "^[%-]?[0-9]*[%.]?[0-9]*$",

     "0.0+"      = "^[%-]?[0-9]+%.[0-9]+$",

     "0.0+?"     = "^[%-]?[0-9]+[%.]?[0-9]*$",

     ".0+"       = "^[%-]?[0-9]*[%.]?[0-9]+$",

     "ID"        = "^[A-Za-z]?[A-Za-z_0-9]*$",

     "ID+"       = "^[A-Za-z][A-Za-z_0-9]*$",

     "ABC"       = "^[A-Z]*$",

     "ABC+"      = "^[A-Z]+$",

     "Abc"       = "^[A-Z]*[a-z]*$",

     "Abc+"      = "^[A-Z][a-z]+$",

     "abc"       = "^[a-z]*$",

     "abc+"      = "^[a-z]+$",

     "aBc+"      = "^[a-z]+[A-Z][A-Za-z]*$",

     "w"         = "^%S*$",

     "w+"        = "^%S+$",

     "base64"    = "^[A-Za-z0-9%+/]*$",

     "base64+"   = "^[A-Za-z0-9%+/]+$",

     "aa"        = "[%a%a].*[%a%a]",

     "pagename"  = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",

                                    1, 31, 127 ),

     "ref"       = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c",

                                    127, 34, "%-", "%-", "%-", "%x+",

                                    "%-", 34, 127 ),

     "+"         = "%S"

}

Local.boolean = { "1"     = true,

                  "true"  = true,

                  y         = true,

                  yes       = true,

                  on        = true,

                  "0"     = true,

                  "false" = true,

                  "-"     = true,

                  n         = true,

                  no        = true,

                  off       = true }

Local.patternCJK = false







local foreignModule = function ( access, advanced, append, alt, alert )

    -- Fetch global module

    -- Precondition:

    --     access    -- string, with name of base module

    --     advanced  -- true, for require(); else mw.loadData()

    --     append    -- string, with subpage part, if any; or false

    --     alt       -- number, of wikidata item of root; or false

    --     alert     -- true, for throwing error on data problem

    -- Postcondition:

    --     Returns whatever, probably table

    -- 2020-01-01

    local storage = access

    local finer = function ()

                      if append then

                          storage = string.format( "%s/%s",

                                                   storage,

                                                   append )

                      end

                  end

    local fun, lucky, r, suited

    if advanced then

        fun = require

    else

        fun = mw.loadData

    end

    GlobalMod.globalModules = GlobalMod.globalModules or { }

    suited = GlobalMod.globalModules access 

    if not suited then

        finer()

        lucky, r = pcall( fun,  "Module:" .. storage )

    end

    if not lucky then

        if not suited  and

           type( alt ) == "number"  and

           alt > 0 then

            suited = string.format( "Q%d", alt )

            suited = mw.wikibase.getSitelink( suited )

            GlobalMod.globalModules access  = suited or true

        end

        if type( suited ) == "string" then

            storage = suited

            finer()

            lucky, r = pcall( fun, storage )

        end

        if not lucky and alert then

            error( "Missing or invalid page: " .. storage )

        end

    end

    return r

end -- foreignModule()







local function Foreign( access  )

    -- Access standardized library

    -- Precondition:

    --     access  -- string, with name of base module

    -- Postcondition:

    --     Return library table, or not

    -- Uses:

    local r

    if Local access  then

        r = Local access 

    else

        local bib = foreignModule( access,

                                   true,

                                   false,

                                   TemplatePar.globals access ],

                                   false )

        if type( bib ) == "table"   and

           type( bib access  ) == "function" then

            bib = bib access ]()

            if type( bib ) == "table" then

                r               = bib

                Local access  = bib

            end

        end

    end

    return r

end -- Foreign()







local function containsCJK( analyse )

    -- Is any CJK character present?

    -- Precondition:

    --     analyse  -- string

    -- Postcondition:

    --     Return false iff no CJK present

    -- Uses:

    --     >< Local.patternCJK

    --     mw.ustring.char()

    --     mw.ustring.match()

    local r = false

    if not Local.patternCJK then

        Local.patternCJK = mw.ustring.char( 91,

                                       13312, 45,  40959,

                                      131072, 45, 178207,

                                      93 )

    end

    if mw.ustring.match( analyse, Local.patternCJK ) then

        r = true

    end

    return r

end -- containsCJK()







local function facility( accept, attempt )

    -- Check string as possible file name or other source page

    -- Precondition:

    --     accept   -- string; requirement

    --                         file

    --                         file+

    --                         file:

    --                         file:+

    --                         image

    --                         image+

    --                         image:

    --                         image:+

    --     attempt  -- string; to be tested

    -- Postcondition:

    --     Return error keyword, or false

    -- Uses:

    --     Module:FileMedia

    --     Foreign()

    --     FileMedia.isFile()

    --     FileMedia.isType()

    local r

    if attempt and attempt ~= "" then

        local FileMedia = Foreign( "FileMedia" )

        if FileMedia  and  type( FileMedia.isFile ) == "function"

                      and  type( FileMedia.isType ) == "function" then

            local s, live = accept:match( "^([a-z]+)(:?)%+?$" )

            if live then

                if FileMedia.isType( attempt, s ) then

                    if FileMedia.isFile( attempt ) then

                        r = false

                    else

                        r = "notFound"

                    end

                else

                    r = "invalid"

                end

            elseif FileMedia.isType( attempt, s ) then

                r = false

            else

                r = "invalid"

            end

        else

            r = "missing"

        end

    elseif accept:match( "%+$" ) then

        r = "empty"

    else

        r = false

    end

    return r

end -- facility()







local function factory( say )

    -- Retrieve localized message string in content language

    -- Precondition:

    --     say  -- string; message ID

    -- Postcondition:

    --     Return some message string

    -- Uses:

    --     >  Local.messagePrefix

    --     >  Local.L10nDef

    --     mw.message.new()

    --     mw.language.getContentLanguage()

    --     Module:Multilingual

    --     Foreign()

    --     TemplatePar.framing()

    --     Multilingual.tabData()

    local m = mw.message.new( Local.messagePrefix .. say )

    local r = false

    if m:isBlank() then

        local c = mw.language.getContentLanguage():getCode()

        local l10n = Local.L10nDef c 

        if l10n then

            r = l10n say 

        else

            local MultiL = Foreign( "Multilingual" )

            if MultiL  and  type( MultiL.tabData ) == "function" then

                local lang

                r, lang = MultiL.tabData( "I18n/Module:TemplatePar",

                                          say,

                                          false,

                                          TemplatePar.framing() )

            end

        end

        if not r then

            r = Local.L10nDef.en say 

        end

    else

        m:inLanguage( c )

        r = m:plain()

    end

    if not r then

        r = string.format( "(((%s)))", say )

    end

    return r

end -- factory()







local function faculty( accept, attempt )

    -- Check string as possible boolean

    -- Precondition:

    --     accept   -- string; requirement

    --                         boolean

    --                         boolean+

    --     attempt  -- string; to be tested

    -- Postcondition:

    --     Return error keyword, or false

    -- Uses:

    --     Module:TemplUtl

    --     Foreign()

    --     TemplUtl.faculty()

    local r

    r = mw.text.trim( attempt ):lower()

    if r == "" then

        if accept == "boolean+" then

            r = "empty"

        else

            r = false

        end

    elseif Local.boolean r   or   r:match( "^[01%-]+$" ) then

        r = false

    else

        local TemplUtl = Foreign( "TemplUtl" )

        if TemplUtl  and  type( TemplUtl.faculty ) == "function" then

            r = TemplUtl.faculty( r, "-" )

            if r == "-" then

                r = "invalid"

            else

                r = false

            end

        else

            r = "invalid"

        end

    end

    return r

end -- faculty()







local function failure( spec, suspect, options )

    -- Submit localized error message

    -- Precondition:

    --     spec     -- string; message ID

    --     suspect  -- string or nil; additional information

    --     options  -- table or nil; optional details

    --                 options.template

    -- Postcondition:

    --     Return string

    -- Uses:

    --     factory()

    local r = factory( spec )

    if type( options ) == "table" then

        if type( options.template ) == "string" then

            if #options.template > 0 then

                r = string.format( "%s (%s)", r, options.template )

            end

        end

    end

    if suspect then

        r = string.format( "%s: %s", r, suspect )

    end

    return r

end -- failure()







local function fair( story, scan )

    -- Test for match (possibly user-defined with syntax error)

    -- Precondition:

    --     story  -- string; parameter value

    --     scan   -- string; pattern

    -- Postcondition:

    --     Return nil, if not matching, else non-nil

    -- Uses:

    --     mw.ustring.match()

    return  mw.ustring.match( story, scan )

end -- fair()







local function familiar( accept, attempt )

    -- Check string as possible language name or list

    -- Precondition:

    --     accept   -- string; requirement

    --                         lang

    --                         langs

    --                         langW

    --                         langsW

    --                         lang+

    --                         langs+

    --                         langW+

    --                         langsW+

    --     attempt  -- string; to be tested

    -- Postcondition:

    --     Return error keyword, or false

    -- Uses:

    --     Module:Multilingual

    --     Foreign()

    --     Multilingual.isLang()

    local r

    if attempt and attempt ~= "" then

        local MultiL = Foreign( "Multilingual" )

        if MultiL  and  type( MultiL.isLang ) == "function" then

            local lazy = accept:find( "W", 1, true )

            if accept:find( "s", 1, true ) then

                local group = mw.text.split( attempt, "%s+" )

                r = false

                for i = 1, #group do

                    if not MultiL.isLang( group i ], lazy ) then

                        r = "invalid"

                        break -- for i

                    end

                end -- for i

            elseif MultiL.isLang( attempt, lazy ) then

                r = false

            else

                r = "invalid"

            end

        else

            r = "missing"

        end

    elseif accept:find( "+", 1, true ) then

        r = "empty"

    else

        r = false

    end

    return r

end -- familiar()







local function far( accept, attempt )

    -- Check string as possible URL

    -- Precondition:

    --     accept   -- string; requirement

    --                         url

    --                         url+

    --     attempt  -- string; to be tested

    -- Postcondition:

    --     Return error keyword, or false

    -- Uses:

    --     Module:URLutil

    --     Foreign()

    --     URLutil.isWebURL()

    local r

    if attempt and attempt ~= "" then

        local URLutil = Foreign( "URLutil" )

        if URLutil  and  type( URLutil.isWebURL ) == "function" then

            if URLutil.isWebURL( attempt ) then

                r = false

            else

                r = "invalid"

            end

        else

            r = "missing"

        end

    elseif accept:find( "+", 1, true ) then

        r = "empty"

    else

        r = false

    end

    return r

end -- far()







local function fast( accept, attempt )

    -- Check string as possible date or time

    -- Precondition:

    --     accept   -- string; requirement

    --                         datetime

    --                         datetime+

    --                         datetime/y

    --                         datetime/y+

    --                         datetime/ym

    --                         datetime/ym+

    --                         datetime/ymd

    --                         datetime/ymd+

    --     attempt  -- string; to be tested

    -- Postcondition:

    --     Return error keyword, or false

    -- Uses:

    --     Module:DateTime

    --     Foreign()

    --     DateTime.DateTime()

    local r

    r = mw.text.trim( attempt )

    if r == "" then

        if accept:find( "+", 1, true ) then

            r = "empty"

        else

            r = false

        end

    else

        local DateTime = Foreign( "DateTime" )

        if type( DateTime ) == "table" then

            local d = DateTime( attempt )

            if type( d ) == "table" then

                if accept:find( "/", 1, true ) then

                    r = "invalid"

                    if accept:sub( 1, 10 ) == "datetime/y" then

                        if d.year then

                            r = false

                            if accept:sub( 1, 11 ) == "datetime/ym" then

                                if d.month then

                                    if accept:sub( 1, 12 )

                                                   == "datetime/ymd" then

                                        if not d.dom then

                                            r = "invalid"

                                        end

                                    end

                                else

                                    r = "invalid"

                                end

                            end

                        end

                    end

                else

                    r = false

                end

            else

                r = "invalid"

            end

        else

            r = "invalid"

        end

    end

    return r

end -- fast()







local function fault( store, key )

    -- Add key to collection string and insert separator

    -- Precondition:

    --     store  -- string or nil or false; collection string

    --     key    -- string or number; to be appended

    -- Postcondition:

    --     Return string; extended

    local r

    local s

    if type( key ) == "number" then

        s = tostring( key )

    else

        s = key

    end

    if store then

        r = string.format( "%s; %s", store, s )

    else

        r = s

    end

    return r

end -- fault()







local function feasible( analyze, options, abbr )

    -- Check content of a value

    -- Precondition:

    --     analyze  -- string to be analyzed

    --     options  -- table or nil; optional details

    --                 options.pattern

    --                 options.key

    --                 options.say

    --     abbr     -- true: abbreviated error message

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid or no answer permitted

    -- Uses:

    --     >  Local.patterns

    --     failure()

    --     mw.text.trim()

    --     faculty()

    --     fast()

    --     facility()

    --     familiar()

    --     far()

    --     fair()

    --     containsCJK()

    local r     = false

    local s     = false

    local show  = nil

    local scan  = false

    local stuff = mw.text.trim( analyze )

    if type( options.pattern ) == "string" then

        if options.key then

            r = failure( "dupRule", false, options )

        else

            scan = options.pattern

        end

    else

        if type( options.key ) == "string" then

            s = mw.text.trim( options.key )

        else

            s = "+"

        end

        if s ~= "*" then

            scan = Local.patterns s 

        end

        if type( scan ) == "string" then

            if s == "n" or s == "0,0" or s == "0.0" then

                if not stuff:match( "[0-9]" )  and

                   not stuff:match( "^%s*$" ) then

                    scan = false

                    if options.say then

                        show = string.format( "&quot;%s&quot;", options.say )

                    end

                    if abbr then

                        r = show

                    else

                        r = failure( "invalid", show, options )

                    end

                end

            end

        elseif s ~= "*" then

            local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" )

            if op then

                n = tonumber( n )

                if n then

                    local i = tonumber( stuff )

                    if i then

                        if op == "<" then

                            i = ( i < n )

                        elseif op == "<=" then

                            i = ( i <= n )

                        elseif op == ">" then

                            i = ( i > n )

                        elseif op == ">=" then

                            i = ( i >= n )

                        elseif op == "==" then

                            i = ( i == n )

                        elseif op == "!=" then

                            i = ( i ~= n )

                        else

                            n = false

                        end

                    end

                    if not i then

                        r = "invalid"

                    end

                elseif plus then

                    r = "undefined"

                end

            elseif s:match( "^boolean%+?$" ) then

                r = faculty( s, stuff )

                n = true

            elseif s:match( "^datetime/?y?m?d?%+?$" ) then

                r = fast( s, stuff )

                n = true

            elseif s:match( "^image%+?:?$" )  or

                   s:match( "^file%+?:?$" ) then

                r = facility( s, stuff )

                n = true

            elseif s:match( "langs?W?%+?" ) then

                r = familiar( s, stuff )

                n = true

            elseif s:match( "url%+?" ) then

                r = far( s, stuff )

                n = true

            end

-- datetime+

-- iso8631+

-- line+

            if not n and not r then

                r = "unknownRule"

            end

            if r then

                if options.say then

                    show = string.format( "&quot;%s&quot; %s", options.say, s )

                else

                    show = s

                end

                if abbr then

                    r = show

                else

                    r = failure( r, show, options )

                end

            end

        end

    end

    if scan then

        local legal, got = pcall( fair, stuff, scan )

        if legal then

            if not got then

                if s == "aa" then

                    got = containsCJK( stuff )

                end

                if not got then

                    if options.say then

                        show = string.format( "&quot;%s&quot;", options.say )

                    end

                    if abbr then

                        r = show

                    else

                        r = failure( "invalid", show, options )

                    end

                end

            end

        else

            r = failure( "badPattern",

                         string.format( "%s *** %s", scan, got ),

                         options )

        end

    end

    return r

end -- feasible()







local function fed( haystack, needle )

    -- Find needle in haystack map

    -- Precondition:

    --     haystack  -- table; map of key values

    --     needle    -- any; identifier

    -- Postcondition:

    --     Return true iff found

    local k, v, r

    for k, v in pairs( haystack ) do

        if k == needle then

            r = true

        end

    end -- for k, v

    return r or false

end -- fed()







local function fetch( light, options )

    -- Return regular table with all parameters

    -- Precondition:

    --     light    -- true: template transclusion;  false: #invoke

    --     options  -- table; optional details

    --                 options.low

    -- Postcondition:

    --     Return table; whitespace-only values as false

    -- Uses:

    --     TemplatePar.downcase()

    --     TemplatePar.framing()

    --     frame:getParent()

    local g, k, v

    local r = { }

    if options.low then

        g = TemplatePar.downcase( options )

    else

        g = TemplatePar.framing()

        if light then

            g = g:getParent()

        end

        g = g.args

    end

    if type( g ) == "table"  then

        r = { }

        for k, v in pairs( g ) do

            if type( v ) == "string" then

                if v:match( "^%s*$" ) then

                    v = false

                end

            else

                v = false

            end

            if type( k ) == "number" then

                k = tostring( k )

            end

            r k  = v

        end -- for k, v

    else

        r = g

    end

    return r

end -- fetch()







local function figure( append, options )

    -- Extend options by rule from #invoke strings

    -- Precondition:

    --     append   -- string or nil; requested rule

    --     options  --  table; details

    --                  ++ .key

    --                  ++ .pattern

    -- Postcondition:

    --     Return sequence table

    local r = options

    if type( append ) == "string" then

        local story = mw.text.trim( append )

        local sub   = story:match( "^/(.*%S)/$" )

        if type( sub ) == "string" then

            sub             = sub:gsub( "%%!", "|" )

                                 :gsub( "%%%(%(", "{{" )

                                 :gsub( "%%%)%)", "}}" )

                                 :gsub( "\\n", string.char( 10 ) )

            options.pattern = sub

            options.key     = nil

        else

            options.key     = story

            options.pattern = nil

        end

    end

    return r

end -- figure()







local function fill( specified )

    -- Split requirement string separated by '='

    -- Precondition:

    --     specified  -- string or nil; requested parameter set

    -- Postcondition:

    --     Return sequence table

    -- Uses:

    --     mw.text.split()

    local r

    if specified then

        local i, s

        r = mw.text.split( specified, "%s*=%s*" )

        for i = #r, 1, -1 do

            s = r i 

            if #s == 0 then

                table.remove( r, i )

            end

        end -- for i, -1

    else

        r = { }

    end

    return r

end -- fill()







local function finalize( submit, options )

    -- Finalize message

    -- Precondition:

    --     submit   -- string or false or nil; non-empty error message

    --     options  -- table or nil; optional details

    --                 options.format

    --                 options.preview

    --                 options.cat

    --                 options.template

    -- Postcondition:

    --     Return string or false

    -- Uses:

    --     TemplatePar.framing()

    --     factory()

    local r = false

    if submit then

        local lazy  = false

        local learn = false

        local show  = false

        local opt, s

        if type( options ) == "table" then

            opt  = options

            show = opt.format

            lazy = ( show == ""  or  show == "0"  or  show == "-" )

            s    = opt.preview

            if type( s ) == "string"  and

               s ~= ""  and  s ~= "0"  and  s ~= "-" then

                local sniffer = "{{REVISIONID}}"

                if lazy then

                    show = ""

                    lazy = false

                end

                if TemplatePar.framing():preprocess( sniffer ) == "" then

                    if s == "1" then

                        show = "*"

                    else

                        show = s

                    end

                    learn = true

                end

            end

        else

            opt = { }

        end

        if lazy then

            if not opt.cat then

                r = string.format( "%s %s",

                                   submit,  factory( "noMSGnoCAT" ) )

            end

        else

            r = submit

        end

        if r  and  not lazy then

            local i

            if not show  or  show == "*" then

                local e = mw.html.create( "span" )

                                 :attr( "class", "error" )

                                 :wikitext( "@@@" )

                if learn then

                    local max  = 1000000000

                    local id   = math.floor( os.clock() * max )

                    local sign = string.format( "error_%d", id )

                    local btn  = mw.html.create( "span" )

                    local top  = mw.html.create( "div" )

                    e:attr( "id", sign )

                    btn:css( { "background"      = "#FFFF00",

                               "border"          = "#FF0000 3px solid",

                               "font-weight"     = "bold",

                               "padding"         = "2px",

                               "text-decoration" = "none" } )

                       :wikitext( "&gt;&gt;&gt;" )

                    sign = string.format( "[[#%s|%s]]",

                                          sign,  tostring( btn ) )

                    top:wikitext( sign, "&#160;", submit )

                    mw.addWarning( tostring( top ) )

                end

                show = tostring( e )

            end

            i = show:find( "@@@", 1, true )

            if i then

                -- No gsub() since r might contain "%3" (e.g. URL)

                r = string.format( "%s%s%s",

                                   show:sub( 1,  i - 1 ),

                                   r,

                                   show:sub( i + 3 ) )

            else

                r = show

            end

        end

        if learn and r then

            -- r = fatal( r )

        end

        s = opt.cat

        if type( s ) == "string" then

            local link

            if opt.errNS then

                local ns = mw.title.getCurrentTitle().namespace

                local st = type( opt.errNS )

                if st == "string" then

                    local space  = string.format( ".*%%s%d%%s.*", ns )

                    local spaces = string.format( " %s ", opt.errNS )

                    if spaces:match( space ) then

                        link = true

                    end

                elseif st == "table" then

                    for i = 1, #opt.errNS do

                        if opt.errNS i  == ns then

                            link = true

                            break    -- for i

                        end

                    end -- for i

                end

            else

                link = true

            end

            if link then

                local cats, i

                if not r then

                   r = ""

                end

                if s:find( "@@@" ) then

                    if type( opt.template ) == "string" then

                        s = s:gsub( "@@@", opt.template )

                    end

                end

                cats = mw.text.split( s, "%s*#%s*" )

                for i = 1, #cats do

                    s = mw.text.trim( cats i  )

                    if #s > 0 then

                        r = string.format( "%s[[Category:%s]]", r, s )

                    end

                end -- for i

            end

        end

    end

    return r

end -- finalize()







local function finder( haystack, needle )

    -- Find needle in haystack sequence

    -- Precondition:

    --     haystack  -- table; sequence of key names, downcased if low

    --     needle    -- any; key name

    -- Postcondition:

    --     Return true iff found

    local i

    for i = 1, #haystack do

        if haystack i  == needle then

            return true

        end

    end -- for i

    return false

end -- finder()







local function fix( valid, duty, got, options )

    -- Perform parameter analysis

    -- Precondition:

    --     valid    -- table; unique sequence of known parameters

    --     duty     -- table; sequence of mandatory parameters

    --     got      -- table; sequence of current parameters

    --     options  -- table or nil; optional details

    -- Postcondition:

    --     Return string as configured; empty if valid

    -- Uses:

    --     finder()

    --     fault()

    --     failure()

    --     fed()

    local r = false

    local lack

    for k, v in pairs( got ) do

        if k == "" then

            lack = true

            break    -- for k, v

        elseif not finder( valid, k ) then

            r = fault( r, k )

        end

    end -- for k, v

    if lack then

        r = failure( "unavailable", false, options )

    elseif r then

        r = failure( "unknown",

                     string.format( "&quot;%s&quot;", r ),

                     options )

    else -- all names valid

        local i, s

        for i = 1, #duty do

            s = duty i 

            if not fed( got, s ) then

                r = fault( r, s )

            end

        end -- for i

        if r then

            r = failure( "undefined", r, options )

        else -- all mandatory present

            for i = 1, #duty do

                s = duty i 

                if not got s  then

                    r = fault( r, s )

                end

            end -- for i

            if r then

                r = failure( "empty", r, options )

            end

        end

    end

    return r

end -- fix()







local function flat( collection, options )

    -- Return all table elements with downcased string

    -- Precondition:

    --     collection  -- table; k=v pairs

    --     options     -- table or nil; optional messaging details

    -- Postcondition:

    --     Return table, may be empty; or string with error message.

    -- Uses:

    --     mw.ustring.lower()

    --     fault()

    --     failure()

    local k, v

    local r = { }

    local e = false

    for k, v in pairs( collection ) do

        if type ( k ) == "string" then

            k = mw.ustring.lower( k )

            if r k  then

                e = fault( e, k )

            end

        end

        r k  = v

    end -- for k, v

    if e then

        r = failure( "multiSpell", e, options )

    end

    return r

end -- flat()







local function fold( options )

    -- Merge two tables, create new sequence if both not empty

    -- Precondition:

    --     options  -- table; details

    --                 options.mandatory   sequence to keep unchanged

    --                 options.optional    sequence to be appended

    --                 options.low         downcased expected

    -- Postcondition:

    --     Return merged table, or message string if error

    -- Uses:

    --     finder()

    --     fault()

    --     failure()

    --     flat()

    local i, e, r, s

    local base   = options.mandatory

    local extend = options.optional

    if #base == 0 then

        if #extend == 0 then

            r = { }

        else

            r = extend

        end

    else

        if #extend == 0 then

            r = base

        else

            e = false

            for i = 1, #extend do

                s = extend i 

                if finder( base, s ) then

                    e = fault( e, s )

                end

            end -- for i

            if e then

                r = failure( "dupOpt", e, options )

            else

                r = { }

                for i = 1, #base do

                    table.insert( r, base i  )

                end -- for i

                for i = 1, #extend do

                    table.insert( r, extend i  )

                end -- for i

            end

        end

    end

    if options.low  and  type( r ) == "table" then

        r = flat( r, options )

    end

    return r

end -- fold()







local function form( light, options, frame )

    -- Run parameter analysis on current environment

    -- Precondition:

    --     light    -- true: template transclusion;  false: #invoke

    --     options  -- table or nil; optional details

    --                 options.mandatory

    --                 options.optional

    --     frame    -- object; #invoke environment, or false

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid

    -- Uses:

    --     TemplatePar.framing()

    --     fold()

    --     fetch()

    --     fix()

    --     finalize()

    local duty, r

    if frame then

        TemplatePar.framing( frame )

    end

    if type( options ) == "table" then

        if type( options.mandatory ) ~= "table" then

            options.mandatory = { }

        end

        duty = options.mandatory

        if type( options.optional ) ~= "table" then

            options.optional = { }

        end

        r = fold( options )

    else

        options = { }

        duty    = { }

        r       = { }

    end

    if type( r ) == "table" then

        local got = fetch( light, options )

        if type( got ) == "table" then

            r = fix( r, duty, got, options )

        else

            r = got

        end

    end

    return finalize( r, options )

end -- form()







local function format( analyze, options )

    -- Check validity of a value

    -- Precondition:

    --     analyze  -- string to be analyzed

    --     options  -- table or nil; optional details

    --                 options.say

    --                 options.min

    --                 options.max

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid or no answer permitted

    -- Uses:

    --     feasible()

    --     failure()

    local r = feasible( analyze, options, false )

    local show

    if options.min  and  not r then

        if type( options.min ) == "number" then

            if type( options.max ) == "number" then

                if options.max < options.min then

                    r = failure( "minmax",

                                 string.format( "%d > %d",

                                                options.min,

                                                options.max ),

                                 options )

                end

            end

            if #analyze < options.min  and  not r then

                show = " <" .. options.min

                if options.say then

                    show = string.format( "%s &quot;%s&quot;", show, options.say )

                end

                r = failure( "tooShort", show, options )

            end

        else

            r = failure( "invalidPar", "min", options )

        end

    end

    if options.max  and  not r then

        if type( options.max ) == "number" then

            if #analyze > options.max then

                show = " >" .. options.max

                if options.say then

                    show = string.format( "%s &quot;%s&quot;", show, options.say )

                end

                r = failure( "tooLong", show, options )

            end

        else

            r = failure( "invalidPar", "max", options )

        end

    end

    return r

end -- format()







local function formatted( assignment, access, options )

    -- Check validity of one particular parameter in a collection

    -- Precondition:

    --     assignment  -- collection

    --     access      -- id of parameter in collection

    --     options     -- table or nil; optional details

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid or no answer permitted

    -- Uses:

    --     mw.text.trim()

    --     format()

    --     failure()

    local r = false

    if type( assignment ) == "table" then

        local story = assignment.args access  or ""

        if type( access ) == "number" then

            story = mw.text.trim( story )

        end

        if type( options ) ~= "table" then

            options = { }

        end

        options.say = access

        r = format( story, options )

    end

    return r

end -- formatted()







local function furnish( frame, action )

    -- Prepare #invoke evaluation of .assert() or .valid()

    -- Precondition:

    --     frame    -- object; #invoke environment

    --     action   -- "assert" or "valid"

    -- Postcondition:

    --     Return string with error message or ""

    -- Uses:

    --     form()

    --     failure()

    --     finalize()

    --     TemplatePar.valid()

    --     TemplatePar.assert()

    local options = { mandatory = { "1" },

                      optional  = { "2",

                                    "cat",

                                    "errNS",

                                    "low",

                                    "max",

                                    "min",

                                    "format",

                                    "preview",

                                    "template" },

                      template  = string.format( "&#35;invoke:%s|%s|",

                                                 "TemplatePar",

                                                 action )

                    }

    local r       = form( false, options, frame )

    if not r then

        local s

        options = { cat      = frame.args.cat,

                    errNS    = frame.args.errNS,

                    low      = frame.args.low,

                    format   = frame.args.format,

                    preview  = frame.args.preview,

                    template = frame.args.template

                  }

        options = figure( frame.args 2 ], options )

        if type( frame.args.min ) == "string" then

            s = frame.args.min:match( "^%s*([0-9]+)%s*$" )

            if s then

                options.min = tonumber( s )

            else

                r = failure( "invalidPar",

                             "min=" .. frame.args.min,

                             options )

            end

        end

        if type( frame.args.max ) == "string" then

            s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )

            if s then

                options.max = tonumber( s )

            else

                r = failure( "invalidPar",

                             "max=" .. frame.args.max,

                             options )

            end

        end

        if r then

            r = finalize( r, options )

        else

            s = frame.args 1  or ""

            r = tonumber( s )

            if ( r ) then

                s = r

            end

            if action == "valid" then

                r = TemplatePar.valid( s, options )

            elseif action == "assert" then

                r = TemplatePar.assert( s, "", options )

            end

        end

    end

    return r or ""

end -- furnish()







TemplatePar.assert = function ( analyze, append, options )

    -- Perform parameter analysis on a single string

    -- Precondition:

    --     analyze  -- string to be analyzed

    --     append   -- string: append error message, prepending <br />

    --                 false or nil: throw error with message

    --     options  -- table; optional details

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid

    -- Uses:

    --     format()

    local r = format( analyze, options )

    if ( r ) then

        if ( type( append ) == "string" ) then

            if ( append ~= "" ) then

                r = string.format( "%s<br /> %s", append, r )

            end

        else

            error( r, 0 )

        end

    end

    return r

end -- TemplatePar.assert()







TemplatePar.check = function ( options )

    -- Run parameter analysis on current template environment

    -- Precondition:

    --     options  -- table or nil; optional details

    --                 options.mandatory

    --                 options.optional

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid

    -- Uses:

    --     form()

    return form( true, options, false )

end -- TemplatePar.check()







TemplatePar.count = function ()

    -- Return number of template parameters

    -- Postcondition:

    --     Return number, starting at 0

    -- Uses:

    --     mw.getCurrentFrame()

    --     frame:getParent()

    local k, v

    local r = 0

    local t = mw.getCurrentFrame():getParent()

    local o = t.args

    for k, v in pairs( o ) do

        r = r + 1

    end -- for k, v

    return r

end -- TemplatePar.count()







TemplatePar.countNotEmpty = function ()

    -- Return number of template parameters with more than whitespace

    -- Postcondition:

    --     Return number, starting at 0

    -- Uses:

    --     mw.getCurrentFrame()

    --     frame:getParent()

    local k, v

    local r = 0

    local t = mw.getCurrentFrame():getParent()

    local o = t.args

    for k, v in pairs( o ) do

        if not v:match( "^%s*$" ) then

            r = r + 1

        end

    end -- for k, v

    return r

end -- TemplatePar.countNotEmpty()







TemplatePar.downcase = function ( options )

    -- Return all template parameters with downcased name

    -- Precondition:

    --     options  -- table or nil; optional messaging details

    -- Postcondition:

    --     Return table, may be empty; or string with error message.

    -- Uses:

    --     mw.getCurrentFrame()

    --     frame:getParent()

    --     flat()

    local t = mw.getCurrentFrame():getParent()

    return flat( t.args, options )

end -- TemplatePar.downcase()







TemplatePar.valid = function ( access, options )

    -- Check validity of one particular template parameter

    -- Precondition:

    --     access   -- id of parameter in template transclusion

    --                 string or number

    --     options  -- table or nil; optional details

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid or no answer permitted

    -- Uses:

    --     mw.text.trim()

    --     TemplatePar.downcase()

    --     TemplatePar.framing()

    --     frame:getParent()

    --     formatted()

    --     failure()

    --     finalize()

    local r = type( access )

    if r == "string" then

        r = mw.text.trim( access )

        if #r == 0 then

            r = false

        end

    elseif r == "number" then

        r = access

    else

        r = false

    end

    if r then

        local params

        if type( options ) ~= "table" then

            options = { }

        end

        if options.low then

            params = TemplatePar.downcase( options )

        else

            params = TemplatePar.framing():getParent()

        end

        r = formatted( params, access, options )

    else

        r = failure( "noname", false, options )

    end

    return finalize( r, options )

end -- TemplatePar.valid()







TemplatePar.verify = function ( options )

    -- Perform #invoke parameter analysis

    -- Precondition:

    --     options  -- table or nil; optional details

    -- Postcondition:

    --     Return string with error message as configured;

    --            false if valid

    -- Uses:

    --     form()

    return form( false, options, false )

end -- TemplatePar.verify()







TemplatePar.framing = function( frame )

    -- Ensure availability of frame object

    -- Precondition:

    --     frame  -- object; #invoke environment, or false

    -- Postcondition:

    --     Return frame object

    -- Uses:

    --     >< Local.frame

    if not Local.frame then

        if type( frame ) == "table"  and

           type( frame.args ) == "table"  and

           type( frame.getParent ) == "function"  and

           type( frame:getParent() ) == "table"  and

           type( frame:getParent().getParent ) == "function"  and

           type( frame:getParent():getParent() ) == "nil" then

            Local.frame = frame

        else

            Local.frame = mw.getCurrentFrame()

        end

    end

    return Local.frame

end -- TemplatePar.framing()







Failsafe.failsafe = function ( atleast )

    -- Retrieve versioning and check for compliance

    -- Precondition:

    --     atleast  -- string, with required version

    --                         or wikidata|item|~|@ or false

    -- Postcondition:

    --     Returns  string  -- with queried version/item, also if problem

    --              false   -- if appropriate

    -- 2020-08-17

    local since = atleast

    local last    = ( since == "~" )

    local linked  = ( since == "@" )

    local link    = ( since == "item" )

    local r

    if last  or  link  or  linked  or  since == "wikidata" then

        local item = Failsafe.item

        since = false

        if type( item ) == "number"  and  item > 0 then

            local suited = string.format( "Q%d", item )

            if link then

                r = suited

            else

                local entity = mw.wikibase.getEntity( suited )

                if type( entity ) == "table" then

                    local seek = Failsafe.serialProperty or "P348"

                    local vsn  = entity:formatPropertyValues( seek )

                    if type( vsn ) == "table"  and

                       type( vsn.value ) == "string"  and

                       vsn.value ~= "" then

                        if last  and  vsn.value == Failsafe.serial then

                            r = false

                        elseif linked then

                            if mw.title.getCurrentTitle().prefixedText

                               ==  mw.wikibase.getSitelink( suited ) then

                                r = false

                            else

                                r = suited

                            end

                        else

                            r = vsn.value

                        end

                    end

                end

            end

        end

    end

    if type( r ) == "nil" then

        if not since  or  since <= Failsafe.serial then

            r = Failsafe.serial

        else

            r = false

        end

    end

    return r

end -- Failsafe.failsafe()







-- Provide external access

local p = {}







function p.assert( frame )

    -- Perform parameter analysis on some single string

    -- Precondition:

    --     frame  -- object; #invoke environment

    -- Postcondition:

    --     Return string with error message or ""

    -- Uses:

    --     furnish()

    return furnish( frame, "assert" )

end -- p.assert()







function p.check( frame )

    -- Check validity of template parameters

    -- Precondition:

    --     frame  -- object; #invoke environment

    -- Postcondition:

    --     Return string with error message or ""

    -- Uses:

    --     form()

    --     fill()

    local options = { optional  = { "all",

                                    "opt",

                                    "cat",

                                    "errNS",

                                    "low",

                                    "format",

                                    "preview",

                                    "template" },

                      template  = "&#35;invoke:TemplatePar|check|"

                    }

    local r = form( false, options, frame )

    if not r then

        options = { mandatory = fill( frame.args.all ),

                    optional  = fill( frame.args.opt ),

                    cat       = frame.args.cat,

                    errNS     = frame.args.errNS,

                    low       = frame.args.low,

                    format    = frame.args.format,

                    preview   = frame.args.preview,

                    template  = frame.args.template

                  }

        r       = form( true, options, frame )

    end

    return r or ""

end -- p.check()







function p.count( frame )

    -- Count number of template parameters

    -- Postcondition:

    --     Return string with digits including "0"

    -- Uses:

    --     TemplatePar.count()

    return tostring( TemplatePar.count() )

end -- p.count()







function p.countNotEmpty( frame )

    -- Count number of template parameters which are not empty

    -- Postcondition:

    --     Return string with digits including "0"

    -- Uses:

    --     TemplatePar.countNotEmpty()

    return tostring( TemplatePar.countNotEmpty() )

end -- p.countNotEmpty()







function p.match( frame )

    -- Combined analysis of parameters and their values

    -- Precondition:

    --     frame  -- object; #invoke environment

    -- Postcondition:

    --     Return string with error message or ""

    -- Uses:

    --     TemplatePar.framing()

    --     mw.text.trim()

    --     mw.ustring.lower()

    --     failure()

    --     form()

    --     TemplatePar.downcase()

    --     figure()

    --     feasible()

    --     fault()

    --     finalize()

    local r = false

    local options = { cat      = frame.args.cat,

                      errNS    = frame.args.errNS,

                      low      = frame.args.low,

                      format   = frame.args.format,

                      preview  = frame.args.preview,

                      template = frame.args.template

                    }

    local k, v, s

    local params = { }

    TemplatePar.framing( frame )

    for k, v in pairs( frame.args ) do

        if type( k ) == "number" then

            s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )

            if s then

                s = mw.text.trim( s )

                if s == "" then

                    s = false

                end

            end

            if s then

                if options.low then

                    s = mw.ustring.lower( s )

                end

                if params s  then

                    s = params s 

                    s #s + 1  = v

                else

                    params s  = { v }

                end

            else

                r = failure( "invalidPar",  tostring( k ),  options )

                break -- for k, v

            end

        end

    end -- for k, v

    if not r then

        s = { }

        for k, v in pairs( params ) do

            s #s + 1  = k

        end -- for k, v

        options.optional = s

        r = form( true, options, frame )

    end

    if not r then

        local errMiss, errValues, lack, rule

        local targs = frame:getParent().args

        options.optional = nil

        if options.low then

            targs = TemplatePar.downcase()

        else

            targs = frame:getParent().args

        end

        errMiss   = false

        errValues = false

        for k, v in pairs( params ) do

            options.say = k

            s           = targs k 

            if s then

                if s == "" then

                    lack = true

                else

                    lack = false

                end

            else

                s    = ""

                lack = true

            end

            for r, rule in pairs( v ) do

                options = figure( rule, options )

                r       = feasible( s, options, true )

                if r then

                    if lack then

                        if errMiss then

                            s       = "%s, &quot;%s&quot;"

                            errMiss = string.format( s, errMiss, k )

                        else

                            errMiss = string.format( "&quot;%s&quot;",

                                                     k )

                        end

                    elseif not errMiss then

                        errValues = fault( errValues, r )

                    end

                    break -- for r, rule

                end

            end -- for s, rule

        end -- for k, v

        r = ( errMiss or errValues )

        if r then

            if errMiss then

                r = failure( "undefined", errMiss, options )

            else

                r = failure( "invalid", errValues, options )

            end

            r = finalize( r, options )

        end

    end

    return r or ""

end -- p.match()







function p.valid( frame )

    -- Check validity of one particular template parameter

    -- Precondition:

    --     frame  -- object; #invoke environment

    -- Postcondition:

    --     Return string with error message or ""

    -- Uses:

    --     furnish()

    return furnish( frame, "valid" )

end -- p.valid()







p.failsafe = function ( frame )

    -- Versioning interface

    local s = type( frame )

    local since

    if s == "table" then

        since = frame.args 1 

    elseif s == "string" then

        since = frame

    end

    if since then

        since = mw.text.trim( since )

        if since == "" then

            since = false

        end

    end

    return Failsafe.failsafe( since )  or  ""

end -- p.failsafe







function p.TemplatePar()

    -- Retrieve function access for modules

    -- Postcondition:

    --     Return table with functions

    return TemplatePar

end -- p.TemplatePar()







setmetatable( p,  { __call = function ( func, ... )

                                 setmetatable( p, nil )

                                 return Failsafe

                             end } )



return p

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook