Mô đun:TemplatePar

Tài liệu mô đun[tạo]
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
Chúng tôi bán
Bài viết liên quan
Thiên Nghịch Mâu - chú cụ đặc cấp phá bỏ mọi đau khổ?
Thiên Nghịch Mâu - chú cụ đặc cấp phá bỏ mọi đau khổ?
Thiên Nghịch Mâu lần đầu tiên xuất hiện tại chương 71, thuộc sở hữu của Fushiguro Touji trong nhiệm vụ tiêu diệt Tinh Tương Thể
Kết thúc truyện Sơ Thần, là em cố ý quên anh
Kết thúc truyện Sơ Thần, là em cố ý quên anh
Đây là kết thúc trong truyện nhoa mọi người
Violet Evergarden - Full Anime + Light Novel + Ova
Violet Evergarden - Full Anime + Light Novel + Ova
Đây là câu chuyện kể về người con gái vô cảm trên hành trình tìm kiếm ý nghĩa của tình yêu
Giới thiệu về Kakuja - Tokyo Ghou
Giới thiệu về Kakuja - Tokyo Ghou
Kakuja (赫者, red one, kakuja) là một loại giáp với kagune biến hình bao phủ cơ thể của ma cà rồng. Mặc dù hiếm gặp, nhưng nó có thể xảy ra do ăn thịt đồng loại lặp đi lặp lại