Jump to content
Toggle menu
  • 8 articles
  • 75 files
  • 7 users
  • 37.8K edits
Neodyland Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:TemplatePar: Difference between revisions

From Neodyland Wiki
mNo edit summary
Undo revision 112214095 by Deavalma (talk): Commons:Village_pump&curid=751116&diff=112214938&oldid=112214833
Line 1: Line 1:
--[=[ TemplatePar 2013-07-09Template parameter utility* assert* check* count
--[=[ TemplatePar 2013-07-09
* countNotEmpty* downcase* match* valid* verify* TemplatePar()]=]
Template parameter utility
-- Module globalslocal TemplatePar = { }
* assert
local messagePrefix = "lua-module-TemplatePar-"local l10nDef = {}
* check
* count
* countNotEmpty
* downcase
* match
* valid
* verify
* TemplatePar()
]=]
 
 
 
-- Module globals
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
l10nDef[ "en" ] = {
l10nDef[ "en" ] = {
     badPattern  = "#invoke:TemplatePar pattern syntax error",
     badPattern  = "#invoke:TemplatePar pattern syntax error",
Line 18: Line 33:
     undefined  = "Error in template * mandatory parameter missing",
     undefined  = "Error in template * mandatory parameter missing",
     unknown    = "Error in template * unknown parameter name",
     unknown    = "Error in template * unknown parameter name",
     unknownRule = "#invoke:TemplatePar unknown rule"}l10nDef[ "de" ]  = {
     unknownRule = "#invoke:TemplatePar unknown rule"
}
l10nDef[ "de" ]  = {
     badPattern  = "#invoke:TemplatePar Syntaxfehler des pattern",
     badPattern  = "#invoke:TemplatePar Syntaxfehler des pattern",
     dupOpt      = "#invoke:TemplatePar Optionsparameter wiederholt",
     dupOpt      = "#invoke:TemplatePar Optionsparameter wiederholt",
Line 33: Line 50:
     undefined  = "Fehler bei Vorlage * Pflichtparameter fehlt",
     undefined  = "Fehler bei Vorlage * Pflichtparameter fehlt",
     unknown    = "Fehler bei Vorlage * Parametername unbekannt",
     unknown    = "Fehler bei Vorlage * Parametername unbekannt",
     unknownRule = "#invoke:TemplatePar Unbekannte Regel"}local Patterns = {
     unknownRule = "#invoke:TemplatePar Unbekannte Regel"
     [ "ASCII" ]  = "^[ -~]*$",   [ "ASCII+" ]  = "^[ -~]+$",
}
     [ "ASCII+1" ]  = "^[!-~]+$",   [ "n" ]        = "^%-?[0-9]*$",
local Patterns = {
     [ "ASCII" ]  = "^[ -~]*$",
    [ "ASCII+" ]  = "^[ -~]+$",
     [ "ASCII+1" ]  = "^[!-~]+$",
    [ "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>0" ]      = "^[1-9][0-9]*$",
     [ "N+" ]      = "^%-?[1-9][0-9]*$",
     [ "x" ]        = "^[0-9A-Fa-f]*$",   [ "x+" ]      = "^[0-9A-Fa-f]+$",
    [ "N>0" ]      = "^[1-9][0-9]*$",
     [ "X" ]        = "^[0-9A-F]*$",   [ "X+" ]      = "^[0-9A-F]+$",
     [ "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]+$",
Line 48: Line 72:
     [ ".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]*$",   [ "ABC" ]      = "^[A-Z]*$",
     [ "ID+" ]      = "^[A-Za-z][A-Za-z_0-9]*$",
     [ "ABC+" ]    = "^[A-Z]+$",   [ "Abc" ]      = "^[A-Z]*[a-z]*$",
    [ "ABC" ]      = "^[A-Z]*$",
     [ "Abc+" ]    = "^[A-Z][a-z]+$",   [ "abc" ]      = "^[a-z]*$",
     [ "ABC+" ]    = "^[A-Z]+$",
     [ "abc+" ]    = "^[a-z]+$",   [ "aBc+" ]    = "^[a-z]+[A-Z][A-Za-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%+/]*$",
     [ "base64+" ]  = "^[A-Za-z0-9%+/]+$",   [ "aa" ]      = "[%a%a].*[%a%a]",
     [ "base64+" ]  = "^[A-Za-z0-9%+/]+$",
    [ "aa" ]      = "[%a%a].*[%a%a]",
     [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%s%-s%s]+$",
     [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%s%-s%s]+$",
                                     1, 31, 127 ),   [ "+" ]        = "%S"}
                                     1, 31, 127 ),
local patternCJK = falselocal function containsCJK( s )
    [ "+" ]        = "%S"
     -- Is any CJK character present?   -- Precondition:   --    s  -- string
}
     -- Postcondition:   --    Return false iff no CJK present   -- Uses:
local patternCJK = false
     --    >< patternCJK   --    mw.ustring.char()
 
     --    mw.ustring.match()   local r = false   if not patternCJK then
 
 
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,
         patternCJK = mw.ustring.char( 91,
                                       13312, 45,  40959,
                                       13312, 45,  40959,
                                       131072, 45, 178207,
                                       131072, 45, 178207,
                                       93 )   end
                                       93 )
     if mw.ustring.match( s, patternCJK ) then       r = true   end   return r
    end
end -- containsCJK()local function factory( say )
     if mw.ustring.match( s, patternCJK ) then
     -- Retrieve localized message string in content language   -- Precondition:
        r = true
     --    say  -- string; message ID   -- Postcondition:
    end
     --    Return some message string   -- Uses:   --    >  messagePrefix
    return r
     --    >  l10nDef   --    mw.language.getContentLanguage()
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()
     --    mw.message.new()
     local c = mw.language.getContentLanguage():getCode()
     local c = mw.language.getContentLanguage():getCode()
     local m = mw.message.new( messagePrefix .. say )   local r = false
     local m = mw.message.new( messagePrefix .. say )
     if m:isBlank() then       local l10n = l10nDef[ c ]       if not l10n then
    local r = false
             l10n = l10nDef[ "en" ]       end       r = l10n[ say ]   else
     if m:isBlank() then
         m:inLanguage( c )       r = m:plain()   end   if not r then
        local l10n = l10nDef[ c ]
         r = "(((".. say .. ")))"   end   return rend -- factory()
        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 )
local function failsafe( story, scan )
     -- Test for match (possibly user-defined with syntax error)
     -- Test for match (possibly user-defined with syntax error)
     -- Precondition:   --    story  -- string; parameter value
     -- Precondition:
     --    scan  -- string; pattern   -- Postcondition:
    --    story  -- string; parameter value
     --    Return nil, if not matching, else non-nil   -- Uses:
     --    scan  -- string; pattern
     --    mw.ustring.match()   return  mw.ustring.match( story, scan )
    -- Postcondition:
