Modul:TemplatePar: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>PerfektesChaos
(update)
te>PerfektesChaos
(update)
Zeile 1: Zeile 1:
--[=[ TemplatePar 2013-05-07
+
--[=[ TemplatePar 2013-05-10
 
Template parameter utility
 
Template parameter utility
 
* check
 
* check
 
* count
 
* count
 +
* downcase
 
* valid
 
* valid
 
* verify
 
* verify
Zeile 22: Zeile 23:
 
     invalidPar  = "#invoke:TemplatePar * invalid parameter",
 
     invalidPar  = "#invoke:TemplatePar * invalid parameter",
 
     minmax      = "#invoke:TemplatePar * min > max",
 
     minmax      = "#invoke:TemplatePar * min > max",
 +
    multiSpell  = "Error in template * multiple spelling of parameter",
 
     noErrorCat  = "#invoke:TemplatePar * noError and missing category",
 
     noErrorCat  = "#invoke:TemplatePar * noError and missing category",
 
     noname      = "#invoke:TemplatePar * missing parameter name",
 
     noname      = "#invoke:TemplatePar * missing parameter name",
 +
    notLow      = "#invoke:TemplatePar * not in lowercase mode",
 
     tooLong    = "Error in template * parameter too long",
 
     tooLong    = "Error in template * parameter too long",
 
     tooShort    = "Error in template * parameter too short",
 
     tooShort    = "Error in template * parameter too short",
Zeile 38: Zeile 41:
 
     invalidPar  = "#invoke:TemplatePar * Ungültiger Parameter",
 
     invalidPar  = "#invoke:TemplatePar * Ungültiger Parameter",
 
     minmax      = "#invoke:TemplatePar * min > max",
 
     minmax      = "#invoke:TemplatePar * min > max",
 +
    multiSpell  = "Fehler bei Vorlage * mehrere Parameter-Schreibweisen",
 
     noErrorCat  = "#invoke:TemplatePar * noError und keine Kategorie",
 
     noErrorCat  = "#invoke:TemplatePar * noError und keine Kategorie",
 
     noname      = "#invoke:TemplatePar * Parametername nicht angegeben",
 
     noname      = "#invoke:TemplatePar * Parametername nicht angegeben",
 +
    notLow      = "#invoke:TemplatePar * Nicht in Kleinbuchstaben",
 
     tooLong    = "Fehler bei Vorlage * Parameter zu lang",
 
     tooLong    = "Fehler bei Vorlage * Parameter zu lang",
 
     tooShort    = "Fehler bei Vorlage * Parameter zu kurz",
 
     tooShort    = "Fehler bei Vorlage * Parameter zu kurz",
Zeile 87: Zeile 92:
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return trimmed string or nil
 
     --    Return trimmed string or nil
    local r = s
 
 
     if type( s ) == "string" then
 
     if type( s ) == "string" then
 
         if s:match( "^%s*$" ) then
 
         if s:match( "^%s*$" ) then
             r = ""
+
             s = ""
 
         else
 
         else
             r = s:match( "^%s*(%S.*)$" )
+
             s = s:match( "^%s*(%S.*)$" ):match( "^(.*%S)%s*$" )
            r = r:match( "^(.*%S)%s*$" )
 
 
         end
 
         end
 
     end
 
     end
     return r
+
     return s
 
end -- trim()
 
end -- trim()
  
Zeile 216: Zeile 219:
  
  
local function fetch( low )
+
local function fetch( light, options )
     -- Return regular table with all template transclusion parameters
+
     -- Return regular table with all parameters
 
     -- Precondition:
 
     -- Precondition:
     --    low  -- true: template transclusion;  false: #invoke
+
     --    light    -- true: template transclusion;  false: #invoke
 +
    --    options  -- table; optional details
 +
    --                options.low
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Return table; whitespace-only values as false
 
     --    Return table; whitespace-only values as false
 
     -- Uses:
 
     -- Uses:
 +
    --    TemplatePar.downcase()
 
     --    mw.getCurrentFrame()
 
     --    mw.getCurrentFrame()
 
     --    frame:getParent()
 
     --    frame:getParent()
     local k, v
+
     local g, k, v
 
     local r = { }
 
     local r = { }
     local t = mw.getCurrentFrame()
+
     if options.low then
    if low then
+
        g = TemplatePar.downcase( options )
        t = t:getParent()
+
    else
 +
        g = mw.getCurrentFrame()
 +
        if light then
 +
            g = g:getParent()
 +
        end
 +
        g = g.args
 
     end
 
     end
    local o = t.args
+
     for k, v in pairs( g ) do
     for k, v in pairs( o ) do
 
 
         if type( v ) == "string" then
 
         if type( v ) == "string" then
 
             if v:match( "^%s*$" ) then
 
             if v:match( "^%s*$" ) then
Zeile 327: Zeile 337:
 
     -- Find needle in haystack sequence
 
     -- Find needle in haystack sequence
 
     -- Precondition:
 
     -- Precondition:
     --    haystack  -- table; sequence of key names
+
     --    haystack  -- table; sequence of key names, downcased if low
 
     --    needle    -- any; key name
 
     --    needle    -- any; key name
 
     -- Postcondition:
 
     -- Postcondition:
Zeile 342: Zeile 352:
  
  
local function fit( base, extend )
+
local function fit( options )
 
     -- Merge two tables, create new sequence if both not empty
 
     -- Merge two tables, create new sequence if both not empty
 
     -- Precondition:
 
     -- Precondition:
     --    base    -- table; sequence kept unchanged
+
     --    options  -- table; details
     --    extend  -- table; sequence to be appended
+
     --                 options.mandatory  sequence to keep unchanged
 +
     --                 options.optional    sequence to be appended
 +
    --                options.low        downcased expected
 
     -- Postcondition:
 
     -- Postcondition:
     --    Return merged table, or message string if duplicated entries
+
     --    Return merged table, or message string if error
 
     -- Uses:
 
     -- Uses:
 
     --    finder()
 
     --    finder()
 
     --    fault()
 
     --    fault()
     local r
+
    --    failure()
 +
    --    mw.ustring.lower()
 +
     local i, e, r, s
 +
    local base  = options.mandatory
 +
    local extend = options.optional
 
     if #base == 0 then
 
     if #base == 0 then
 
         if #extend == 0 then
 
         if #extend == 0 then
Zeile 363: Zeile 379:
 
             r = base
 
             r = base
 
         else
 
         else
             local i, s
+
             e = false
            r = false
 
 
             for i = 1, #extend do
 
             for i = 1, #extend do
 
                 s = extend[ i ]
 
                 s = extend[ i ]
 
                 if finder( base, s ) then
 
                 if finder( base, s ) then
                     r = fault( r, s )
+
                     e = fault( e, s )
 
                 end
 
                 end
 
             end -- for i
 
             end -- for i
             if not r then
+
             if e then
 +
                r = failure( "dupOpt", e, options )
 +
            else
 
                 r = { }
 
                 r = { }
 
                 for i = 1, #base do
 
                 for i = 1, #base do
Zeile 380: Zeile 397:
 
                 end -- for i
 
                 end -- for i
 
             end
 
             end
 +
        end
 +
    end
 +
    if options.low then
 +
        e = false
 +
        for i = 1, #r do
 +
            s = r[ i ]
 +
            if type( s ) == "string" then
 +
                if s ~= mw.ustring.lower( s ) then
 +
                    e = fault( e, s )
 +
                end
 +
            end
 +
        end -- for i
 +
        if e then
 +
            r = failure( "notLow", e, options )
 
         end
 
         end
 
     end
 
     end
Zeile 437: Zeile 468:
  
  
local function fold( low , options )
+
local function fold( light, options )
 
     -- Run parameter analysis on current environment
 
     -- Run parameter analysis on current environment
 
     -- Precondition:
 
     -- Precondition:
     --    low      -- true: template transclusion;  false: #invoke
+
     --    light    -- true: template transclusion;  false: #invoke
 
     --    options  -- table or nil; optional details
 
     --    options  -- table or nil; optional details
 
     --                options.mandatory
 
     --                options.mandatory
Zeile 455: Zeile 486:
 
     local duty, r
 
     local duty, r
 
     if type( options ) == "table" then
 
     if type( options ) == "table" then
         if type( options.mandatory ) == "table" then
+
         if type( options.mandatory ) ~= "table" then
             duty = options.mandatory
+
             options.mandatory = { }
        else
 
            duty = { }
 
 
         end
 
         end
 +
        duty = options.mandatory
 
         if type( options.optional ) ~= "table" then
 
         if type( options.optional ) ~= "table" then
 
             options.optional = { }
 
             options.optional = { }
 
         end
 
         end
         r = fit( duty, options.optional )
+
         r = fit( options )
 
     else
 
     else
         duty = { }
+
        options = { }
         r   = { }
+
         duty   = { }
 +
         r       = { }
 
     end
 
     end
     if type( r ) == "string" then
+
     if type( r ) == "table" then
         r = failure( "dupOpt", r, options )
+
         local got = fetch( light, options )
    else
+
         if type( got ) == "table" then
         local got = fetch( low )
+
            r = fix( r, duty, got, options )
        r = fix( r, duty, got, options )
+
        else
 +
            r = got
 +
        end
 
     end
 
     end
 
     return finalize( r, options )
 
     return finalize( r, options )
Zeile 486: Zeile 519:
 
     --                options.pattern
 
     --                options.pattern
 
     --                options.key
 
     --                options.key
 +
    --                options.low
 
     --                options.min
 
     --                options.min
 
     --                options.max
 
     --                options.max
Zeile 493: Zeile 527:
 
     -- Uses:
 
     -- Uses:
 
     --    >  Patterns
 
     --    >  Patterns
     --    mw.getCurrentFrame()()
+
    --    TemplatePar.downcase()
 +
     --    mw.getCurrentFrame()
 
     --    frame:getParent()
 
     --    frame:getParent()
 
     --    failure()
 
     --    failure()
Zeile 500: Zeile 535:
 
     local s
 
     local s
 
     local scan  = false
 
     local scan  = false
     local story = mw.getCurrentFrame():getParent()
+
     local story
    story = ( story.args[ seek ] or "" )
+
     if type( options ) ~= "table" then
     if type( options.pattern ) == "string" then
+
         options = { }
         if options.key then
+
    end
            r = failure( "dupRule", false, options )
+
    if options.low then
        else
+
        story = TemplatePar.downcase( options )
            scan = options.pattern
 
        end
 
 
     else
 
     else
         if type( options.key ) == "string" then
+
        story = mw.getCurrentFrame():getParent()
             s = trim( options.key )
+
    end
 +
    if type( story ) == "table" then
 +
        story = ( story.args[ seek ] or "" )
 +
         if type( options.pattern ) == "string" then
 +
             if options.key then
 +
                r = failure( "dupRule", false, options )
 +
            else
 +
                scan = options.pattern
 +
            end
 
         else
 
         else
             s = "+"
+
             if type( options.key ) == "string" then
        end
+
                s = trim( options.key )
        scan = Patterns[ s ]
+
            else
        if type( scan ) == "string" then
+
                s = "+"
            if s == "n" or s == "0,0" or s == "0.0" then
+
            end
              if not story:match( "[0-9]" ) then
+
            scan = Patterns[ s ]
                    scan = false
+
            if type( scan ) == "string" then
                    r    = failure( "invalid",
+
                if s == "n" or s == "0,0" or s == "0.0" then
                                    "'" .. seek .. "'",
+
                  if not story:match( "[0-9]" ) then
                                    options )
+
                        scan = false
 +
                        r    = failure( "invalid",
 +
                                        "'" .. seek .. "'",
 +
                                        options )
 +
                    end
 
                 end
 
                 end
 +
            else
 +
                r = failure( "unknownRule", s, options )
 
             end
 
             end
        else
 
            r = failure( "unknownRule", s, options )
 
 
         end
 
         end
 +
    else
 +
        r = story
 
     end
 
     end
 
     if scan then
 
     if scan then
Zeile 608: Zeile 655:
 
     return r
 
     return r
 
end -- TemplatePar.count()
 
end -- TemplatePar.count()
 +
 +
 +
 +
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 k, v
 +
    local r = { }
 +
    local t = mw.getCurrentFrame():getParent()
 +
    local o = t.args
 +
    local e = false
 +
    for k, v in pairs( o ) 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 -- TemplatePar.downcase()
  
  
Zeile 672: Zeile 753:
 
                                     "2",
 
                                     "2",
 
                                     "cat",
 
                                     "cat",
 +
                                    "low",
 
                                     "noError",
 
                                     "noError",
 
                                     "template" },
 
                                     "template" },
Zeile 681: Zeile 763:
 
                     optional  = fill( frame.args[ 2 ] ),
 
                     optional  = fill( frame.args[ 2 ] ),
 
                     cat      = frame.args.cat,
 
                     cat      = frame.args.cat,
 +
                    low      = frame.args.low,
 
                     noError  = frame.args.noError,
 
                     noError  = frame.args.noError,
 
                     template  = frame.args.template
 
                     template  = frame.args.template
Zeile 711: Zeile 794:
 
     --    fold()
 
     --    fold()
 
     --    trim()
 
     --    trim()
    --    mw.ustring.gsub()
 
 
     --    TemplatePar.valid()
 
     --    TemplatePar.valid()
 
     local options = { mandatory = { "1" },
 
     local options = { mandatory = { "1" },
 
                       optional  = { "2",
 
                       optional  = { "2",
 
                                     "cat",
 
                                     "cat",
 +
                                    "low",
 
                                     "max",
 
                                     "max",
 
                                     "min",
 
                                     "min",
Zeile 726: Zeile 809:
 
         local s = trim( frame.args[ 2 ] )
 
         local s = trim( frame.args[ 2 ] )
 
         options = { cat      = frame.args.cat,
 
         options = { cat      = frame.args.cat,
 +
                    low      = frame.args.low,
 
                     noError  = frame.args.noError,
 
                     noError  = frame.args.noError,
 
                     template = frame.args.template
 
                     template = frame.args.template

Version vom 12. Mai 2013, 19:40 Uhr

--[=[ TemplatePar 2013-05-10 Template parameter utility

  • check
  • count
  • 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",
   notLow      = "#invoke:TemplatePar * not in lowercase mode",
   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",
   notLow      = "#invoke:TemplatePar * Nicht in Kleinbuchstaben",
   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%+/]+$",
   [ "+" ]       = "%S"

}


function trim( s )

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

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
   --     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 synrax 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
   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
   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 = "" .. r .. ""
       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 .. ""
       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 fit( 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()
   --     mw.ustring.lower()
   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 then
       e = false
       for i = 1, #r do
           s = r[ i ]
           if type( s ) == "string" then
               if s ~= mw.ustring.lower( s ) then
                   e = fault( e, s )
               end
           end
       end -- for i
       if e then
           r = failure( "notLow", e, options )
       end
   end
   return r

end -- fit()


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 fold( 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:
   --     fit()
   --     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 = fit( 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 -- fold()


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.low
   --                 options.min
   --                 options.max
   -- Postcondition:
   --     Return string with error message as configured;
   --            false if valid or no answer permitted
   -- Uses:
   --     >  Patterns
   --     TemplatePar.downcase()
   --     mw.getCurrentFrame()
   --     frame:getParent()
   --     failure()
   --     failsafe()
   local r = false
   local s
   local scan  = false
   local story
   if type( options ) ~= "table" then
       options = { }
   end
   if options.low then
       story = TemplatePar.downcase( options )
   else
       story = mw.getCurrentFrame():getParent()
   end
   if type( story ) == "table" then
       story = ( story.args[ seek ] or "" )
       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 = 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 story:match( "[0-9]" ) then
                       scan = false
                       r    = failure( "invalid",
                                       "'" .. seek .. "'",
                                       options )
                   end
               end
           else
               r = failure( "unknownRule", s, options )
           end
       end
   else
       r = story
   end
   if scan then
       local legal, got = pcall( failsafe, story, scan )
       if legal then
           if not got then
               r = failure( "invalid",  "'" .. seek .. "'",  options )
           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 #story < options.min  and  not r 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 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:
   --     fold()
   return fold( 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.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 k, v
   local r = { }
   local t = mw.getCurrentFrame():getParent()
   local o = t.args
   local e = false
   for k, v in pairs( o ) 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 -- TemplatePar.downcase()


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


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:
   --     fold()
   return fold( false, options )

end -- TemplatePar.verify()


-- 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:
   --     fold()
   --     fill()
   local options = { optional  = { "1",
                                   "2",
                                   "cat",
                                   "low",
                                   "noError",
                                   "template" },
                     template  = "#invoke:TemplatePar|check|"
                   }
   local r = fold( false, options )
   if not r then
       options = { mandatory = fill( frame.args[ 1 ] ),
                   optional  = fill( frame.args[ 2 ] ),
                   cat       = frame.args.cat,
                   low       = frame.args.low,
                   noError   = frame.args.noError,
                   template  = frame.args.template
                 }
       r       = fold( 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.valid( frame )

   -- Check validity of one particular template parameter
   -- Precondition:
   --     frame  -- object; #invoke environment
   -- Postcondition:
   --     Return string with error message or ""
   -- Uses:
   --     fold()
   --     trim()
   --     TemplatePar.valid()
   local options = { mandatory = { "1" },
                     optional  = { "2",
                                   "cat",
                                   "low",
                                   "max",
                                   "min",
                                   "noError",
                                   "template" },
                     template  = "#invoke:TemplatePar|valid|"
                   }
   local r = fold( false, options )
   if not r then
       local s = 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 = TemplatePar.valid( s, options )
       end
   end
   return r or ""

end -- .valid()


function p.TemplatePar()

   -- Retrieve function access for modules
   -- Postcondition:
   --     Return table with functions
   return TemplatePar

end -- .TemplatePar()


return p