Modul:TemplatePar: Unterschied zwischen den Versionen

Aus ÖsterreichWiki
Zur Navigation springen Zur Suche springen
wp>Leyo
K (Schützte „Modul:TemplatePar“: Häufig eingebundenes Modul ([Bearbeiten=Nur angemeldete, nicht neue Benutzer] (unbeschränkt) [Verschieben=Nur Administratoren] (unbeschränkt)))
wp>PerfektesChaos
(update)
Zeile 1: Zeile 1:
--[=[ TemplatePar 2013-04-28
--[=[ TemplatePar 2013-05-03
Template parameter utility
Template parameter utility
* check
* check
* count
* count
* valid
* TemplatePar()
]=]
]=]


Zeile 8: Zeile 10:


-- Module globals
-- Module globals
local invokeFrame
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
local l10nDef = {}
l10nDef[ "en" ] = {
l10nDef[ "en" ] = {
     dupOpt   = "TemplatePar#invoke: repeated optional parameter",
     dupOpt     = "TemplatePar#invoke: repeated optional parameter",
     empty     = "Error in template: undefined value for mandatory",
     empty       = "Error in template: undefined value for mandatory",
     undefined = "Error in template: mandatory parameter missing",
    invalid    = "Error in template: invalid parameter",
     unknown   = "Error in template: unknown parameter name"
    invalidPar  = "TemplatePar#invoke: invalid parameter",
    noname      = "TemplatePar#invoke: missing parameter name",
    tooLong    = "Error in template: parameter too long",
    tooShort    = "Error in template: parameter too short",
     undefined   = "Error in template: mandatory parameter missing",
     unknown     = "Error in template: unknown parameter name",
    unknownRule = "TemplatePar#invoke: unknown rule"
}
}
l10nDef[ "de" ] = {
l10nDef[ "de" ] = {
     dupOpt   = "TemplatePar#invoke: Wiederholter Optionsparameter",
     dupOpt     = "TemplatePar#invoke: Optionsparameter wiederholt",
     empty     = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
     empty       = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
     undefined = "Fehler bei Vorlage: fehlender Pflichtparameter",
    invalid    = "Fehler bei Vorlage: Parameter ungültig",
     unknown   = "Fehler bei Vorlage: Unbekannter Parametername"
    invalidPar  = "TemplatePar#invoke: Ungültiger Parameter",
    noname      = "TemplatePar#invoke: Parametername nicht angegeben",
    tooLong    = "Fehler bei Vorlage: Parameter zu lang",
    tooShort    = "Fehler bei Vorlage: Parameter zu kurz",
     undefined   = "Fehler bei Vorlage: Pflichtparameter fehlt",
     unknown     = "Fehler bei Vorlage: Parametername unbekannt",
    unknownRule = "TemplatePar#invoke: Unbekannte Regel"
}
}
local Patterns = {
    [ "ASCII" ]  = "^[ -~]*$",
    [ "ASCII+" ]  = "^[ -~]+$",
    [ "ASCII+1" ] = "^[!-~]+$",
    [ "n" ]      = "^%-?[0-9]*$",        -- einzelnes Minus ausschließen
    [ "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]+$",
    [ "+" ]      = "%S"
}
function trim( s )
    -- Trim string
    -- Precondition:
    --    s  -- string or nil; to be trimmed
    -- Postcondition:
    --    Return trimmed string or nil
    local r = s
    if type( s ) == "string" then
        if s:match( "^%s*$" ) then
            r = ""
        else
            r = s:match( "^%s*(%S.*)$" )
            r = r:match( "^(.*%S)%s*$" )
        end
    end
    return r
end -- trim()
local function factory( say )
    -- Retrieve localized message string in content language
    -- Precondition:
    --    say  -- string; message ID
    -- Postcondition:
    --    Return some message string
    -- Uses:
    --    >  messagePrefix
    --    >  l10nDef
    local c = mw.language.getContentLanguage():getCode()
    local m = mw.message.new( messagePrefix .. say )
    local r = false
    if m:isBlank() then
        local l10n = l10nDef[ c ]
        if not l10n then
            l10n = l10nDef[ "en" ]
        end
        r = l10n[ say ]
    else
        m:inLanguage( c )
        r = m:plain()
    end
    if not r then
        r = "(((".. say .. ")))"
    end
    return r
end -- factory()






local function failed( spec, suspect )
local function failure( spec, suspect, options )
     -- Submit error message
     -- Submit localized error message
     -- Precondition:
     -- Precondition:
     --    spec    -- string; message ID
     --    spec    -- string; message ID
     --    suspect  -- string or nil; additional information
     --    suspect  -- string or nil; additional information
    --    options  -- table or nil; optional details
    --                options.template
     -- Postcondition:
     -- Postcondition:
     --    Return string
     --    Return string
     -- Uses:
     -- Uses:
     --    >  invokeFrame
     --    factory()
    --    >  l10nDef
     local r = factory( spec )
     local r
     if type( options ) == "table" then
    local show = invokeFrame.args[ "template" ]
         if type( options.template ) == "string" then
    local l10n = mw.language.getContentLanguage()
            if #options.template > 0 then
     l10n = l10nDef[ l10n:getCode() ]
                r = r .. " (" .. options.template .. ")"
    if not l10n then
            end
         l10n = l10nDef[ "en" ]
        end
    end
    r = l10n[ spec ]
    if show then
        r = r .. " (" .. show .. ")"
     end
     end
     if suspect then
     if suspect then
Zeile 50: Zeile 127:
     end
     end
     return r
     return r
end -- failed()
end -- failure()




Zeile 97: Zeile 174:


local function fetch()
local function fetch()
     -- Return regular table with template parameters
     -- Return regular table with all template transclusion parameters
     -- Postcondition:
     -- Postcondition:
     --    Return table; whitespace-only values as false
     --    Return table; whitespace-only values as false
     -- Uses:
     -- Uses:
     --    >  invokeFrame
     --    mw.getCurrentFrame()
     --    frame:getParent()
     --    frame:getParent()
     local k, v
     local k, v
     local r = { }
     local r = { }
     local t = invokeFrame:getParent()
     local t = mw.getCurrentFrame():getParent()
     local o = t.args
     local o = t.args
     for k, v in pairs( o ) do
     for k, v in pairs( o ) do
Zeile 122: Zeile 199:
     return r
     return r
end -- fetch()
end -- fetch()
local function figure()
    -- Return number of template parameters
    -- Postcondition:
    --    Return number, starting at 0
    -- Uses:
    --    >  invokeFrame
    --    frame:getParent()
    local k, v
    local r = 0
    local t = invokeFrame:getParent()
    local o = t.args
    for k, v in pairs( o ) do
        r = r + 1
    end -- for k, v
    return r
end -- figure()




Zeile 165: Zeile 223:
     return r
     return r
end -- fill()
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.noError
    --                options.cat
    --                options.template
    -- Postcondition:
    --    Return string or false
    local r = false
    if submit then
        local opt, s
        if type( options ) == "table" then
            opt = options
        else
            opt = { }
        end
        if opt.noError then
            r = false
        else
            r = "<span class='error'>" .. submit .. "</span>"
        end
        s = opt.cat
        if type( s ) == "string" then
            if not r then
              r = ""
            end
            if s:find( "@@@" ) then
                if type( opt.template ) == "string" then
                    s = s:gsub( "@@@", opt.template )
                end
            end
            r = r .. "[[Category:" .. s .. "]]"
        end
    end
    return r
end -- finalize()




Zeile 231: Zeile 330:




local function fix( valid, duty )
local function fix( valid, duty, options )
     -- Perform parameter analysis
     -- Perform transclusion parameter analysis
     -- Precondition:
     -- Precondition:
     --    valid -- table; unique sequence of known parameters
     --    valid   -- table; unique sequence of known parameters
     --    duty   -- table; sequence of mandatory parameters
     --    duty     -- table; sequence of mandatory parameters
    --    options  -- table or nil; optional details
     -- Postcondition:
     -- Postcondition:
     --    Return string as configured; empty if valid
     --    Return string as configured; empty if valid
     -- Uses:
     -- Uses:
    --    >  invokeFrame
     --    fetch()
     --    fetch()
     --    finder()
     --    finder()
     --    fault()
     --    fault()
     --    failed()
     --    failure()
     --    fed()
     --    fed()
     local k, v
     local k, v
Zeile 254: Zeile 353:
     end -- for k, v
     end -- for k, v
     if r then
     if r then
         r = failed( "unknown", r )
         r = failure( "unknown", r, options )
     else -- all names valid
     else -- all names valid
        -- avoid confusing consecutive error messages
         local i, s
         local i, s
         for i = 1, #duty do
         for i = 1, #duty do
Zeile 265: Zeile 363:
         end -- for i
         end -- for i
         if r then
         if r then
             r = failed( "undefined", r )
             r = failure( "undefined", r, options )
         else
         else -- all mandatory present
             for i = 1, #duty do
             for i = 1, #duty do
                 s = duty[ i ]
                 s = duty[ i ]
Zeile 274: Zeile 372:
             end -- for i
             end -- for i
             if r then
             if r then
                 r = failed( "empty", r )
                 r = failure( "empty", r, options )
             end
             end
         end
         end
     end
     end
     if r then
     return r
        if invokeFrame.args[ "noError" ] then
end -- fix()
             r = ""
 
 
 
local function format( seek, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --    seek    -- string non-empty; name of template parameter
    --    options  -- table or nil; optional details
    --                options.pattern
    --                options.key
    --                options.min
    --                options.max
    -- Postcondition:
    --    Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --    >  Patterns
    --    failure()
    --    mw.ustring.match()
    --    frame:getParent()
    local r = false
    local s
    local scan  = false
    local story = mw.getCurrentFrame():getParent()
    story = ( story.args[ seek ] or "" )
    if type( options.pattern ) == "string" then
        if options.key then
             r = failure( "duplicatedRule", false, options )
        else
            scan = options.pattern
        end
    else
        if type( options.key ) == "string" then
            s = trim( options.key )
         else
         else
             r = "<span class='error'>" .. r .. "</span>"
             s = "+"
         end
         end
         k = invokeFrame.args[ "cat" ]
         scan = Patterns[ s ]
         if k then
         if type( scan ) == "string" then
             if k:find( "@@@" ) then
             if s == "n" then
                 v = invokeFrame.args[ "template" ]
                 if trim( story ) == "-" then
                if v then
                    scan = false
                     k = k:gsub( "@@@", v )
                     r    = failure( "invalid",
                                    "'" .. seek .. "'",
                                    options )
                 end
                 end
             end
             end
             r = r .. "[[Category:" .. k .. "]]"
        else
             r = failure( "unknownRule", s, options )
        end
    end
    if scan then
        if not mw.ustring.match( story, scan ) then
            r = failure( "invalid",  "'" .. seek .. "'",  options )
        end
    end
    if options.min  and  not r then
        if type( options.min ) == "number" then
            if #story < options.min then
                r = failure( "tooShort",
                            " <" .. options.min .. " '" .. seek .. "'",
                            options )
            end
        else
            r = failure( "invalidPar", "min", options )
        end
    end
    if options.max  and  not r then
        if type( options.max ) == "number" then
            if #story > options.max then
                r = failure( "tooLong",
                            " >" .. options.max .. " '" .. seek .. "'",
                            options )
            end
        else
            r = failure( "invalidPar", "max", options )
         end
         end
    else
        r = ""
     end
     end
     return r
     return r
end -- fix()
end -- format()






local function force()
TemplatePar.check = function ( options )
     -- Initialize parameter analysis
     -- Run parameter analysis
    -- Precondition:
    --    options  -- table or nil; optional details
    --                options.mandatory
    --                options.optional
     -- Postcondition:
     -- Postcondition:
     --    Return string as configured; empty if valid
     --    Return string with error message as configured;
    --            false if valid or no answer permitted
     -- Uses:
     -- Uses:
    --    >  invokeFrame
    --    fill()
     --    fit()
     --    fit()
     --    failed()
     --    failure()
     --    fix()
     --    fix()
     local duty   = fill( invokeFrame.args[ 1 ] )
    --    finalize()
    local options = fill( invokeFrame.args[ 2 ] )
     local duty, r
    local r       = fit( duty, options )
    if type( options ) == "table" then
        if type( options.mandatory ) == "table" then
            duty = options.mandatory
        else
            duty = { }
        end
        if type( options.optional ) ~= "table" then
            options.optional = { }
        end
        r = fit( duty, options.optional )
    else
        duty = { }
        r    = { }
    end
     if type( r ) == "string" then
     if type( r ) == "string" then
         r = failed( "dupOpt", r )
         r = failure( "dupOpt", r, options )
     else
     else
         r = fix( r, duty )
         r = fix( r, duty, options )
     end
     end
    return finalize( r, options )
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
     return r
end -- force()
end -- TemplatePar.count()
 
 


TemplatePar.valid = function ( seek, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --    seek    -- string; name of template parameter
    --    options  -- table or nil; optional details
    -- Postcondition:
    --    Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --    trim()
    --    format()
    --    failure()
    --    finalize()
    local r
    if type( seek ) == "string" then
        r = trim( seek )
        if #r == 0 then
            r = false
        end
    end
    if r then
        r = format( seek, options )
    else
        r = failure( "noname", false, options )
    end
    return finalize( r, options )
end -- TemplatePar.valid()




-- Provide template access


-- Provide external access
local p = {}
local p = {}


function p.check( frame )
function p.check( frame )
Zeile 336: Zeile 561:
     --    Return string with error message or ""
     --    Return string with error message or ""
     -- Uses:
     -- Uses:
     --    force()
     --    fill()
     --     < invokeFrame
     --     TemplatePar.check()
     invokeFrame = frame
     local options = { mandatory = fill( frame.args[ 1 ] ),
     return force()
                      optional  = fill( frame.args[ 2 ] ),
                      cat      = frame.args.cat,
                      noError  = frame.args.noError,
                      template  = frame.args.template
                    }
     return TemplatePar.check( options ) or ""
end -- .check()
end -- .check()


function p.count( frame )
function p.count( frame )
     -- Count number of template parameters
     -- Count number of template parameters
    -- Precondition:
    --    frame  -- object; #invoke environment
     -- Postcondition:
     -- Postcondition:
     --    Return string with digits including "0"
     --    Return string with digits including "0"
     -- Uses:
     -- Uses:
     --    figure()
     --    TemplatePar.count()
    --      < invokeFrame
     return tostring( TemplatePar.count() )
    invokeFrame = frame
     return tostring( figure() )
end -- .count()
end -- .count()


function p.valid( frame )
function p.valid( frame )
     -- Check validity of one template parameter
     -- Check validity of one particular template parameter
     -- Precondition:
     -- Precondition:
     --    frame  -- object; #invoke environment
     --    frame  -- object; #invoke environment
Zeile 362: Zeile 592:
     --    Return string with error message or ""
     --    Return string with error message or ""
     -- Uses:
     -- Uses:
     --      < invokeFrame
     --     trim()
     invokeFrame = frame
    --    TemplatePar.valid()
     return "#invoke:TemplatePar|valid| Not yet available"
    local r = false
    local s
    local options = { cat     = frame.args.cat,
                      noError  = frame.args.noError,
                      template = frame.args.template
                    }
     s = trim( frame.args[ 2 ] )
    if type( s ) == "string" then
        local sub = s:match( "^/(.*%S)/$" )
        if type( sub ) == "string" then
            options.pattern = sub
        else
            options.key = s
        end
    end
     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 = TemplatePar.valid( s, options ) or ""
    end
    return r
end -- .valid()
end -- .valid()
function p.TemplatePar()
    -- Retrieve function access for modules
    -- Postcondition:
    --    Return table with functions
    return TemplatePar
end -- .TemplatePar()


return p
return p

Version vom 4. Mai 2013, 09:10 Uhr

Modul:Vorlage:LuaModuleDoc:142: attempt to index field 'wikibase' (a nil value)


--[=[ TemplatePar 2013-05-03
Template parameter utility
* check
* count
* valid
* TemplatePar()
]=]



-- Module globals
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
l10nDef[ "en" ] = {
    dupOpt      = "TemplatePar#invoke: repeated optional parameter",
    empty       = "Error in template: undefined value for mandatory",
    invalid     = "Error in template: invalid parameter",
    invalidPar  = "TemplatePar#invoke: invalid parameter",
    noname      = "TemplatePar#invoke: missing parameter name",
    tooLong     = "Error in template: parameter too long",
    tooShort    = "Error in template: parameter too short",
    undefined   = "Error in template: mandatory parameter missing",
    unknown     = "Error in template: unknown parameter name",
    unknownRule = "TemplatePar#invoke: unknown rule"
}
l10nDef[ "de" ]  = {
    dupOpt      = "TemplatePar#invoke: Optionsparameter wiederholt",
    empty       = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
    invalid     = "Fehler bei Vorlage: Parameter ungültig",
    invalidPar  = "TemplatePar#invoke: Ungültiger Parameter",
    noname      = "TemplatePar#invoke: Parametername nicht angegeben",
    tooLong     = "Fehler bei Vorlage: Parameter zu lang",
    tooShort    = "Fehler bei Vorlage: Parameter zu kurz",
    undefined   = "Fehler bei Vorlage: Pflichtparameter fehlt",
    unknown     = "Fehler bei Vorlage: Parametername unbekannt",
    unknownRule = "TemplatePar#invoke: Unbekannte Regel"
}
local Patterns = {
    [ "ASCII" ]   = "^[ -~]*$",
    [ "ASCII+" ]  = "^[ -~]+$",
    [ "ASCII+1" ] = "^[!-~]+$",
    [ "n" ]       = "^%-?[0-9]*$",        -- einzelnes Minus ausschließen
    [ "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]+$",
    [ "+" ]       = "%S"
}



function trim( s )
    -- Trim string
    -- Precondition:
    --     s  -- string or nil; to be trimmed
    -- Postcondition:
    --     Return trimmed string or nil
    local r = s
    if type( s ) == "string" then
        if s:match( "^%s*$" ) then
            r = ""
        else
            r = s:match( "^%s*(%S.*)$" )
            r = r:match( "^(.*%S)%s*$" )
        end
    end
    return r
end -- trim()



local function factory( say )
    -- Retrieve localized message string in content language
    -- Precondition:
    --     say  -- string; message ID
    -- Postcondition:
    --     Return some message string
    -- Uses:
    --     >  messagePrefix
    --     >  l10nDef
    local c = mw.language.getContentLanguage():getCode()
    local m = mw.message.new( messagePrefix .. say )
    local r = false
    if m:isBlank() then
        local l10n = l10nDef[ c ]
        if not l10n then
            l10n = l10nDef[ "en" ]
        end
        r = l10n[ say ]
    else
        m:inLanguage( c )
        r = m:plain()
    end
    if not r then
        r = "(((".. say .. ")))"
    end
    return r
end -- factory()



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 = r .. " (" .. options.template .. ")"
            end
        end
    end
    if suspect then
        r = r .. " " .. suspect
    end
    return r
end -- failure()



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 = store .. "; " .. s
    else
        r = s
    end
    return r
end -- fault()



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
    for k, v in pairs( haystack ) do
        if k == needle then
            return true
        end
    end -- for k, v
    return false
end -- fed()



local function fetch()
    -- Return regular table with all template transclusion parameters
    -- Postcondition:
    --    Return table; whitespace-only values as false
    -- Uses:
    --     mw.getCurrentFrame()
    --     frame:getParent()
    local k, v
    local r = { }
    local t = mw.getCurrentFrame():getParent()
    local o = t.args
    for k, v in pairs( o ) 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
    return r
end -- fetch()



local function fill( specified )
    -- Split requirement string separated by '='
    -- Precondition:
    --     specified  -- string or nil; requested parameter set
    -- Postcondition:
    --    Return sequence table
    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.noError
    --                 options.cat
    --                 options.template
    -- Postcondition:
    --     Return string or false
    local r = false
    if submit then
        local opt, s
        if type( options ) == "table" then
            opt = options
        else
            opt = { }
        end
        if opt.noError then
            r = false
        else
            r = "<span class='error'>" .. submit .. "</span>"
        end
        s = opt.cat
        if type( s ) == "string" then
            if not r then
               r = ""
            end
            if s:find( "@@@" ) then
                if type( opt.template ) == "string" then
                    s = s:gsub( "@@@", opt.template )
                end
            end
            r = r .. "[[Category:" .. s .. "]]"
        end
    end
    return r
end -- finalize()



local function finder( haystack, needle )
    -- Find needle in haystack sequence
    -- Precondition:
    --     haystack  -- table; sequence of key names
    --     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 fit( base, extend )
    -- Merge two tables, create new sequence if both not empty
    -- Precondition:
    --     base    -- table; sequence kept unchanged
    --     extend  -- table; sequence to be appended
    -- Postcondition:
    --     Return merged table, or message string if duplicated entries
    -- Uses:
    --     finder()
    --     fault()
    local r
    if #base == 0 then
        if #extend == 0 then
            r = { }
        else
            r = extend
        end
    else
        if #extend == 0 then
            r = base
        else
            local i, s
            r = false
            for i = 1, #extend do
                s = extend[ i ]
                if finder( base, s ) then
                    r = fault( r, s )
                end
            end -- for i
            if not r then
                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
    return r
end -- fit()



local function fix( valid, duty, options )
    -- Perform transclusion parameter analysis
    -- Precondition:
    --     valid    -- table; unique sequence of known parameters
    --     duty     -- table; sequence of mandatory parameters
    --     options  -- table or nil; optional details
    -- Postcondition:
    --     Return string as configured; empty if valid
    -- Uses:
    --     fetch()
    --     finder()
    --     fault()
    --     failure()
    --     fed()
    local k, v
    local r   = false
    local got = fetch()
    for k, v in pairs( got ) do
        if not finder( valid, k ) then
            r = fault( r, k )
        end
    end -- for k, v
    if r then
        r = failure( "unknown", 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 format( seek, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     seek     -- string non-empty; name of template parameter
    --     options  -- table or nil; optional details
    --                 options.pattern
    --                 options.key
    --                 options.min
    --                 options.max
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     >  Patterns
    --     failure()
    --     mw.ustring.match()
    --     frame:getParent()
    local r = false
    local s
    local scan  = false
    local story = mw.getCurrentFrame():getParent()
    story = ( story.args[ seek ] or "" )
    if type( options.pattern ) == "string" then
        if options.key then
            r = failure( "duplicatedRule", false, options )
        else
            scan = options.pattern
        end
    else
        if type( options.key ) == "string" then
            s = trim( options.key )
        else
            s = "+"
        end
        scan = Patterns[ s ]
        if type( scan ) == "string" then
            if s == "n" then
                if trim( story ) == "-" then
                    scan = false
                    r    = failure( "invalid",
                                    "'" .. seek .. "'",
                                    options )
                end
            end
        else
            r = failure( "unknownRule", s, options )
        end
    end
    if scan then
        if not mw.ustring.match( story, scan ) then
            r = failure( "invalid",  "'" .. seek .. "'",  options )
        end
    end
    if options.min  and  not r then
        if type( options.min ) == "number" then
            if #story < options.min then
                r = failure( "tooShort",
                             " <" .. options.min .. " '" .. seek .. "'",
                             options )
            end
        else
            r = failure( "invalidPar", "min", options )
        end
    end
    if options.max  and  not r then
        if type( options.max ) == "number" then
            if #story > options.max then
                r = failure( "tooLong",
                             " >" .. options.max .. " '" .. seek .. "'",
                             options )
            end
        else
            r = failure( "invalidPar", "max", options )
        end
    end
    return r
end -- format()



TemplatePar.check = function ( options )
    -- Run parameter analysis
    -- Precondition:
    --     options  -- table or nil; optional details
    --                 options.mandatory
    --                 options.optional
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     fit()
    --     failure()
    --     fix()
    --     finalize()
    local duty, r
    if type( options ) == "table" then
        if type( options.mandatory ) == "table" then
            duty = options.mandatory
        else
            duty = { }
        end
        if type( options.optional ) ~= "table" then
            options.optional = { }
        end
        r = fit( duty, options.optional )
    else
        duty = { }
        r    = { }
    end
    if type( r ) == "string" then
        r = failure( "dupOpt", r, options )
    else
        r = fix( r, duty, options )
    end
    return finalize( r, options )
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.valid = function ( seek, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     seek     -- string; name of template parameter
    --     options  -- table or nil; optional details
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     trim()
    --     format()
    --     failure()
    --     finalize()
    local r
    if type( seek ) == "string" then
        r = trim( seek )
        if #r == 0 then
            r = false
        end
    end
    if r then
        r = format( seek, options )
    else
        r = failure( "noname", false, options )
    end
    return finalize( r, options )
end -- TemplatePar.valid()



-- Provide external access
local p = {}



function p.check( frame )
    -- Check validity of template parameters
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     fill()
    --     TemplatePar.check()
    local options = { mandatory = fill( frame.args[ 1 ] ),
                      optional  = fill( frame.args[ 2 ] ),
                      cat       = frame.args.cat,
                      noError   = frame.args.noError,
                      template  = frame.args.template
                    }
    return TemplatePar.check( options ) or ""
end -- .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 -- .count()



function p.valid( frame )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     trim()
    --     TemplatePar.valid()
    local r = false
    local s
    local options = { cat      = frame.args.cat,
                      noError  = frame.args.noError,
                      template = frame.args.template
                    }
    s = trim( frame.args[ 2 ] )
    if type( s ) == "string" then
        local sub = s:match( "^/(.*%S)/$" )
        if type( sub ) == "string" then
            options.pattern = sub
        else
            options.key = s
        end
    end
    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 = TemplatePar.valid( s, options ) or ""
    end
    return r
end -- .valid()



function p.TemplatePar()
    -- Retrieve function access for modules
    -- Postcondition:
    --     Return table with functions
    return TemplatePar
end -- .TemplatePar()



return p