end -- failsafe()local function failure( spec, suspect, options )
     --    Return nil, if not matching, else non-nil
     -- Submit localized error message   -- Precondition:
    -- 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
     --    spec    -- string; message ID
     --    suspect  -- string or nil; additional information
     --    suspect  -- string or nil; additional information
     --    options  -- table or nil; optional details
     --    options  -- table or nil; optional details
     --                options.template   -- Postcondition:
     --                options.template
     --    Return string   -- Uses:   --    factory()
    -- Postcondition:
     local r = factory( spec )   if type( options ) == "table" then
     --    Return string
    -- Uses:
    --    factory()
     local r = factory( spec )
    if type( options ) == "table" then
         if type( options.template ) == "string" then
         if type( options.template ) == "string" then
             if #options.template > 0 then
             if #options.template > 0 then
                 r = r .. " (" .. options.template .. ")"           end
                 r = r .. " (" .. options.template .. ")"
         end   end   if suspect then       r = r .. ": " .. suspect   end
            end
     return rend -- failure()local function fault( store, key )
         end
     -- Add key to collection string and insert separator   -- Precondition:
    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
     --    store  -- string or nil or false; collection string
     --    key    -- string or number; to be appended   -- Postcondition:
     --    key    -- string or number; to be appended
     --    Return string; extended   local r   local s
    -- Postcondition:
     if type( key ) == "number" then       s = tostring( key )   else
     --    Return string; extended
         s = key   end   if store then       r = store .. "; " .. s   else
    local r
         r = s   end   return rend -- fault()
    local s
local function feasible( analyze, options, abbr )   -- Check content of a value
     if type( key ) == "number" then
     -- Precondition:   --    analyze  -- string to be analyzed
        s = tostring( key )
    else
         s = key
    end
    if store then
        r = store .. "; " .. s
    else
         r = s
    end
    return r
end -- fault()
 
 
 
local function feasible( analyze, options, abbr )
    -- Check content of a value
     -- Precondition:
    --    analyze  -- string to be analyzed
     --    options  -- table or nil; optional details
     --    options  -- table or nil; optional details
     --                options.pattern   --                options.key
     --                options.pattern
    --                options.key
     --                options.say
     --                options.say
     --    abbr    -- true: abbreviated error message   -- Postcondition:
     --    abbr    -- true: abbreviated error message
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid or no answer permitted   -- Uses:
     --            false if valid or no answer permitted
     --    >  Patterns   --    failure()   --    mw.text.trim()
    -- Uses:
     --    failsafe()   --    containsCJK()   local r    = false
     --    >  Patterns
     local s    = false   local show = nil   local scan = false
    --    failure()
     if type( options.pattern ) == "string" then       if options.key then
    --    mw.text.trim()
             r = failure( "dupRule", false, options )       else
     --    failsafe()
             scan = options.pattern       end   else
    --    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
         if type( options.key ) == "string" then
             s = mw.text.trim( options.key )       else           s = "+"
             s = mw.text.trim( options.key )
         end       if s ~= "*" then           scan = Patterns[ s ]       end
        else
            s = "+"
         end
        if s ~= "*" then
            scan = Patterns[ s ]
        end
         if type( scan ) == "string" then
         if type( scan ) == "string" then
             if s == "n" or s == "0,0" or s == "0.0" then
             if s == "n" or s == "0,0" or s == "0.0" then
                 if not analyze:match( "[0-9]" ) then
                 if not analyze:match( "[0-9]" ) then
                     scan = false                   if options.say then
                     scan = false
                         show = "'" .. options.say .. "'"                   end
                    if options.say then
                     if abbr then                       r = show
                         show = "'" .. options.say .. "'"
                    end
                     if abbr then
                        r = show
                     else
                     else
                         r = failure( "invalid", show, options )
                         r = failure( "invalid", show, options )
                     end               end           end
                     end
                end
            end
         elseif s ~= "*" then
         elseif s ~= "*" then
             local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" )
             local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" )
             if op then               n = tonumber( n )               if n then
             if op then
                     local i = tonumber( analyze )                   if i then
                n = tonumber( n )
                if n then
                     local i = tonumber( analyze )
                    if i then
                         if op == "<" then
                         if op == "<" then
                             i = ( i < n )
                             i = ( i < n )
Line 143: Line 280:
                             i = ( i >= n )
                             i = ( i >= n )
                         elseif op == "!=" then
                         elseif op == "!=" then
                             i = ( i ~= n )                       else
                             i = ( i ~= n )
                             n = false                       end
                        else
                     end                   if not i then
                             n = false
                         r = "invalid"                   end
                        end
                 elseif plus then                   r = "undefined"
                     end
                 end           end           if not n and not r then
                    if not i then
                 r = "unknownRule"           end           if r then
                         r = "invalid"
                    end
                 elseif plus then
                    r = "undefined"
                 end
            end
            if not n and not r then
                 r = "unknownRule"
            end
            if r then
                 if options.say then
                 if options.say then
                     show = "'" .. options.say .. "' " .. s               else
                     show = "'" .. options.say .. "' " .. s
                     show = s               end               if abbr then
                else
                     r = show               else
                     show = s
                     r = failure( r, show, options )               end
                end
             end       end   end   if scan then
                if abbr then
         local legal, got = pcall( failsafe, analyze, scan )       if legal then
                     r = show
             if not got then               if s == "aa" then
                else
                     got = containsCJK( analyze )               end
                     r = failure( r, show, options )
                 if not got then                   if options.say then
                end
                         show = "'" .. options.say .. "'"                   end
             end
                     if abbr then                       r = show
        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
                     if abbr then
                        r = show
                     else
                     else
                         r = failure( "invalid", show, options )
                         r = failure( "invalid", show, options )
                     end               end           end       else
                     end
                end
            end
        else
             r = failure( "badPattern",
             r = failure( "badPattern",
                         scan .. " *** " .. got,
                         scan .. " *** " .. got,
                         options )       end   end   return r
                         options )
end -- feasible()local function fed( haystack, needle )
        end
     -- Find needle in haystack map   -- Precondition:
    end
    return r
end -- feasible()
 
 
 
local function fed( haystack, needle )
     -- Find needle in haystack map
    -- Precondition:
     --    haystack  -- table; map of key values
     --    haystack  -- table; map of key values
     --    needle    -- any; identifier   -- Postcondition:
     --    needle    -- any; identifier
     --    Return true iff found   local k, v
    -- Postcondition:
     for k, v in pairs( haystack ) do       if k == needle then
     --    Return true iff found
             return true       end   end -- for k, v   return false
    local k, v
