VereinsWiki
Im>PerfektesChaos
(update)
Im>PerfektesChaos
(+pagename)
Zeile 1: Zeile 1:
--[=[ TemplatePar 2013-06-10
+
--[=[ TemplatePar 2013-06-28
 
Template parameter utility
 
Template parameter utility
 
* assert
 
* assert
Zeile 53: Zeile 53:
 
local Patterns = {
 
local Patterns = {
 
[ "ASCII" ] = "^[ -~]*$",
 
[ "ASCII" ] = "^[ -~]*$",
[ "ASCII+" ] = "^[ -~]+$",
+
[ "ASCII+" ] = "^[ -~]+$",
[ "ASCII+1" ] = "^[!-~]+$",
+
[ "ASCII+1" ] = "^[!-~]+$",
[ "n" ] = "^%-?[0-9]*$",
+
[ "n" ] = "^%-?[0-9]*$",
[ "n>0" ] = "^[0-9]*[1-9][0-9]*$",
+
[ "n>0" ] = "^[0-9]*[1-9][0-9]*$",
[ "N+" ] = "^%-?[1-9][0-9]*$",
+
[ "N+" ] = "^%-?[1-9][0-9]*$",
[ "N>0" ] = "^[1-9][0-9]*$",
+
[ "N>0" ] = "^[1-9][0-9]*$",
[ "x" ] = "^[0-9A-Fa-f]*$",
+
[ "x" ] = "^[0-9A-Fa-f]*$",
[ "x+" ] = "^[0-9A-Fa-f]+$",
+
[ "x+" ] = "^[0-9A-Fa-f]+$",
[ "X" ] = "^[0-9A-F]*$",
+
[ "X" ] = "^[0-9A-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" ] = "^%-?[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]+$",
+
[ ".0+" ] = "^%-?[0-9]*%.?[0-9]+$",
[ "ID" ] = "^[A-Za-z]?[A-Za-z_0-9]*$",
+
[ "ID" ] = "^[A-Za-z]?[A-Za-z_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]+$",
+
[ "ABC+" ] = "^[A-Z]+$",
[ "Abc" ] = "^[A-Z]*[a-z]*$",
+
[ "Abc" ] = "^[A-Z]*[a-z]*$",
[ "Abc+" ] = "^[A-Z][a-z]+$",
+
[ "Abc+" ] = "^[A-Z][a-z]+$",
[ "abc" ] = "^[a-z]*$",
+
[ "abc" ] = "^[a-z]*$",
[ "abc+" ] = "^[a-z]+$",
+
[ "abc+" ] = "^[a-z]+$",
[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$",
+
[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$",
[ "base64" ] = "^[A-Za-z0-9%+/]*$",
+
[ "base64" ] = "^[A-Za-z0-9%+/]*$",
[ "base64+" ] = "^[A-Za-z0-9%+/]+$",
+
[ "base64+" ] = "^[A-Za-z0-9%+/]+$",
[ "aa" ] = "[%a%a].*[%a%a]",
+
[ "aa" ] = "[%a%a].*[%a%a]",
[ "+" ] = "%S"
+
[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%s%-s%s]+$",
 
1, 31, 127 ),
 
[ "+" ] = "%S"
 
}
 
}
 
local patternCJK = false
 
local patternCJK = false
Zeile 686: Zeile 688:
 
return r
 
return r
 
end -- formatted()
 
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()
 
-- mw.text.trim()
 
-- TemplatePar.assert()
 
-- TemplatePar.valid()
 
local options = { mandatory = { "1" },
 
optional = { "2",
 
"cat",
 
"low",
 
"max",
 
"min",
 
"noError",
 
"template" },
 
template = "#invoke:TemplatePar|".. action .. "|"
 
}
 
local r = form( false, options )
 
if not r then
 
local s = mw.text.trim( frame.args[ 2 ] )
 
options = { cat = frame.args.cat,
 
low = frame.args.low,
 
noError = frame.args.noError,
 
template = frame.args.template
 
}
 
if type( s ) == "string" then
 
local sub = s:match( "^/(.*%S)/$" )
 
if type( sub ) == "string" then
 
sub = sub:gsub( "%%!", "|" )
 
sub = sub:gsub( "%%%(%(", "{{" )
 
sub = sub:gsub( "%%%)%)", "}}" )
 
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 = tonumber( s )
  +
if ( r ) then
  +
s = r
 
end
  +
r = TemplatePar[ action ]( s, options )
 
end
  +
end
 
return r or ""
  +
end -- furnish()
   
   
Zeile 848: Zeile 928:
 
-- Provide external access
 
-- Provide external access
 
local p = {}
 
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 -- .assert()
   
   
Zeile 913: Zeile 1.006:
 
-- Return string with error message or ""
 
-- Return string with error message or ""
 
-- Uses:
 
-- Uses:
-- form()
+
-- furnish()
  +
return furnish( frame, "valid" )
-- mw.text.trim()
 
-- TemplatePar.valid()
 
local options = { mandatory = { "1" },
 
optional = { "2",
 
"cat",
 
"low",
 
"max",
 
"min",
 
"noError",
 
"template" },
 
template = "#invoke:TemplatePar|valid|"
 
}
 
local r = form( false, options )
 
if not r then
 
local s = mw.text.trim( frame.args[ 2 ] )
 
options = { cat = frame.args.cat,
 
low = frame.args.low,
 
noError = frame.args.noError,
 
template = frame.args.template
 
}
 
if type( s ) == "string" then
 
local sub = s:match( "^/(.*%S)/$" )
 
if type( sub ) == "string" then
 
sub = sub:gsub( "%%!", "|" )
 
sub = sub:gsub( "%%%(%(", "{{" )
 
sub = sub:gsub( "%%%)%)", "}}" )
 
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 = tonumber( s )
 
if ( r ) then
 
s = r
 
end
 
r = TemplatePar.valid( s, options )
 
end
 
end
 
return r or ""
 
 
end -- .valid()
 
end -- .valid()
   

Version vom 30. Juni 2013, 19:33 Uhr

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


--[=[ TemplatePar 2013-06-28
Template parameter utility
* assert
* check
* count
* countNotEmpty
* downcase
* valid
* verify
* TemplatePar()
]=]



-- Module globals
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
l10nDef[ "en" ] = {
    badPattern  = "#invoke:TemplatePar * pattern syntax error",
    dupOpt      = "#invoke:TemplatePar * repeated optional parameter",
    dupRule     = "#invoke:TemplatePar * parameter 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",
    multiSpell  = "Error in template * multiple spelling of parameter",
    noErrorCat  = "#invoke:TemplatePar * noError and missing category",
    noname      = "#invoke:TemplatePar * 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 = "#invoke:TemplatePar * unknown rule"
}
l10nDef[ "de" ]  = {
    badPattern  = "#invoke:TemplatePar * Syntaxfehler des pattern",
    dupOpt      = "#invoke:TemplatePar * Optionsparameter wiederholt",
    dupRule     = "#invoke:TemplatePar * Parameterkonflikt key/pattern",
    empty       = "Fehler bei Vorlage * Pflichtparameter ohne Wert",
    invalid     = "Fehler bei Vorlage * Parameter ungültig",
    invalidPar  = "#invoke:TemplatePar * Ungültiger Parameter",
    minmax      = "#invoke:TemplatePar * min > max",
    multiSpell  = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen",
    noErrorCat  = "#invoke:TemplatePar * noError und keine Kategorie",
    noname      = "#invoke:TemplatePar * 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 = "#invoke:TemplatePar * Unbekannte Regel"
}
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]*$",
    [ "base64" ]   = "^[A-Za-z0-9%+/]*$",
    [ "base64+" ]  = "^[A-Za-z0-9%+/]+$",
    [ "aa" ]       = "[%a%a].*[%a%a]",
    [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%s%-s%s]+$",
                                    1, 31, 127 ),
    [ "+" ]        = "%S"
}
local patternCJK = false



local function containsCJK( s )
    -- Is any CJK character present?
    -- Precondition:
    --     s  -- string
    -- Postcondition:
    --     Return false iff no CJK present
    -- Uses:
    --     >< patternCJK
    --     mw.ustring.char()
    --     mw.ustring.match()
    local r = false
    if not patternCJK then
        patternCJK = mw.ustring.char( 91,
                                       13312, 45,  40959,
                                      131072, 45, 178207,
                                      93 )
    end
    if mw.ustring.match( s, patternCJK ) then
        r = true
    end
    return r
end -- containsCJK()



local function factory( say )
    -- Retrieve localized message string in content language
    -- Precondition:
    --     say  -- string; message ID
    -- Postcondition:
    --     Return some message string
    -- Uses:
    --     >  messagePrefix
    --     >  l10nDef
    --     mw.language.getContentLanguage()
    --     mw.message.new()
    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 failsafe( 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 -- failsafe()



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( 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()
    --     mw.getCurrentFrame()
    --     frame:getParent()
    local g, k, v
    local r = { }
    if options.low then
        g = TemplatePar.downcase( options )
    else
        g = mw.getCurrentFrame()
        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 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.noError
    --                 options.cat
    --                 options.template
    -- Postcondition:
    --     Return string or false
    -- Uses:
    --     factory()
    local r = false
    if submit then
        local opt, s
        if type( options ) == "table" then
            opt = options
        else
            opt = { }
        end
        if opt.noError then
            if not opt.cat then
                r = submit .. " " .. factory( "noErrorCat" )
            end
        else
            r = submit
        end
        if r then
            r = "<span class='error'>" .. r .. "</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, 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 k, v
    local r = false
    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 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 )
    -- Run parameter analysis on current environment
    -- Precondition:
    --     light    -- true: template transclusion;  false: #invoke
    --     options  -- table or nil; optional details
    --                 options.mandatory
    --                 options.optional
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid
    -- Uses:
    --     fold()
    --     failure()
    --     fetch()
    --     fix()
    --     finalize()
    local duty, r
    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.pattern
    --                 options.key
    --                 options.say
    --                 options.min
    --                 options.max
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     >  Patterns
    --     failure()
    --     mw.text.trim()
    --     failsafe()
    --     containsCJK()
    local r    = false
    local s    = false
    local show = nil
    local scan = false
    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
        scan = Patterns[ s ]
        if type( scan ) == "string" then
            if s == "n" or s == "0,0" or s == "0.0" then
                if not analyze:match( "[0-9]" ) then
                    scan = false
                    if options.say then
                        show = "'" .. options.say .. "'"
                    end
                    r = failure( "invalid", show, options )
                end
            end
        else
            r = failure( "unknownRule", s, options )
        end
    end
    if scan then
        local legal, got = pcall( failsafe, analyze, scan )
        if legal then
            if not got then
                if s == "aa" then
                    got = containsCJK( analyze )
                end
                if not got then
                    if options.say then
                        show = "'" .. options.say .. "'"
                    end
                    r = failure( "invalid", show, options )
                end
            end
        else
            r = failure( "badPattern",
                         scan .. " *** " .. got,
                         options )
        end
    end
    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",
                                 tostring( options.min )
                                 .. " > " ..
                                 tostring( options.max ),
                                 options )
                end
            end
            if #analyze < options.min  and  not r then
                show = " <" .. options.min
                if options.say then
                    show = 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 = 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:
    --     >  Patterns
    --     failure()
    --     mw.text.trim()
    --     format()
    --     failure()
    local r = false
    if type( assignment ) == "table" then
        local story = assignment.args[ access ]
        if type( story ) == "string" then
            if type( options ) ~= "table" then
                options = { }
            end
            options.say = access
            r = format( story, options )
        else
            r = failure( "invalid", access, options )
        end
    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()
    --     mw.text.trim()
    --     TemplatePar.assert()
    --     TemplatePar.valid()
    local options = { mandatory = { "1" },
                      optional  = { "2",
                                    "cat",
                                    "low",
                                    "max",
                                    "min",
                                    "noError",
                                    "template" },
                      template  = "#invoke:TemplatePar|".. action .. "|"
                    }
    local r       = form( false, options )
    if not r then
        local s = mw.text.trim( frame.args[ 2 ] )
        options = { cat      = frame.args.cat,
                    low      = frame.args.low,
                    noError  = frame.args.noError,
                    template = frame.args.template
                  }
        if type( s ) == "string" then
            local sub = s:match( "^/(.*%S)/$" )
            if type( sub ) == "string" then
                sub = sub:gsub( "%%!", "|" )
                sub = sub:gsub( "%%%(%(", "{{" )
                sub = sub:gsub( "%%%)%)", "}}" )
                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 = tonumber( s )
            if ( r ) then
                s = r
            end
            r = TemplatePar[ action ]( s, options )
        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 = append .. "<br />" .. 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 )
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()
    --     mw.ustring.lower()
    --     fault()
    --     failure()
    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
    --     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()
    --     mw.getCurrentFrame()
    --     frame:getParent()
    --     formatted()
    --     failure()
    --     finalize()
    local r
    if type( access ) == "string" then
        r = mw.text.trim( access )
        if #r == 0 then
            r = false
        end
    end
    if r then
        local params
        if type( options ) ~= "table" then
            options = { }
        end
        if options.low then
            params = TemplatePar.downcase( options )
        else
            params = mw.getCurrentFrame():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 )
end -- TemplatePar.verify()



-- 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 -- .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",
                                    "low",
                                    "noError",
                                    "template" },
                      template  = "#invoke:TemplatePar|check|"
                    }
    local r = form( false, options )
    if not r then
        options = { mandatory = fill( frame.args.all ),
                    optional  = fill( frame.args.opt ),
                    cat       = frame.args.cat,
                    low       = frame.args.low,
                    noError   = frame.args.noError,
                    template  = frame.args.template
                  }
        r       = form( true, options )
    end
    return r 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.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 -- .countNotEmpty()



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 -- .valid()



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



return p