end -- fed()local function fetch( light, options )
     for k, v in pairs( haystack ) do
     -- Return regular table with all parameters   -- Precondition:
        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
     --    light    -- true: template transclusion;  false: #invoke
     --    options  -- table; optional details   --                options.low
     --    options  -- table; optional details
     -- Postcondition:   --    Return table; whitespace-only values as false
    --                options.low
     -- Uses:   --    TemplatePar.downcase()   --    mw.getCurrentFrame()
     -- Postcondition:
     --    frame:getParent()   local g, k, v   local r = { }
    --    Return table; whitespace-only values as false
     if options.low then       g = TemplatePar.downcase( options )   else
     -- Uses:
         g = mw.getCurrentFrame()       if light then
    --    TemplatePar.downcase()
             g = g:getParent()       end       g = g.args   end
    --    mw.getCurrentFrame()
     if type( g ) == "table"  then       r = { }
     --    frame:getParent()
         for k, v in pairs( g ) do           if type( v ) == "string" then
    local g, k, v
                 if v:match( "^%s*$" ) then                   v = false
    local r = { }
                 end           else               v = false           end
     if options.low then
             if type( k ) == "number" then               k = tostring( k )
        g = TemplatePar.downcase( options )
             end           r[ k ] = v       end -- for k, v   else
    else
         r = g   end   return rend -- fetch()
         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 figure( append, options )
local function figure( append, options )
     -- Extend options by rule from #invoke strings   -- Precondition:
     -- Extend options by rule from #invoke strings
    -- Precondition:
     --    append  -- string or nil; requested rule
     --    append  -- string or nil; requested rule
     --    options  --  table; details   --                  ++ .key
     --    options  --  table; details
     --                  ++ .pattern   -- Postcondition:
    --                  ++ .key
     --    Return sequence table   local r = options
     --                  ++ .pattern
    -- Postcondition:
     --    Return sequence table
    local r = options
     if type( append ) == "string" then
     if type( append ) == "string" then
         local story = mw.text.trim( append )
         local story = mw.text.trim( append )
Line 205: Line 419:
             sub            = sub:gsub( "%%%(%(", "{{" )
             sub            = sub:gsub( "%%%(%(", "{{" )
             sub            = sub:gsub( "%%%)%)", "}}" )
             sub            = sub:gsub( "%%%)%)", "}}" )
             options.pattern = sub           options.key    = nil       else
             options.pattern = sub
             options.key    = story           options.pattern = nil       end
            options.key    = nil
     end   return rend -- figure()local function fill( specified )
        else
     -- Split requirement string separated by '='   -- Precondition:
             options.key    = story
            options.pattern = nil
        end
     end
    return r
end -- figure()
 
 
 
local function fill( specified )
     -- Split requirement string separated by '='
    -- Precondition:
     --    specified  -- string or nil; requested parameter set
     --    specified  -- string or nil; requested parameter set
     -- Postcondition:   --    Return sequence table   -- Uses:
     -- Postcondition:
     --    mw.text.split()   local r   if specified then       local i, s
    --    Return sequence table
         r = mw.text.split( specified, "%s*=%s*" )       for i = #r, 1, -1 do
    -- Uses:
             s = r[ i ]           if #s == 0 then
     --    mw.text.split()
                 table.remove( r, i )           end       end -- for i, -1
    local r
     else       r = { }   end   return rend -- fill()
    if specified then
local function finalize( submit, options )   -- Finalize message
        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:
     -- Precondition:
     --    submit  -- string or false or nil; non-empty error message
     --    submit  -- string or false or nil; non-empty error message
     --    options  -- table or nil; optional details
     --    options  -- table or nil; optional details
     --                options.noError   --                options.cat
     --                options.noError
     --                options.template   -- Postcondition:
    --                options.cat
     --    Return string or false   -- Uses:   --    factory()
     --                options.template
     local r = false   if submit then       local opt, s
    -- Postcondition:
         if type( options ) == "table" then           opt = options       else
     --    Return string or false
             opt = { }       end       if opt.noError then
    -- 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
             if not opt.cat then
                 r = submit .. " " .. factory( "noErrorCat" )           end
                 r = submit .. " " .. factory( "noErrorCat" )
         else           r = submit       end       if r then
            end
             r = "<span class='error'>" .. r .. "</span>"       end
         else
         s = opt.cat       if type( s ) == "string" then
            r = submit
             if not r then               r = ""           end
        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 s:find( "@@@" ) then
                 if type( opt.template ) == "string" then
                 if type( opt.template ) == "string" then
                     s = s:gsub( "@@@", opt.template )               end
                     s = s:gsub( "@@@", opt.template )
             end           r = r .. "[[Category:" .. s .. "]]"       end   end
                end
     return rend -- finalize()local function finder( haystack, needle )
             end
     -- Find needle in haystack sequence   -- Precondition:
            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
     --    haystack  -- table; sequence of key names, downcased if low
     --    needle    -- any; key name   -- Postcondition:
     --    needle    -- any; key name
     --    Return true iff found   local i   for i = 1, #haystack do
    -- Postcondition:
         if haystack[ i ] == needle then           return true       end
     --    Return true iff found
     end -- for i   return falseend -- finder()
    local i
local function fix( valid, duty, got, options )   -- Perform parameter analysis
    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:
     -- 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
     --    got      -- table; sequence of current parameters
     --    got      -- table; sequence of current parameters
     --    options  -- table or nil; optional details   -- Postcondition:
     --    options  -- table or nil; optional details
     --    Return string as configured; empty if valid   -- Uses:
    -- Postcondition:
     --    finder()   --    fault()   --    failure()   --    fed()
     --    Return string as configured; empty if valid
     local k, v   local r = false   for k, v in pairs( got ) do
    -- Uses:
         if not finder( valid, k ) then           r = fault( r, k )       end
     --    finder()
     end -- for k, v   if r then
    --    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 )
         r = failure( "unknown",  "'" .. r .. "'",  options )
     else -- all names valid       local i, s       for i = 1, #duty do
     else -- all names valid
             s = duty[ i ]           if not fed( got, s ) then
        local i, s
                 r = fault( r, s )           end       end -- for i
        for i = 1, #duty do
         if r then           r = failure( "undefined", r, options )
             s = duty[ i ]
         else -- all mandatory present           for i = 1, #duty do
            if not fed( got, s ) then
                 s = duty[ i ]               if not got[ s ] then
                 r = fault( r, s )
                     r = fault( r, s )               end           end -- for i
            end
             if r then               r = failure( "empty", r, options )
        end -- for i
             end       end   end   return rend -- fix()
         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 )
local function flat( collection, options )
     -- Return all table elements with downcased string   -- Precondition:
     -- Return all table elements with downcased string
    -- Precondition:
     --    collection  -- table; k=v pairs
     --    collection  -- table; k=v pairs
     --    options    -- table or nil; optional messaging details
     --    options    -- table or nil; optional messaging details
     -- Postcondition:
     -- Postcondition:
     --    Return table, may be empty; or string with error message.   -- Uses:
     --    Return table, may be empty; or string with error message.
     --    mw.ustring.lower()   --    fault()   --    failure()
    -- Uses:
     local k, v   local r = { }   local e = false
     --    mw.ustring.lower()
     for k, v in pairs( collection ) do       if type ( k ) == "string" then
    --    fault()
             k = mw.ustring.lower( k )           if r[ k ] then
    --    failure()
                 e = fault( e, k )           end       end       r[ k ] = v
     local k, v
     end -- for k, v   if e then       r = failure( "multiSpell", e, options )
    local r = { }
     end   return rend -- flat()local function fold( options )
    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
     -- Merge two tables, create new sequence if both not empty
     -- Precondition:   --    options  -- table; details
     -- Precondition:
    --    options  -- table; details
     --                options.mandatory  sequence to keep unchanged
     --                options.mandatory  sequence to keep unchanged
     --                options.optional    sequence to be appended
     --                options.optional    sequence to be appended
     --                options.low        downcased expected
     --                options.low        downcased expected
     -- Postcondition:   --    Return merged table, or message string if error
     -- Postcondition:
     -- Uses:   --    finder()   --    fault()   --    failure()
    --    Return merged table, or message string if error
     --    flat()   local i, e, r, s   local base  = options.mandatory
     -- Uses:
     local extend = options.optional   if #base == 0 then
    --    finder()
         if #extend == 0 then           r = { }       else
    --    fault()
             r = extend       end   else       if #extend == 0 then
    --    failure()
             r = base       else           e = false
     --    flat()
             for i = 1, #extend do               s = extend[ i ]
    local i, e, r, s
                 if finder( base, s ) then                   e = fault( e, s )
    local base  = options.mandatory
                 end           end -- for i           if e then
     local extend = options.optional
                 r = failure( "dupOpt", e, options )           else
    if #base == 0 then
                 r = { }               for i = 1, #base do
         if #extend == 0 then
                     table.insert( r, base[ i ] )               end -- for i
            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
                 for i = 1, #extend do
                     table.insert( r, extend[ i ] )               end -- for i
                     table.insert( r, extend[ i ] )
             end       end   end
                end -- for i
     if options.low  and  type( r ) == "table" then       r = flat( r, options )
             end
     end   return rend -- fold()local function form( light, options )
        end
     -- Run parameter analysis on current environment   -- Precondition:
    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
     --    light    -- true: template transclusion;  false: #invoke
     --    options  -- table or nil; optional details
     --    options  -- table or nil; optional details
     --                options.mandatory   --                options.optional
     --                options.mandatory
     -- Postcondition:   --    Return string with error message as configured;
    --                options.optional
     --            false if valid   -- Uses:   --    fold()   --    fetch()
     -- Postcondition:
     --    fix()   --    finalize()   local duty, r
    --    Return string with error message as configured;
     --            false if valid
    -- Uses:
    --    fold()
    --    fetch()
     --    fix()
    --    finalize()
    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
             options.mandatory = { }       end       duty = options.mandatory
             options.mandatory = { }
        end
        duty = options.mandatory
         if type( options.optional ) ~= "table" then
         if type( options.optional ) ~= "table" then
             options.optional = { }       end       r = fold( options )   else
             options.optional = { }
         options = { }       duty    = { }       r      = { }   end
        end
     if type( r ) == "table" then       local got = fetch( light, options )
        r = fold( options )
    else
         options = { }
        duty    = { }
        r      = { }
    end
     if type( r ) == "table" then
        local got = fetch( light, options )
         if type( got ) == "table" then
         if type( got ) == "table" then
             r = fix( r, duty, got, options )       else           r = got
             r = fix( r, duty, got, options )
         end   end   return finalize( r, options )end -- form()
        else
local function format( analyze, options )   -- Check validity of a value
            r = got
     -- Precondition:   --    analyze  -- string to be analyzed
         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  -- table or nil; optional details
     --                options.say   --                options.min
     --                options.say
     --                options.max   -- Postcondition:
    --                options.min
     --                options.max
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid or no answer permitted   -- Uses:
     --            false if valid or no answer permitted
     --    feasible()   --    failure()
    -- Uses:
     local r    = feasible( analyze, options, false )   local show
     --    feasible()
    --    failure()
     local r    = feasible( analyze, options, false )
    local show
     if options.min  and  not r then
     if options.min  and  not r then
         if type( options.min ) == "number" then
         if type( options.min ) == "number" then
Line 334: Line 726:
                                 .. " > " ..
                                 .. " > " ..
                                 tostring( options.max ),
                                 tostring( options.max ),
                                 options )               end           end
                                 options )
                end
            end
             if #analyze < options.min  and  not r then
             if #analyze < options.min  and  not r then
                 show = " <" .. options.min               if options.say then
                 show = " <" .. options.min
                     show = show .. " '" .. options.say .. "'"               end
                if options.say then
                 r = failure( "tooShort", show, options )           end
                     show = show .. " '" .. options.say .. "'"
         else           r = failure( "invalidPar", "min", options )       end
                end
     end   if options.max  and  not r then
                 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 type( options.max ) == "number" then
             if #analyze > options.max then
             if #analyze > options.max then
                 show = " >" .. options.max               if options.say then
                 show = " >" .. options.max
                     show = show .. " '" .. options.say .. "'"               end
                if options.say then
                 r = failure( "tooLong", show, options )           end
                     show = show .. " '" .. options.say .. "'"
         else           r = failure( "invalidPar", "max", options )       end
                end
     end   return rend -- format()
                 r = failure( "tooLong", show, options )
            end
         else
            r = failure( "invalidPar", "max", options )
        end
     end
    return r
end -- format()
 
 
 
local function formatted( assignment, access, options )
local function formatted( assignment, access, options )
     -- Check validity of one particular parameter in a collection
     -- Check validity of one particular parameter in a collection
     -- Precondition:   --    assignment  -- collection
     -- Precondition:
    --    assignment  -- collection
     --    access      -- id of parameter in collection
     --    access      -- id of parameter in collection
     --    options    -- table or nil; optional details   -- Postcondition:
     --    options    -- table or nil; optional details
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid or no answer permitted   -- Uses:
     --            false if valid or no answer permitted
     --    format()   --    failure()   local r = false
    -- Uses:
     --    format()
    --    failure()
    local r = false
     if type( assignment ) == "table" then
     if type( assignment ) == "table" then
         local story = assignment.args[ access ]
         local story = assignment.args[ access ]
         if type( story ) == "string" then
         if type( story ) == "string" then
             if type( options ) ~= "table" then               options = { }
             if type( options ) ~= "table" then
             end           options.say = access
                options = { }
             r = format( story, options )       else
             end
             r = failure( "invalid", access, options )       end   end
            options.say = access
     return rend -- formatted()local function furnish( frame, action )
             r = format( story, options )
     -- Prepare #invoke evaluation of .assert() or .valid()   -- Precondition:
        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
     --    frame    -- object; #invoke environment
     --    action  -- "assert" or "valid"   -- Postcondition:
     --    action  -- "assert" or "valid"
     --    Return string with error message or ""   -- Uses:   --    form()
    -- Postcondition:
     --    mw.text.trim()   --    failure()   --    TemplatePar.assert()
     --    Return string with error message or ""
     --    TemplatePar.valid()   local options = { mandatory = { "1" },
    -- Uses:
    --    form()
     --    mw.text.trim()
    --    failure()
    --    TemplatePar.assert()
     --    TemplatePar.valid()
    local options = { mandatory = { "1" },
                       optional  = { "2",
                       optional  = { "2",
                                     "cat",
                                     "cat",
Line 378: Line 810:
                                     "template" },
                                     "template" },
                       template  = "&#35;invoke:TemplatePar|".. action .. "|"
                       template  = "&#35;invoke:TemplatePar|".. action .. "|"
                     }   local r      = form( false, options )   if not r then
                     }
         local s       options = { cat      = frame.args.cat,
    local r      = form( false, options )
    if not r then
         local s
        options = { cat      = frame.args.cat,
                     low      = frame.args.low,
                     low      = frame.args.low,
                     noError  = frame.args.noError,
                     noError  = frame.args.noError,
                     template = frame.args.template                 }
                     template = frame.args.template
                  }
         options = figure( frame.args[ 2 ], options )
         options = figure( frame.args[ 2 ], options )
         if type( frame.args.min ) == "string" then
         if type( frame.args.min ) == "string" then
             s = frame.args.min:match( "^%s*([0-9]+)%s*$" )           if s then
             s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
                 options.min = tonumber( s )           else
            if s then
                 options.min = tonumber( s )
            else
                 r = failure( "invalidPar",
                 r = failure( "invalidPar",
                             "min=" .. frame.args.min,
                             "min=" .. frame.args.min,
                             options )           end       end
                             options )
            end
        end
         if type( frame.args.max ) == "string" then
         if type( frame.args.max ) == "string" then
             s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
             s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
             if s then               options.max = tonumber( s )           else
             if s then
                options.max = tonumber( s )
            else
                 r = failure( "invalidPar",
                 r = failure( "invalidPar",
                             "max=" .. frame.args.max,
                             "max=" .. frame.args.max,
                             options )           end       end
                             options )
         if r then           r = finalize( r, options )       else
            end
             s = frame.args[ 1 ] or ""           r = tonumber( s )
        end
             if ( r ) then               s = r           end
         if r then
             r = TemplatePar[ action ]( s, options )       end   end
            r = finalize( r, options )
     return r or ""end -- furnish()
        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 )
TemplatePar.assert = function ( analyze, append, options )
     -- Perform parameter analysis on a single string   -- Precondition:
     -- Perform parameter analysis on a single string
    -- Precondition:
     --    analyze  -- string to be analyzed
     --    analyze  -- string to be analyzed
     --    append  -- string: append error message, prepending <br />
     --    append  -- string: append error message, prepending <br />
     --                false or nil: throw error with message
     --                false or nil: throw error with message
     --    options  -- table; optional details   -- Postcondition:
     --    options  -- table; optional details
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid   -- Uses:   --    format()
     --            false if valid
     local r = format( analyze, options )   if ( r ) then
    -- Uses:
    --    format()
     local r = format( analyze, options )
    if ( r ) then
         if ( type( append ) == "string" ) then
         if ( type( append ) == "string" ) then
             if ( append ~= "" ) then               r = append .. "<br />" .. r
             if ( append ~= "" ) then
             end       else           error( r, 0 )       end   end
                r = append .. "<br />" .. r
     return rend -- TemplatePar.assert()TemplatePar.check = function ( options )
             end
        else
            error( r, 0 )
        end
    end
     return r
end -- TemplatePar.assert()
 
 
 
TemplatePar.check = function ( options )
     -- Run parameter analysis on current template environment
     -- Run parameter analysis on current template environment
     -- Precondition:   --    options  -- table or nil; optional details
     -- Precondition:
     --                options.mandatory   --                options.optional
    --    options  -- table or nil; optional details
     -- Postcondition:   --    Return string with error message as configured;
     --                options.mandatory
     --            false if valid   -- Uses:   --    form()
    --                options.optional
     return form( true, options )end -- TemplatePar.check()
     -- Postcondition:
TemplatePar.count = function ()   -- Return number of template parameters
    --    Return string with error message as configured;
     -- Postcondition:   --    Return number, starting at 0   -- Uses:
     --            false if valid
     --    mw.getCurrentFrame()   --    frame:getParent()   local k, v
    -- Uses:
     local r = 0   local t = mw.getCurrentFrame():getParent()
    --    form()
     local o = t.args   for k, v in pairs( o ) do       r = r + 1
     return form( true, options )
     end -- for k, v   return rend -- TemplatePar.count()
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 ()
TemplatePar.countNotEmpty = function ()
     -- Return number of template parameters with more than whitespace
     -- Return number of template parameters with more than whitespace
     -- Postcondition:   --    Return number, starting at 0   -- Uses:
     -- Postcondition:
     --    mw.getCurrentFrame()   --    frame:getParent()   local k, v
    --    Return number, starting at 0
     local r = 0   local t = mw.getCurrentFrame():getParent()
    -- Uses:
     local o = t.args   for k, v in pairs( o ) do
     --    mw.getCurrentFrame()
         if not v:match( "^%s*$" ) then           r = r + 1       end
    --    frame:getParent()
     end -- for k, v   return rend -- TemplatePar.countNotEmpty()
    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 )
TemplatePar.downcase = function ( options )
     -- Return all template parameters with downcased name   -- Precondition:
     -- Return all template parameters with downcased name
    -- Precondition:
     --    options  -- table or nil; optional messaging details
     --    options  -- table or nil; optional messaging details
     -- Postcondition:
     -- Postcondition:
     --    Return table, may be empty; or string with error message.   -- Uses:
     --    Return table, may be empty; or string with error message.
     --    mw.getCurrentFrame()   --    frame:getParent()   --    flat()
    -- Uses:
     local t = mw.getCurrentFrame():getParent()   return flat( t.args, options )
     --    mw.getCurrentFrame()
end -- TemplatePar.downcase()TemplatePar.valid = function ( access, options )
    --    frame:getParent()
     -- Check validity of one particular template parameter   -- Precondition:
    --    flat()
     local t = mw.getCurrentFrame():getParent()
    return flat( t.args, options )
end -- TemplatePar.downcase()
 
 
 
TemplatePar.valid = function ( access, options )
     -- Check validity of one particular template parameter
    -- Precondition:
     --    access  -- id of parameter in template transclusion
     --    access  -- id of parameter in template transclusion
     --    options  -- table or nil; optional details   -- Postcondition:
     --    options  -- table or nil; optional details
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid or no answer permitted   -- Uses:
     --            false if valid or no answer permitted
     --    mw.text.trim()   --    TemplatePar.downcase()
    -- Uses:
     --    mw.getCurrentFrame()   --    frame:getParent()
     --    mw.text.trim()
     --    formatted()   --    failure()   --    finalize()   local r
    --    TemplatePar.downcase()
     if type( access ) == "string" then       r = mw.text.trim( access )
     --    mw.getCurrentFrame()
         if #r == 0 then           r = false       end   end   if r then
    --    frame:getParent()
         local params       if type( options ) ~= "table" then
     --    formatted()
             options = { }       end       if options.low then
    --    failure()
             params = TemplatePar.downcase( options )       else
    --    finalize()
             params = mw.getCurrentFrame():getParent()       end
    local r
         r = formatted( params, access, options )   else
     if type( access ) == "string" then
         r = failure( "noname", false, options )   end
        r = mw.text.trim( access )
     return finalize( r, options )end -- TemplatePar.valid()
         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 )
TemplatePar.verify = function ( options )
     -- Perform #invoke parameter analysis   -- Precondition:
     -- Perform #invoke parameter analysis
     --    options  -- table or nil; optional details   -- Postcondition:
    -- Precondition:
     --    options  -- table or nil; optional details
    -- Postcondition:
     --    Return string with error message as configured;
     --    Return string with error message as configured;
     --            false if valid   -- Uses:   --    form()
     --            false if valid
     return form( false, options )end -- TemplatePar.verify()
    -- Uses:
-- Provide external accesslocal p = {}function p.assert( frame )
    --    form()
     -- Perform parameter analysis on some single string   -- Precondition:
     return form( false, options )
     --    frame  -- object; #invoke environment   -- Postcondition:
end -- TemplatePar.verify()
     --    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
-- Provide external access
     -- Postcondition:   --    Return string with error message or ""
local p = {}
     -- Uses:   --    form()   --    fill()
 
 
 
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",
     local options = { optional  = { "all",
                                     "opt",
                                     "opt",
Line 481: Line 1,045:
                                     "template" },
                                     "template" },
                       template  = "&#35;invoke:TemplatePar|check|"
                       template  = "&#35;invoke:TemplatePar|check|"
                     }   local r = form( false, options )   if not r then
                     }
    local r = form( false, options )
    if not r then
         options = { mandatory = fill( frame.args.all ),
         options = { mandatory = fill( frame.args.all ),
                     optional  = fill( frame.args.opt ),
                     optional  = fill( frame.args.opt ),
Line 487: Line 1,053:
                     low      = frame.args.low,
                     low      = frame.args.low,
                     noError  = frame.args.noError,
                     noError  = frame.args.noError,
                     template  = frame.args.template                 }
                     template  = frame.args.template
         r      = form( true, options )   end   return r or ""end -- .check()
                  }
function p.count( frame )   -- Count number of template parameters
         r      = form( true, options )
     -- Postcondition:   --    Return string with digits including "0"
    end
     -- Uses:   --    TemplatePar.count()
    return r or ""
     return tostring( TemplatePar.count() )end -- .count()
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 )
function p.countNotEmpty( frame )
     -- Count number of template parameters which are not empty
     -- Count number of template parameters which are not empty
     -- Postcondition:   --    Return string with digits including "0"
     -- Postcondition:
     -- Uses:   --    TemplatePar.countNotEmpty()
    --    Return string with digits including "0"
     return tostring( TemplatePar.countNotEmpty() )end -- .countNotEmpty()
     -- Uses:
function p.match( frame )   -- Combined analysis of parameters and their values
    --    TemplatePar.countNotEmpty()
     -- Postcondition:   --    Return string with error message or ""
     return tostring( TemplatePar.countNotEmpty() )
     -- Uses:   --    mw.text.trim()   --    mw.ustring.lower()
end -- .countNotEmpty()
     --    failure()   --    form()   --    TemplatePar.downcase()
 
     --    figure()   --    feasible()   --    fault()   --    finalize()
 
     local r = false   local options = { cat      = frame.args.cat,
 
function p.match( frame )
    -- Combined analysis of parameters and their values
     -- Postcondition:
    --    Return string with error message or ""
     -- Uses:
    --    mw.text.trim()
    --    mw.ustring.lower()
     --    failure()
    --    form()
    --    TemplatePar.downcase()
     --    figure()
    --    feasible()
    --    fault()
    --    finalize()
     local r = false
    local options = { cat      = frame.args.cat,
                       low      = frame.args.low,
                       low      = frame.args.low,
                       noError  = frame.args.noError,
                       noError  = frame.args.noError,
                       template  = frame.args.template                   }
                       template  = frame.args.template
     local k, v, s   local params = { }   for k, v in pairs( frame.args ) do
                    }
     local k, v, s
    local params = { }
    for k, v in pairs( frame.args ) do
         if type( k ) == "number" then
         if type( k ) == "number" then
             s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )           if s then
             s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
                 s = mw.text.trim( s )               if s == "" then
            if s then
                     s = false               end           end
                 s = mw.text.trim( s )
             if s then               if options.low then
                if s == "" then
                     s = mw.ustring.lower( s )               end
                     s = false
                 if params[ s ] then                   s = params[ s ]
                end
                     s[ #s + 1 ] = v               else
            end
                     params[ s ] = { v }               end           else
             if s then
                if options.low then
                     s = mw.ustring.lower( s )
                end
                 if params[ s ] then
                    s = params[ s ]
                     s[ #s + 1 ] = v
                else
                     params[ s ] = { v }
                end
            else
                 r = failure( "invalidPar",  tostring( k ),  options )
                 r = failure( "invalidPar",  tostring( k ),  options )
                 break -- for k, v           end       end   end -- for k, v
                 break -- for k, v
     if not r then       s = { }       for k, v in pairs( params ) do
            end
             s[ #s + 1 ] = k       end -- for k, v       options.optional = s
        end
         r = form( true, options )   end   if not r then
    end -- for k, v
     if not r then
        s = { }
        for k, v in pairs( params ) do
             s[ #s + 1 ] = k
        end -- for k, v
        options.optional = s
         r = form( true, options )
    end
    if not r then
         local errMiss, errValues, lack, rule
         local errMiss, errValues, lack, rule
         local targs = frame:getParent().args       options.optional = nil
         local targs = frame:getParent().args
         if options.low then           targs = TemplatePar.downcase()
        options.optional = nil
         else           targs = frame:getParent().args       end
         if options.low then
         errMiss  = false       errValues = false
            targs = TemplatePar.downcase()
         for k, v in pairs( params ) do           options.say = k
         else
             errValue    = false           s = targs[ k ]           if s then
            targs = frame:getParent().args
                 if s == "" then                   lack = true
        end
                 else                   lack = false               end
         errMiss  = false
             else               s    = ""               lack = true
        errValues = false
             end           for r, rule in pairs( v ) do
         for k, v in pairs( params ) do
            options.say = k
             errValue    = false
            s = targs[ k ]
            if s then
                 if s == "" then
                    lack = true
                 else
                    lack = false
                end
             else
                s    = ""
                lack = true
             end
            for r, rule in pairs( v ) do
                 options = figure( rule, options )
                 options = figure( rule, options )
                 r      = feasible( s, options, true )               if r then
                 r      = feasible( s, options, true )
                     if lack then                       if errMiss then
                if r then
                     if lack then
                        if errMiss then
                             errMiss = errMiss .. ", '" .. k .. "'"
                             errMiss = errMiss .. ", '" .. k .. "'"
                         else
                         else
                             errMiss = "'" .. k .. "'"                       end
                             errMiss = "'" .. k .. "'"
                        end
                     elseif not errMiss then
                     elseif not errMiss then
                         errValues = fault( errValues, r )                   end
                         errValues = fault( errValues, r )
                     break -- for r, rule               end
                    end
             end -- for s, rule       end -- for k, v
                     break -- for r, rule
         r = ( errMiss or errValues )       if r then           if errMiss then
                end
                 r = failure( "undefined", errMiss, options )           else
             end -- for s, rule
                 r = failure( "invalid", errValues, options )           end
        end -- for k, v
             r = finalize( r, options )       end   end   return r or ""
         r = ( errMiss or errValues )
end -- .match()function p.valid( frame )
        if r then
     -- Check validity of one particular template parameter   -- Precondition:
            if errMiss then
     --    frame  -- object; #invoke environment   -- Postcondition:
                 r = failure( "undefined", errMiss, options )
     --    Return string with error message or ""   -- Uses:
            else
     --    furnish()   return furnish( frame, "valid" )end -- .valid()
                 r = failure( "invalid", errValues, options )
function p.TemplatePar()   -- Retrieve function access for modules
            end
     -- Postcondition:   --    Return table with functions
             r = finalize( r, options )
     return TemplateParend -- .TemplatePar()return p
        end
    end
    return r or ""
end -- .match()
 
 
 
function p.valid( frame )
     -- Check validity of one particular template parameter
    -- Precondition:
     --    frame  -- object; #invoke environment
    -- Postcondition:
     --    Return string with error message or ""
    -- Uses:
     --    furnish()
    return furnish( frame, "valid" )
end -- .valid()
 
 
 
function p.TemplatePar()
    -- Retrieve function access for modules
     -- Postcondition:
    --    Return table with functions
     return TemplatePar
end -- .TemplatePar()
 
 
 
return p

Revision as of 09:04, 20 December 2013

Module documentation[ view · edit · history · purge ]
This documentation is transcluded from Module:TemplatePar/doc.
--[=[ TemplatePar 2013-07-09
Template parameter utility
* assert
* check
* count
* countNotEmpty
* downcase
* match
* valid
* verify
* TemplatePar()
]=]



-- Module globals
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
l10nDef[ "en" ] = {
    badPattern  = "&#35;invoke:TemplatePar pattern syntax error",
    dupOpt      = "&#35;invoke:TemplatePar repeated optional parameter",
    dupRule     = "&#35;invoke:TemplatePar conflict key/pattern",
    empty       = "Error in template * undefined value for mandatory",
    invalid     = "Error in template * invalid parameter",
    invalidPar  = "&#35;invoke:TemplatePar invalid parameter",
    minmax      = "&#35;invoke:TemplatePar min > max",
    multiSpell  = "Error in template * multiple spelling of parameter",
    noErrorCat  = "&#35;invoke:TemplatePar noError and missing category",
    noname      = "&#35;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 = "&#35;invoke:TemplatePar unknown rule"
}
l10nDef[ "de" ]  = {
    badPattern  = "&#35;invoke:TemplatePar Syntaxfehler des pattern",
    dupOpt      = "&#35;invoke:TemplatePar Optionsparameter wiederholt",
    dupRule     = "&#35;invoke:TemplatePar Konflikt key/pattern",
    empty       = "Fehler bei Vorlage * Pflichtparameter ohne Wert",
    invalid     = "Fehler bei Vorlage * Parameter ungültig",
    invalidPar  = "&#35;invoke:TemplatePar Ungültiger Parameter",
    minmax      = "&#35;invoke:TemplatePar min > max",
    multiSpell  = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen",
    noErrorCat  = "&#35;invoke:TemplatePar noError und keine Kategorie",
    noname      = "&#35;invoke:TemplatePar Parameter 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 = "&#35;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 feasible( analyze, options, abbr )
    -- Check content of a value
    -- Precondition:
    --     analyze  -- string to be analyzed
    --     options  -- table or nil; optional details
    --                 options.pattern
    --                 options.key
    --                 options.say
    --     abbr     -- true: abbreviated error message
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     >  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
        if s ~= "*" then
            scan = Patterns[ s ]
        end
        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
                    if abbr then
                        r = show
                    else
                        r = failure( "invalid", show, options )
                    end
                end
            end
        elseif s ~= "*" then
            local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" )
            if op then
                n = tonumber( n )
                if n then
                    local i = tonumber( analyze )
                    if i then
                        if op == "<" then
                            i = ( i < n )
                        elseif op == "<=" then
                            i = ( i <= n )
                        elseif op == ">" then
                            i = ( i > n )
                        elseif op == ">=" then
                            i = ( i >= n )
                        elseif op == "!=" then
                            i = ( i ~= n )
                        else
                            n = false
                        end
                    end
                    if not i then
                        r = "invalid"
                    end
                elseif plus then
                    r = "undefined"
                end
            end
            if not n and not r then
                r = "unknownRule"
            end
            if r then
                if options.say then
                    show = "'" .. options.say .. "' " .. s
                else
                    show = s
                end
                if abbr then
                    r = show
                else
                    r = failure( r, show, options )
                end
            end
        end
    end
    if scan then
        local legal, got = pcall( 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
                    if abbr then
                        r = show
                    else
                        r = failure( "invalid", show, options )
                    end
                end
            end
        else
            r = failure( "badPattern",
                         scan .. " *** " .. got,
                         options )
        end
    end
    return r
end -- feasible()



local function fed( haystack, needle )
    -- Find needle in haystack map
    -- Precondition:
    --     haystack  -- table; map of key values
    --     needle    -- any; identifier
    -- Postcondition:
    --     Return true iff found
    local k, v
    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 figure( append, options )
    -- Extend options by rule from #invoke strings
    -- Precondition:
    --     append   -- string or nil; requested rule
    --     options  --  table; details
    --                  ++ .key
    --                  ++ .pattern
    -- Postcondition:
    --     Return sequence table
    local r = options
    if type( append ) == "string" then
        local story = mw.text.trim( append )
        local sub   = story:match( "^/(.*%S)/$" )
        if type( sub ) == "string" then
            sub             = sub:gsub( "%%!", "|" )
            sub             = sub:gsub( "%%%(%(", "{{" )
            sub             = sub:gsub( "%%%)%)", "}}" )
            options.pattern = sub
            options.key     = nil
        else
            options.key     = story
            options.pattern = nil
        end
    end
    return r
end -- figure()



local function fill( specified )
    -- Split requirement string separated by '='
    -- Precondition:
    --     specified  -- string or nil; requested parameter set
    -- Postcondition:
    --     Return sequence table
    -- Uses:
    --     mw.text.split()
    local r
    if specified then
        local i, s
        r = mw.text.split( specified, "%s*=%s*" )
        for i = #r, 1, -1 do
            s = r[ i ]
            if #s == 0 then
                table.remove( r, i )
            end
        end -- for i, -1
    else
        r = { }
    end
    return r
end -- fill()



local function finalize( submit, options )
    -- Finalize message
    -- Precondition:
    --     submit   -- string or false or nil; non-empty error message
    --     options  -- table or nil; optional details
    --                 options.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()
    --     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.say
    --                 options.min
    --                 options.max
    -- Postcondition:
    --     Return string with error message as configured;
    --            false if valid or no answer permitted
    -- Uses:
    --     feasible()
    --     failure()
    local r    = feasible( analyze, options, false )
    local show
    if options.min  and  not r then
        if type( options.min ) == "number" then
            if type( options.max ) == "number" then
                if options.max < options.min then
                    r = failure( "minmax",
                                 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:
    --     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()
    --     failure()
    --     TemplatePar.assert()
    --     TemplatePar.valid()
    local options = { mandatory = { "1" },
                      optional  = { "2",
                                    "cat",
                                    "low",
                                    "max",
                                    "min",
                                    "noError",
                                    "template" },
                      template  = "&#35;invoke:TemplatePar|".. action .. "|"
                    }
    local r       = form( false, options )
    if not r then
        local s
        options = { cat      = frame.args.cat,
                    low      = frame.args.low,
                    noError  = frame.args.noError,
                    template = frame.args.template
                  }
        options = figure( frame.args[ 2 ], options )
        if type( frame.args.min ) == "string" then
            s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
            if s then
                options.min = tonumber( s )
            else
                r = failure( "invalidPar",
                             "min=" .. frame.args.min,
                             options )
            end
        end
        if type( frame.args.max ) == "string" then
            s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
            if s then
                options.max = tonumber( s )
            else
                r = failure( "invalidPar",
                             "max=" .. frame.args.max,
                             options )
            end
        end
        if r then
            r = finalize( r, options )
        else
            s = frame.args[ 1 ] or ""
            r = tonumber( s )
            if ( r ) then
                s = r
            end
            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()
    --     flat()
    local t = mw.getCurrentFrame():getParent()
    return flat( t.args, options )
end -- TemplatePar.downcase()



TemplatePar.valid = function ( access, options )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     access   -- id of parameter in template transclusion
    --     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  = "&#35;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.match( frame )
    -- Combined analysis of parameters and their values
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     mw.text.trim()
    --     mw.ustring.lower()
    --     failure()
    --     form()
    --     TemplatePar.downcase()
    --     figure()
    --     feasible()
    --     fault()
    --     finalize()
    local r = false
    local options = { cat       = frame.args.cat,
                      low       = frame.args.low,
                      noError   = frame.args.noError,
                      template  = frame.args.template
                    }
    local k, v, s
    local params = { }
    for k, v in pairs( frame.args ) do
        if type( k ) == "number" then
            s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
            if s then
                s = mw.text.trim( s )
                if s == "" then
                    s = false
                end
            end
            if s then
                if options.low then
                    s = mw.ustring.lower( s )
                end
                if params[ s ] then
                    s = params[ s ]
                    s[ #s + 1 ] = v
                else
                    params[ s ] = { v }
                end
            else
                r = failure( "invalidPar",  tostring( k ),  options )
                break -- for k, v
            end
        end
    end -- for k, v
    if not r then
        s = { }
        for k, v in pairs( params ) do
            s[ #s + 1 ] = k
        end -- for k, v
        options.optional = s
        r = form( true, options )
    end
    if not r then
        local errMiss, errValues, lack, rule
        local targs = frame:getParent().args
        options.optional = nil
        if options.low then
            targs = TemplatePar.downcase()
        else
            targs = frame:getParent().args
        end
        errMiss   = false
        errValues = false
        for k, v in pairs( params ) do
            options.say = k
            errValue    = false
            s = targs[ k ]
            if s then
                if s == "" then
                    lack = true
                else
                    lack = false
                end
            else
                s    = ""
                lack = true
            end
            for r, rule in pairs( v ) do
                options = figure( rule, options )
                r       = feasible( s, options, true )
                if r then
                    if lack then
                        if errMiss then
                            errMiss = errMiss .. ", '" .. k .. "'"
                        else
                            errMiss = "'" .. k .. "'"
                        end
                    elseif not errMiss then
                        errValues = fault( errValues, r )
                    end
                    break -- for r, rule
                end
            end -- for s, rule
        end -- for k, v
        r = ( errMiss or errValues )
        if r then
            if errMiss then
                r = failure( "undefined", errMiss, options )
            else
                r = failure( "invalid", errValues, options )
            end
            r = finalize( r, options )
        end
    end
    return r or ""
end -- .match()



function p.valid( frame )
    -- Check validity of one particular template parameter
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     furnish()
    return furnish( frame, "valid" )
end -- .valid()



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



return p
Cookies help us deliver our services. By using our services, you agree to our use of cookies.