Jump to content

Module:TemplatePar/sandbox: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
Create sandbox version of Module:TemplatePar
 
mild simplifications, replace quintuple spaces with tabs
 
Line 19: Line 19:
local L10nDef = {}
local L10nDef = {}
L10nDef.en = {
L10nDef.en = {
badPattern = "#invoke:TemplatePar pattern syntax error",
badPattern = "#invoke:TemplatePar pattern syntax error",
dupOpt = "#invoke:TemplatePar repeated optional parameter",
dupOpt = "#invoke:TemplatePar repeated optional parameter",
dupRule = "#invoke:TemplatePar conflict key/pattern",
dupRule = "#invoke:TemplatePar conflict key/pattern",
empty = "Error in template * undefined value for mandatory",
empty = "Error in template * undefined value for mandatory",
invalid = "Error in template * invalid parameter",
invalid = "Error in template * invalid parameter",
invalidPar = "#invoke:TemplatePar invalid parameter",
invalidPar = "#invoke:TemplatePar invalid parameter",
minmax = "#invoke:TemplatePar min > max",
minmax = "#invoke:TemplatePar min > max",
missing = "#invoke:TemplatePar missing library",
missing = "#invoke:TemplatePar missing library",
multiSpell = "Error in template * multiple spelling of parameter",
multiSpell = "Error in template * multiple spelling of parameter",
noMSGnoCAT = "#invoke:TemplatePar neither message nor category",
noMSGnoCAT = "#invoke:TemplatePar neither message nor category",
noname = "#invoke:TemplatePar missing parameter name",
noname = "#invoke:TemplatePar missing parameter name",
notFound = "Error in template * missing page",
notFound = "Error in template * missing page",
tooLong = "Error in template * parameter too long",
tooLong = "Error in template * parameter too long",
tooShort = "Error in template * parameter too short",
tooShort = "Error in template * parameter too short",
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"
unknownRule = "#invoke:TemplatePar unknown rule"
}
}
L10nDef.de = {
L10nDef.de = {
badPattern = "#invoke:TemplatePar Syntaxfehler des pattern",
badPattern = "#invoke:TemplatePar Syntaxfehler des pattern",
dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt",
dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt",
dupRule = "#invoke:TemplatePar Konflikt key/pattern",
dupRule = "#invoke:TemplatePar Konflikt key/pattern",
empty = "Fehler bei Vorlage * Pflichtparameter ohne Wert",
empty = "Fehler bei Vorlage * Pflichtparameter ohne Wert",
invalid = "Fehler bei Vorlage * Parameter ungültig",
invalid = "Fehler bei Vorlage * Parameter ungültig",
invalidPar = "#invoke:TemplatePar Ungültiger Parameter",
invalidPar = "#invoke:TemplatePar Ungültiger Parameter",
minmax = "#invoke:TemplatePar min > max",
minmax = "#invoke:TemplatePar min > max",
multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen",
multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen",
noMSGnoCAT = "#invoke:TemplatePar weder Meldung noch Kategorie",
noMSGnoCAT = "#invoke:TemplatePar weder Meldung noch Kategorie",
noname = "#invoke:TemplatePar Parameter nicht angegeben",
noname = "#invoke:TemplatePar Parameter nicht angegeben",
notFound = "Fehler bei Vorlage * Seite fehlt",
notFound = "Fehler bei Vorlage * Seite fehlt",
tooLong = "Fehler bei Vorlage * Parameter zu lang",
tooLong = "Fehler bei Vorlage * Parameter zu lang",
tooShort = "Fehler bei Vorlage * Parameter zu kurz",
tooShort = "Fehler bei Vorlage * Parameter zu kurz",
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"
unknownRule = "#invoke:TemplatePar Unbekannte Regel"
}
}
local Patterns = {
local Patterns = {
[ "ASCII" ] = "^[ -~]*$",
[ "ASCII" ] = "^[ -~]*$",
[ "ASCII+" ] = "^[ -~]+$",
[ "ASCII+" ] = "^[ -~]+$",
[ "ASCII+1" ] = "^[!-~]+$",
[ "ASCII+1" ] = "^[!-~]+$",
[ "n" ] = "^[%-]?[0-9]*$",
[ "n" ] = "^[%-]?[0-9]*$",
[ "n>0" ] = "^[0-9]*[1-9][0-9]*$",
[ "n>0" ] = "^[0-9]*[1-9][0-9]*$",
[ "N+" ] = "^[%-]?[1-9][0-9]*$",
[ "N+" ] = "^[%-]?[1-9][0-9]*$",
[ "N>0" ] = "^[1-9][0-9]*$",
[ "N>0" ] = "^[1-9][0-9]*$",
[ "x" ] = "^[0-9A-Fa-f]*$",
[ "x" ] = "^[0-9A-Fa-f]*$",
[ "x+" ] = "^[0-9A-Fa-f]+$",
[ "x+" ] = "^[0-9A-Fa-f]+$",
[ "X" ] = "^[0-9A-F]*$",
[ "X" ] = "^[0-9A-F]*$",
[ "X+" ] = "^[0-9A-F]+$",
[ "X+" ] = "^[0-9A-F]+$",
[ "0,0" ] = "^[%-]?[0-9]*,?[0-9]*$",
[ "0,0" ] = "^[%-]?[0-9]*,?[0-9]*$",
[ "0,0+" ] = "^[%-]?[0-9]+,[0-9]+$",
[ "0,0+" ] = "^[%-]?[0-9]+,[0-9]+$",
[ "0,0+?" ] = "^[%-]?[0-9]+,?[0-9]*$",
[ "0,0+?" ] = "^[%-]?[0-9]+,?[0-9]*$",
[ "0.0" ] = "^[%-]?[0-9]*[%.]?[0-9]*$",
[ "0.0" ] = "^[%-]?[0-9]*[%.]?[0-9]*$",
[ "0.0+" ] = "^[%-]?[0-9]+%.[0-9]+$",
[ "0.0+" ] = "^[%-]?[0-9]+%.[0-9]+$",
[ "0.0+?" ] = "^[%-]?[0-9]+[%.]?[0-9]*$",
[ "0.0+?" ] = "^[%-]?[0-9]+[%.]?[0-9]*$",
[ ".0+" ] = "^[%-]?[0-9]*[%.]?[0-9]+$",
[ ".0+" ] = "^[%-]?[0-9]*[%.]?[0-9]+$",
[ "ID" ] = "^[A-Za-z]?[A-Za-z_0-9]*$",
[ "ID" ] = "^[A-Za-z]?[A-Za-z_0-9]*$",
[ "ID+" ] = "^[A-Za-z][A-Za-z_0-9]*$",
[ "ID+" ] = "^[A-Za-z][A-Za-z_0-9]*$",
[ "ABC" ] = "^[A-Z]*$",
[ "ABC" ] = "^[A-Z]*$",
[ "ABC+" ] = "^[A-Z]+$",
[ "ABC+" ] = "^[A-Z]+$",
[ "Abc" ] = "^[A-Z]*[a-z]*$",
[ "Abc" ] = "^[A-Z]*[a-z]*$",
[ "Abc+" ] = "^[A-Z][a-z]+$",
[ "Abc+" ] = "^[A-Z][a-z]+$",
[ "abc" ] = "^[a-z]*$",
[ "abc" ] = "^[a-z]*$",
[ "abc+" ] = "^[a-z]+$",
[ "abc+" ] = "^[a-z]+$",
[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$",
[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$",
[ "w" ] = "^%S*$",
[ "w" ] = "^%S*$",
[ "w+" ] = "^%S+$",
[ "w+" ] = "^%S+$",
[ "base64" ] = "^[A-Za-z0-9%+/]*$",
[ "base64" ] = "^[A-Za-z0-9%+/]*$",
[ "base64+" ] = "^[A-Za-z0-9%+/]+$",
[ "base64+" ] = "^[A-Za-z0-9%+/]+$",
[ "aa" ] = "[%a%a].*[%a%a]",
[ "aa" ] = "[%a%a].*[%a%a]",
[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",
[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",
1, 31, 127 ),
1, 31, 127 ),
[ "+" ] = "%S"
[ "+" ] = "%S"
}
}
local patternCJK = false
local patternCJK = false
Line 97: Line 97:


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


Line 122: Line 120:


local function facility( accept, attempt )
local function facility( accept, attempt )
-- Check string as possible file name or other source page
-- Check string as possible file name or other source page
-- Precondition:
-- Precondition:
-- accept -- string; requirement
-- accept -- string; requirement
-- file
-- file
-- file+
-- file+
-- file:
-- file:
-- file:+
-- file:+
-- image
-- image
-- image+
-- image+
-- image:
-- image:
-- image:+
-- image:+
-- attempt -- string; to be tested
-- attempt -- string; to be tested
-- Postcondition:
-- Postcondition:
-- Return error keyword, or false
-- Return error keyword, or false
-- Uses:
-- Uses:
-- Module:FileMedia
-- Module:FileMedia
-- FileMedia.isType()
-- FileMedia.isType()
local r
local r
if attempt and attempt ~= "" then
if attempt and attempt ~= "" then
local lucky, FileMedia = pcall( require, "Module:FileMedia" )
local lucky, FileMedia = pcall( require, "Module:FileMedia" )
if type( FileMedia ) == "table" then
if type( FileMedia ) == "table" then
FileMedia = FileMedia.FileMedia()
FileMedia = FileMedia.FileMedia()
local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
if live then
if live then
if FileMedia.isType( attempt, s ) then
if FileMedia.isType( attempt, s ) then
if FileMedia.isFile( attempt ) then
if FileMedia.isFile( attempt ) then
r = false
r = false
else
else
r = "notFound"
r = "notFound"
end
end
else
else
r = "invalid"
r = "invalid"
end
end
elseif FileMedia.isType( attempt, s ) then
elseif FileMedia.isType( attempt, s ) then
r = false
r = false
else
else
r = "invalid"
r = "invalid"
end
end
else
else
r = "missing"
r = "missing"
end
end
elseif accept:match( "%+$" ) then
elseif accept:match( "%+$" ) then
r = "empty"
r = "empty"
else
else
r = false
r = false
end
end
return r
return r
end -- facility()
end -- facility()


Line 174: Line 172:


local function factory( say )
local function factory( say )
-- Retrieve localized message string in content language
-- Retrieve localized message string in content language
-- Precondition:
-- Precondition:
-- say -- string; message ID
-- say -- string; message ID
-- Postcondition:
-- Postcondition:
-- Return some message string
-- Return some message string
-- Uses:
-- Uses:
-- > MessagePrefix
-- > MessagePrefix
-- > L10nDef
-- > L10nDef
-- mw.language.getContentLanguage()
-- 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 m = mw.message.new( MessagePrefix .. say )
local r = false
local r = false
if m:isBlank() then
if m:isBlank() then
local l10n = L10nDef[ c ]
local l10n = L10nDef[ c ] or L10nDef[ "en" ]
r = l10n[ say ]
if not l10n then
else
l10n = L10nDef[ "en" ]
m:inLanguage( c )
end
r = m:plain()
r = l10n[ say ]
end
else
r = r or string.format( "(((%s)))", say )
m:inLanguage( c )
return r
r = m:plain()
end
if not r then
r = string.format( "(((%s)))", say )
end
return r
end -- factory()
end -- factory()


Line 206: Line 199:


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:
-- Precondition:
-- story -- string; parameter value
-- story -- string; parameter value
-- scan -- string; pattern
-- scan -- string; pattern
-- Postcondition:
-- Postcondition:
-- Return nil, if not matching, else non-nil
-- Return nil, if not matching, else non-nil
-- Uses:
-- Uses:
-- mw.ustring.match()
-- mw.ustring.match()
return mw.ustring.match( story, scan )
return mw.ustring.match( story, scan )
end -- failsafe()
end -- failsafe()


Line 220: Line 213:


local function failure( spec, suspect, options )
local function failure( spec, suspect, options )
-- Submit localized error message
-- Submit localized error message
-- Precondition:
-- Precondition:
-- spec -- string; message ID
-- spec -- string; message ID
-- suspect -- string or nil; additional information
-- suspect -- string or nil; additional information
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- options.template
-- options.template
-- Postcondition:
-- Postcondition:
-- Return string
-- Return string
-- Uses:
-- Uses:
-- factory()
-- factory()
local r = factory( spec )
local r = factory( spec )
if type( options ) == "table" then
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 = string.format( "%s (%s)", r, options.template )
r = string.format( "%s (%s)", r, options.template )
end
end
end
end
end
end
if suspect then
if suspect then
r = string.format( "%s: %s", r, suspect )
r = string.format( "%s: %s", r, suspect )
end
end
return r
return r
end -- failure()
end -- failure()


Line 247: Line 240:


local function fault( store, key )
local function fault( store, key )
-- Add key to collection string and insert separator
-- Add key to collection string and insert separator
-- Precondition:
-- Precondition:
-- store -- string or nil or false; collection string
-- store -- string or nil or false; collection string
-- key -- string or number; to be appended
-- key -- string or number; to be appended
-- Postcondition:
-- Postcondition:
-- Return string; extended
-- Return string; extended
local r
local r
local s
local s
if type( key ) == "number" then
if type( key ) == "number" then
s = tostring( key )
s = tostring( key )
else
else
s = key
s = key
end
end
if store then
if store then
r = string.format( "%s; %s", store, s )
r = string.format( "%s; %s", store, s )
else
else
r = s
r = s
end
end
return r
return r
end -- fault()
end -- fault()


Line 271: Line 264:


local function feasible( analyze, options, abbr )
local function feasible( analyze, options, abbr )
-- Check content of a value
-- Check content of a value
-- Precondition:
-- Precondition:
-- analyze -- string to be analyzed
-- analyze -- string to be analyzed
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- options.pattern
-- options.pattern
-- options.key
-- options.key
-- options.say
-- options.say
-- abbr -- true: abbreviated error message
-- abbr -- true: abbreviated error message
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- false if valid or no answer permitted
-- Uses:
-- Uses:
-- > Patterns
-- > Patterns
-- failure()
-- failure()
-- mw.text.trim()
-- mw.text.trim()
-- facility()
-- facility()
-- failsafe()
-- failsafe()
-- containsCJK()
-- containsCJK()
local r = false
local r = false
local s = false
local s = false
local show = nil
local show = nil
local scan = false
local scan = false
if type( options.pattern ) == "string" then
if type( options.pattern ) == "string" then
if options.key then
if options.key then
r = failure( "dupRule", false, options )
r = failure( "dupRule", false, options )
else
else
scan = options.pattern
scan = options.pattern
end
end
else
else
if type( options.key ) == "string" then
if type( options.key ) == "string" then
s = mw.text.trim( options.key )
s = mw.text.trim( options.key )
else
else
s = "+"
s = "+"
end
end
if s ~= "*" then
if s ~= "*" then
scan = Patterns[ s ]
scan = Patterns[ s ]
end
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]" ) and
if not analyze:match( "[0-9]" ) and
not analyze:match( "^%s*$" ) then
not analyze:match( "^%s*$" ) then
scan = false
scan = false
if options.say then
if options.say then
show = string.format( "'%s'", options.say )
show = string.format( "'%s'", options.say )
end
end
if abbr then
if abbr then
r = show
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
if op then
n = tonumber( n )
n = tonumber( n )
if n then
if n then
local i = tonumber( analyze )
local i = tonumber( analyze )
if i then
if i then
if op == "<" then
if op == "<" then
i = ( i < n )
i = ( i < n )
elseif op == "<=" then
elseif op == "<=" then
i = ( i <= n )
i = ( i <= n )
elseif op == ">" then
elseif op == ">" then
i = ( i > n )
i = ( i > n )
elseif op == ">=" then
elseif op == ">=" then
i = ( i >= n )
i = ( i >= n )
elseif op == "==" then
elseif op == "==" then
i = ( i == n )
i = ( i == n )
elseif op == "!=" then
elseif op == "!=" then
i = ( i ~= n )
i = ( i ~= n )
else
else
n = false
n = false
end
end
end
end
if not i then
if not i then
r = "invalid"
r = "invalid"
end
end
elseif plus then
elseif plus then
r = "undefined"
r = "undefined"
end
end
elseif s:match( "^image%+?:?$" ) or
elseif s:match( "^image%+?:?$" ) or
s:match( "^file%+?:?$" ) then
s:match( "^file%+?:?$" ) then
r = facility( s, analyze )
r = facility( s, analyze )
n = true
n = true
elseif s:match( "langW?%+?" ) then
elseif s:match( "langW?%+?" ) then
n = "lang"
n = "lang"
-- lang lang+
-- lang lang+
-- langW langW+
-- langW langW+
end
end
if not n and not r then
if not n and not r then
r = "unknownRule"
r = "unknownRule"
end
end
if r then
if r then
if options.say then
if options.say then
show = string.format( "'%s' %s", options.say, s )
show = string.format( "'%s' %s", options.say, s )
else
else
show = s
show = s
end
end
if abbr then
if abbr then
r = show
r = show
else
else
r = failure( r, show, options )
r = failure( r, show, options )
end
end
end
end
end
end
end
end
if scan then
if scan then
local legal, got = pcall( failsafe, analyze, scan )
local legal, got = pcall( failsafe, analyze, scan )
if legal then
if legal then
if not got then
if not got then
if s == "aa" then
if s == "aa" then
got = containsCJK( analyze )
got = containsCJK( analyze )
end
end
if not got then
if not got then
if options.say then
if options.say then
show = string.format( "'%s'", options.say )
show = string.format( "'%s'", options.say )
end
end
if abbr then
if abbr then
r = show
r = show
else
else
r = failure( "invalid", show, options )
r = failure( "invalid", show, options )
end
end
end
end
end
end
else
else
r = failure( "badPattern",
r = failure( "badPattern",
string.format( "%s *** %s", scan, got ),
string.format( "%s *** %s", scan, got ),
options )
options )
end
end
end
end
return r
return r
end -- feasible()
end -- feasible()


Line 408: Line 401:


local function fed( haystack, needle )
local function fed( haystack, needle )
-- Find needle in haystack map
-- Find needle in haystack map
-- Precondition:
-- Precondition:
-- haystack -- table; map of key values
-- haystack -- table; map of key values
-- needle -- any; identifier
-- needle -- any; identifier
-- Postcondition:
-- Postcondition:
-- Return true iff found
-- Return true iff found
local k, v
local k, v
for k, v in pairs( haystack ) do
for k, v in pairs( haystack ) do
if k == needle then
if k == needle then
return true
return true
end
end
end -- for k, v
end -- for k, v
return false
return false
end -- fed()
end -- fed()


Line 426: Line 419:


local function fetch( light, options )
local function fetch( light, options )
-- Return regular table with all parameters
-- Return regular table with all parameters
-- Precondition:
-- Precondition:
-- light -- true: template transclusion; false: #invoke
-- light -- true: template transclusion; false: #invoke
-- options -- table; optional details
-- options -- table; optional details
-- options.low
-- options.low
-- Postcondition:
-- Postcondition:
-- Return table; whitespace-only values as false
-- Return table; whitespace-only values as false
-- Uses:
-- Uses:
-- TemplatePar.downcase()
-- TemplatePar.downcase()
-- mw.getCurrentFrame()
-- mw.getCurrentFrame()
-- frame:getParent()
-- frame:getParent()
local g, k, v
local g, k, v
local r = { }
local r = { }
if options.low then
if options.low then
g = TemplatePar.downcase( options )
g = TemplatePar.downcase( options )
else
else
g = mw.getCurrentFrame()
g = mw.getCurrentFrame()
if light then
if light then
g = g:getParent()
g = g:getParent()
end
end
g = g.args
g = g.args
end
end
if type( g ) == "table" then
if type( g ) == "table" then
r = { }
r = { }
for k, v in pairs( g ) do
for k, v in pairs( g ) do
if type( v ) == "string" then
if type( v ) == "string" then
if v:match( "^%s*$" ) then
if v:match( "^%s*$" ) then
v = false
v = false
end
end
else
else
v = false
v = false
end
end
if type( k ) == "number" then
if type( k ) == "number" then
k = tostring( k )
k = tostring( k )
end
end
r[ k ] = v
r[ k ] = v
end -- for k, v
end -- for k, v
else
else
r = g
r = g
end
end
return r
return r
end -- fetch()
end -- fetch()


Line 472: Line 465:


local function figure( append, options )
local function figure( append, options )
-- Extend options by rule from #invoke strings
-- Extend options by rule from #invoke strings
-- Precondition:
-- Precondition:
-- append -- string or nil; requested rule
-- append -- string or nil; requested rule
-- options -- table; details
-- options -- table; details
-- ++ .key
-- ++ .key
-- ++ .pattern
-- ++ .pattern
-- Postcondition:
-- Postcondition:
-- Return sequence table
-- Return sequence table
local r = options
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 )
local sub = story:match( "^/(.*%S)/$" )
local sub = story:match( "^/(.*%S)/$" )
if type( sub ) == "string" then
if type( sub ) == "string" then
sub = sub:gsub( "%%!", "|" )
sub = sub:gsub( "%%!", "|" )
sub = sub:gsub( "%%%(%(", "{{" )
sub = sub:gsub( "%%%(%(", "{{" )
sub = sub:gsub( "%%%)%)", "}}" )
sub = sub:gsub( "%%%)%)", "}}" )
options.pattern = sub
options.pattern = sub
options.key = nil
options.key = nil
else
else
options.key = story
options.key = story
options.pattern = nil
options.pattern = nil
end
end
end
end
return r
return r
end -- figure()
end -- figure()


Line 501: Line 494:


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


Line 527: Line 520:


local function finalize( submit, options, frame )
local function finalize( submit, options, frame )
-- Finalize message
-- 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.format
-- options.format
-- options.preview
-- options.preview
-- options.cat
-- options.cat
-- options.template
-- options.template
-- frame -- object, or false
-- frame -- object, or false
-- Postcondition:
-- Postcondition:
-- Return string or false
-- Return string or false
-- Uses:
-- Uses:
-- factory()
-- factory()
local r = false
local r = false
if submit then
if submit then
local opt, s
local opt, s
local lazy = false
local lazy = false
local show = false
local show = false
if type( options ) == "table" then
if type( options ) == "table" then
opt = options
opt = options
show = opt.format
show = opt.format
lazy = ( show == "" or show == "0" or show == "-" )
lazy = ( show == "" or show == "0" or show == "-" )
s = opt.preview
s = opt.preview
if type( s ) == "string" and
if type( s ) == "string" and
s ~= "" and s ~= "0" and s ~= "-" then
s ~= "" and s ~= "0" and s ~= "-" then
if lazy then
if lazy then
show = ""
show = ""
lazy = false
lazy = false
end
end
frame = frame or mw.getCurrentFrame()
if not frame then
if frame:preprocess( "{{REVISIONID}}" ) == "" then
frame = mw.getCurrentFrame()
if s == "1" then
end
show = "*"
if frame:preprocess( "{{REVISIONID}}" ) == "" then
else
if s == "1" then
show = "*"
show = s
end
else
end
show = s
end
end
else
end
opt = { }
end
end
else
if lazy then
opt = { }
if not opt.cat then
end
r = string.format( "%s %s",
if lazy then
submit, factory( "noMSGnoCAT" ) )
if not opt.cat then
end
r = string.format( "%s %s",
else
submit, factory( "noMSGnoCAT" ) )
r = submit
end
end
else
if r and not lazy then
r = submit
local i
end
if r and not lazy then
if not show or show == "*" then
show = "<span class=\"error\">@@@</span>"
local i
end
if not show or show == "*" then
i = show:find( "@@@", 1, true )
show = "<span class=\"error\">@@@</span>"
if i then
end
-- No gsub() since r might contain "%3" (e.g. URL)
i = show:find( "@@@", 1, true )
r = string.format( "%s%s%s",
if i then
show:sub( 1, i - 1 ),
-- No gsub() since r might contain "%3" (e.g. URL)
r,
r = string.format( "%s%s%s",
show:sub( 1, i - 1 ),
show:sub( i + 3 ) )
else
r,
r = show
show:sub( i + 3 ) )
end
else
end
r = show
s = opt.cat
end
if type( s ) == "string" then
end
if opt.errNS then
s = opt.cat
local ns = mw.title.getCurrentTitle().namespace
if type( s ) == "string" then
if opt.errNS then
local st = type( opt.errNS )
if st == "string" then
local ns = mw.title.getCurrentTitle().namespace
local space = string.format( ".*%%s%d%%s.*", ns )
local st = type( opt.errNS )
local spaces = string.format( " %s ", opt.errNS )
if st == "string" then
if spaces:match( space ) then
local space = string.format( ".*%%s%d%%s.*", ns )
opt.errNS = false
local spaces = string.format( " %s ", opt.errNS )
end
if spaces:match( space ) then
elseif st == "table" then
opt.errNS = false
for i = 1, #opt.errNS do
end
if opt.errNS[ i ] == ns then
elseif st == "table" then
for i = 1, #opt.errNS do
opt.errNS = false
break -- for i
if opt.errNS[ i ] == ns then
end
opt.errNS = false
end -- for i
break -- for i
end
end
end
end -- for i
if opt.errNS then
end
r = ""
end
else
if opt.errNS then
r = ""
r = r or ""
if s:find( "@@@" ) then
else
if type( opt.template ) == "string" then
if not r then
s = s:gsub( "@@@", opt.template )
r = ""
end
end
end
if s:find( "@@@" ) then
local i
if type( opt.template ) == "string" then
local cats = mw.text.split( s, "%s*#%s*" )
s = s:gsub( "@@@", opt.template )
for i = 1, #cats do
end
s = mw.text.trim( cats[ i ] )
end
if #s > 0 then
local i
r = string.format( "%s[[Category:%s]]", r, s )
local cats = mw.text.split( s, "%s*#%s*" )
end
for i = 1, #cats do
end -- for i
s = mw.text.trim( cats[ i ] )
end
if #s > 0 then
end
r = string.format( "%s[[Category:%s]]", r, s )
end
end
return r
end -- for i
end
end
end
return r
end -- finalize()
end -- finalize()


Line 642: Line 631:


local function finder( haystack, needle )
local function finder( haystack, needle )
-- Find needle in haystack sequence
-- Find needle in haystack sequence
-- Precondition:
-- Precondition:
-- haystack -- table; sequence of key names, downcased if low
-- haystack -- table; sequence of key names, downcased if low
-- needle -- any; key name
-- needle -- any; key name
-- Postcondition:
-- Postcondition:
-- Return true iff found
-- Return true iff found
local i
local i
for i = 1, #haystack do
for i = 1, #haystack do
if haystack[ i ] == needle then
if haystack[ i ] == needle then
return true
return true
end
end
end -- for i
end -- for i
return false
return false
end -- finder()
end -- finder()


Line 660: Line 649:


local function fix( valid, duty, got, options )
local function fix( valid, duty, got, options )
-- Perform parameter analysis
-- 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
-- options -- table or nil; optional details
-- Postcondition:
-- Postcondition:
-- Return string as configured; empty if valid
-- Return string as configured; empty if valid
-- Uses:
-- Uses:
-- finder()
-- finder()
-- fault()
-- fault()
-- failure()
-- failure()
-- fed()
-- fed()
local k, v
local k, v
local r = false
local r = false
for k, v in pairs( got ) do
for k, v in pairs( got ) do
if not finder( valid, k ) then
if not finder( valid, k ) then
r = fault( r, k )
r = fault( r, k )
end
end
end -- for k, v
end -- for k, v
if r then
if r then
r = failure( "unknown",
r = failure( "unknown",
string.format( "'%s'", r ),
string.format( "'%s'", r ),
options )
options )
else -- all names valid
else -- all names valid
local i, s
local i, s
for i = 1, #duty do
for i = 1, #duty do
s = duty[ i ]
s = duty[ i ]
if not fed( got, s ) then
if not fed( got, s ) then
r = fault( r, s )
r = fault( r, s )
end
end
end -- for i
end -- for i
if r then
if r then
r = failure( "undefined", r, options )
r = failure( "undefined", r, options )
else -- all mandatory present
else -- all mandatory present
for i = 1, #duty do
for i = 1, #duty do
s = duty[ i ]
s = duty[ i ]
if not got[ s ] then
if not got[ s ] then
r = fault( r, s )
r = fault( r, s )
end
end
end -- for i
end -- for i
if r then
if r then
r = failure( "empty", r, options )
r = failure( "empty", r, options )
end
end
end
end
end
end
return r
return r
end -- fix()
end -- fix()


Line 712: Line 701:


local function flat( collection, options )
local function flat( collection, options )
-- Return all table elements with downcased string
-- Return all table elements with downcased string
-- Precondition:
-- 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.
-- Return table, may be empty; or string with error message.
-- Uses:
-- Uses:
-- mw.ustring.lower()
-- mw.ustring.lower()
-- fault()
-- fault()
-- failure()
-- failure()
local k, v
local k, v
local r = { }
local r = { }
local e = false
local e = false
for k, v in pairs( collection ) do
for k, v in pairs( collection ) do
if type ( k ) == "string" then
if type ( k ) == "string" then
k = mw.ustring.lower( k )
k = mw.ustring.lower( k )
if r[ k ] then
if r[ k ] then
e = fault( e, k )
e = fault( e, k )
end
end
end
end
r[ k ] = v
r[ k ] = v
end -- for k, v
end -- for k, v
if e then
if e then
r = failure( "multiSpell", e, options )
r = failure( "multiSpell", e, options )
end
end
return r
return r
end -- flat()
end -- flat()


Line 743: Line 732:


local function fold( options )
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:
-- Precondition:
-- options -- table; details
-- 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:
-- Postcondition:
-- Return merged table, or message string if error
-- Return merged table, or message string if error
-- Uses:
-- Uses:
-- finder()
-- finder()
-- fault()
-- fault()
-- failure()
-- failure()
-- flat()
-- flat()
local i, e, r, s
local i, e, r, s
local base = options.mandatory
local base = options.mandatory
local extend = options.optional
local extend = options.optional
if #base == 0 then
if #base == 0 then
if #extend == 0 then
if #extend == 0 then
r = { }
r = { }
else
else
r = extend
r = extend
end
end
else
else
if #extend == 0 then
if #extend == 0 then
r = base
r = base
else
else
e = false
e = false
for i = 1, #extend do
for i = 1, #extend do
s = extend[ i ]
s = extend[ i ]
if finder( base, s ) then
if finder( base, s ) then
e = fault( e, s )
e = fault( e, s )
end
end
end -- for i
end -- for i
if e then
if e then
r = failure( "dupOpt", e, options )
r = failure( "dupOpt", e, options )
else
else
r = { }
r = { }
for i = 1, #base do
for i = 1, #base do
table.insert( r, base[ i ] )
table.insert( r, base[ i ] )
end -- for i
end -- for i
for i = 1, #extend do
for i = 1, #extend do
table.insert( r, extend[ i ] )
table.insert( r, extend[ i ] )
end -- for i
end -- for i
end
end
end
end
end
end
if options.low and type( r ) == "table" then
if options.low and type( r ) == "table" then
r = flat( r, options )
r = flat( r, options )
end
end
return r
return r
end -- fold()
end -- fold()


Line 798: Line 787:


local function form( light, options, frame )
local function form( light, options, frame )
-- Run parameter analysis on current environment
-- Run parameter analysis on current environment
-- Precondition:
-- 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.mandatory
-- options.optional
-- options.optional
-- frame -- object, or false
-- frame -- object, or false
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid
-- false if valid
-- Uses:
-- Uses:
-- fold()
-- fold()
-- fetch()
-- fetch()
-- fix()
-- fix()
-- finalize()
-- finalize()
local duty, r
local duty, r
if type( options ) == "table" then
if type( options ) == "table" then
if type( options.mandatory ) ~= "table" then
if type( options.mandatory ) ~= "table" then
options.mandatory = { }
options.mandatory = { }
end
end
duty = options.mandatory
duty = options.mandatory
if type( options.optional ) ~= "table" then
if type( options.optional ) ~= "table" then
options.optional = { }
options.optional = { }
end
end
r = fold( options )
r = fold( options )
else
else
options = { }
options = { }
duty = { }
duty = { }
r = { }
r = { }
end
end
if type( r ) == "table" then
if type( r ) == "table" then
local got = fetch( light, options )
local got = fetch( light, options )
if type( got ) == "table" then
if type( got ) == "table" then
r = fix( r, duty, got, options )
r = fix( r, duty, got, options )
else
else
r = got
r = got
end
end
end
end
return finalize( r, options, frame )
return finalize( r, options, frame )
end -- form()
end -- form()


Line 842: Line 831:


local function format( analyze, options )
local function format( analyze, options )
-- Check validity of a value
-- Check validity of a value
-- Precondition:
-- Precondition:
-- analyze -- string to be analyzed
-- analyze -- string to be analyzed
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- options.say
-- options.say
-- options.min
-- options.min
-- options.max
-- options.max
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- false if valid or no answer permitted
-- Uses:
-- Uses:
-- feasible()
-- feasible()
-- failure()
-- failure()
local r = feasible( analyze, options, false )
local r = feasible( analyze, options, false )
local show
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
if type( options.max ) == "number" then
if type( options.max ) == "number" then
if options.max < options.min then
if options.max < options.min then
r = failure( "minmax",
r = failure( "minmax",
string.format( "%d > %d",
string.format( "%d > %d",
options.min,
options.min,
options.max ),
options.max ),
options )
options )
end
end
end
end
if #analyze < options.min and not r then
if #analyze < options.min and not r then
show = " <" .. options.min
show = " <" .. options.min
if options.say then
if options.say then
show = string.format( "%s '%s'", show, options.say )
show = string.format( "%s '%s'", show, options.say )
end
end
r = failure( "tooShort", show, options )
r = failure( "tooShort", show, options )
end
end
else
else
r = failure( "invalidPar", "min", options )
r = failure( "invalidPar", "min", options )
end
end
end
end
if options.max and not r then
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
show = " >" .. options.max
if options.say then
if options.say then
show = string.format( "%s '%s'", show, options.say )
show = string.format( "%s '%s'", show, options.say )
end
end
r = failure( "tooLong", show, options )
r = failure( "tooLong", show, options )
end
end
else
else
r = failure( "invalidPar", "max", options )
r = failure( "invalidPar", "max", options )
end
end
end
end
return r
return r
end -- format()
end -- format()


Line 898: Line 887:


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:
-- Precondition:
-- assignment -- collection
-- assignment -- collection
-- access -- id of parameter in collection
-- access -- id of parameter in collection
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- false if valid or no answer permitted
-- Uses:
-- Uses:
-- mw.text.trim()
-- mw.text.trim()
-- format()
-- format()
-- failure()
-- failure()
local r = false
local r = false
if type( assignment ) == "table" then
if type( assignment ) == "table" then
local story = assignment.args[ access ] or ""
local story = assignment.args[ access ] or ""
if type( access ) == "number" then
if type( access ) == "number" then
story = mw.text.trim( story )
story = mw.text.trim( story )
end
end
if type( options ) ~= "table" then
if type( options ) ~= "table" then
options = { }
options = { }
end
end
options.say = access
options.say = access
r = format( story, options )
r = format( story, options )
end
end
return r
return r
end -- formatted()
end -- formatted()


Line 928: Line 917:


local function furnish( frame, action )
local function furnish( frame, action )
-- Prepare #invoke evaluation of .assert() or .valid()
-- Prepare #invoke evaluation of .assert() or .valid()
-- Precondition:
-- Precondition:
-- frame -- object; #invoke environment
-- frame -- object; #invoke environment
-- action -- "assert" or "valid"
-- action -- "assert" or "valid"
-- Postcondition:
-- Postcondition:
-- Return string with error message or ""
-- Return string with error message or ""
-- Uses:
-- Uses:
-- form()
-- form()
-- failure()
-- failure()
-- finalize()
-- finalize()
-- TemplatePar.valid()
-- TemplatePar.valid()
-- TemplatePar.assert()
-- TemplatePar.assert()
local options = { mandatory = { "1" },
local options = { mandatory = { "1" },
optional = { "2",
optional = { "2",
"cat",
"cat",
"errNS",
"errNS",
"low",
"low",
"max",
"max",
"min",
"min",
"format",
"format",
"preview",
"preview",
"template" },
"template" },
template = string.format( "&#35;invoke:%s|%s|",
template = string.format( "&#35;invoke:%s|%s|",
"TemplatePar",
"TemplatePar",
action )
action )
}
}
local r = form( false, options, frame )
local r = form( false, options, frame )
if not r then
if not r then
local s
local s
options = { cat = frame.args.cat,
options = { cat = frame.args.cat,
errNS = frame.args.errNS,
errNS = frame.args.errNS,
low = frame.args.low,
low = frame.args.low,
format = frame.args.format,
format = frame.args.format,
preview = frame.args.preview,
preview = frame.args.preview,
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*$" )
s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
if s then
if s then
options.min = tonumber( s )
options.min = tonumber( s )
else
else
r = failure( "invalidPar",
r = failure( "invalidPar",
"min=" .. frame.args.min,
"min=" .. frame.args.min,
options )
options )
end
end
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
if s then
options.max = tonumber( s )
options.max = tonumber( s )
else
else
r = failure( "invalidPar",
r = failure( "invalidPar",
"max=" .. frame.args.max,
"max=" .. frame.args.max,
options )
options )
end
end
end
end
if r then
if r then
r = finalize( r, options, frame )
r = finalize( r, options, frame )
else
else
s = frame.args[ 1 ] or ""
s = frame.args[ 1 ] or ""
r = tonumber( s )
r = tonumber( s )
if ( r ) then
if ( r ) then
s = r
s = r
end
end
if action == "valid" then
if action == "valid" then
r = TemplatePar.valid( s, options, frame )
r = TemplatePar.valid( s, options, frame )
elseif action == "assert" then
elseif action == "assert" then
r = TemplatePar.assert( s, "", options )
r = TemplatePar.assert( s, "", options )
end
end
end
end
end
end
return r or ""
return r or ""
end -- furnish()
end -- furnish()


Line 1,006: Line 995:


TemplatePar.assert = function ( analyze, append, options )
TemplatePar.assert = function ( analyze, append, options )
-- Perform parameter analysis on a single string
-- Perform parameter analysis on a single string
-- Precondition:
-- 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
-- options -- table; optional details
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid
-- false if valid
-- Uses:
-- Uses:
-- format()
-- format()
local r = format( analyze, options )
local r = format( analyze, options )
if ( r ) then
if ( r ) then
if ( type( append ) == "string" ) then
if ( type( append ) == "string" ) then
if ( append ~= "" ) then
if ( append ~= "" ) then
r = string.format( "%s<br />%s", append, r )
r = string.format( "%s<br />%s", append, r )
end
end
else
else
error( r, 0 )
error( r, 0 )
end
end
end
end
return r
return r
end -- TemplatePar.assert()
end -- TemplatePar.assert()


Line 1,033: Line 1,022:


TemplatePar.check = function ( options )
TemplatePar.check = function ( options )
-- Run parameter analysis on current template environment
-- Run parameter analysis on current template environment
-- Precondition:
-- Precondition:
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- options.mandatory
-- options.mandatory
-- options.optional
-- options.optional
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid
-- false if valid
-- Uses:
-- Uses:
-- form()
-- form()
return form( true, options, false )
return form( true, options, false )
end -- TemplatePar.check()
end -- TemplatePar.check()


Line 1,049: Line 1,038:


TemplatePar.count = function ()
TemplatePar.count = function ()
-- Return number of template parameters
-- Return number of template parameters
-- Postcondition:
-- Postcondition:
-- Return number, starting at 0
-- Return number, starting at 0
-- Uses:
-- Uses:
-- mw.getCurrentFrame()
-- mw.getCurrentFrame()
-- frame:getParent()
-- frame:getParent()
local k, v
local k, v
local r = 0
local r = 0
local t = mw.getCurrentFrame():getParent()
local t = mw.getCurrentFrame():getParent()
local o = t.args
local o = t.args
for k, v in pairs( o ) do
for k, v in pairs( o ) do
r = r + 1
r = r + 1
end -- for k, v
end -- for k, v
return r
return r
end -- TemplatePar.count()
end -- TemplatePar.count()


Line 1,068: Line 1,057:


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:
-- Postcondition:
-- Return number, starting at 0
-- Return number, starting at 0
-- Uses:
-- Uses:
-- mw.getCurrentFrame()
-- mw.getCurrentFrame()
-- frame:getParent()
-- frame:getParent()
local k, v
local k, v
local r = 0
local r = 0
local t = mw.getCurrentFrame():getParent()
local t = mw.getCurrentFrame():getParent()
local o = t.args
local o = t.args
for k, v in pairs( o ) do
for k, v in pairs( o ) do
if not v:match( "^%s*$" ) then
if not v:match( "^%s*$" ) then
r = r + 1
r = r + 1
end
end
end -- for k, v
end -- for k, v
return r
return r
end -- TemplatePar.countNotEmpty()
end -- TemplatePar.countNotEmpty()


Line 1,089: Line 1,078:


TemplatePar.downcase = function ( options )
TemplatePar.downcase = function ( options )
-- Return all template parameters with downcased name
-- Return all template parameters with downcased name
-- Precondition:
-- 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.
-- Return table, may be empty; or string with error message.
-- Uses:
-- Uses:
-- mw.getCurrentFrame()
-- mw.getCurrentFrame()
-- frame:getParent()
-- frame:getParent()
-- flat()
-- flat()
local t = mw.getCurrentFrame():getParent()
local t = mw.getCurrentFrame():getParent()
return flat( t.args, options )
return flat( t.args, options )
end -- TemplatePar.downcase()
end -- TemplatePar.downcase()


Line 1,105: Line 1,094:


TemplatePar.valid = function ( access, options, frame )
TemplatePar.valid = function ( access, options, frame )
-- Check validity of one particular template parameter
-- Check validity of one particular template parameter
-- Precondition:
-- Precondition:
-- access -- id of parameter in template transclusion
-- access -- id of parameter in template transclusion
-- string or number
-- string or number
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- frame -- object; #invoke environment
-- frame -- object; #invoke environment
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- false if valid or no answer permitted
-- Uses:
-- Uses:
-- mw.text.trim()
-- mw.text.trim()
-- TemplatePar.downcase()
-- TemplatePar.downcase()
-- frame:getParent()
-- frame:getParent()
-- formatted()
-- formatted()
-- failure()
-- failure()
-- finalize()
-- finalize()
local r = type( access )
local r = type( access )
if r == "string" then
if r == "string" then
r = mw.text.trim( access )
r = mw.text.trim( access )
if #r == 0 then
if #r == 0 then
r = false
r = false
end
end
elseif r == "number" then
elseif r == "number" then
r = access
r = access
else
else
r = false
r = false
end
end
if r then
if r then
local params
local params
if type( options ) ~= "table" then
if type( options ) ~= "table" then
options = { }
options = { }
end
end
if options.low then
if options.low then
params = TemplatePar.downcase( options )
params = TemplatePar.downcase( options )
else
else
params = frame:getParent()
params = frame:getParent()
end
end
r = formatted( params, access, options )
r = formatted( params, access, options )
else
else
r = failure( "noname", false, options )
r = failure( "noname", false, options )
end
end
return finalize( r, options, frame )
return finalize( r, options, frame )
end -- TemplatePar.valid()
end -- TemplatePar.valid()


Line 1,152: Line 1,141:


TemplatePar.verify = function ( options )
TemplatePar.verify = function ( options )
-- Perform #invoke parameter analysis
-- Perform #invoke parameter analysis
-- Precondition:
-- Precondition:
-- options -- table or nil; optional details
-- options -- table or nil; optional details
-- Postcondition:
-- Postcondition:
-- Return string with error message as configured;
-- Return string with error message as configured;
-- false if valid
-- false if valid
-- Uses:
-- Uses:
-- form()
-- form()
return form( false, options, false )
return form( false, options, false )
end -- TemplatePar.verify()
end -- TemplatePar.verify()


Line 1,171: Line 1,160:


function p.assert( frame )
function p.assert( frame )
-- Perform parameter analysis on some single string
-- Perform parameter analysis on some single string
-- Precondition:
-- Precondition:
-- frame -- object; #invoke environment
-- frame -- object; #invoke environment
-- Postcondition:
-- Postcondition:
-- Return string with error message or ""
-- Return string with error message or ""
-- Uses:
-- Uses:
-- furnish()
-- furnish()
return furnish( frame, "assert" )
return furnish( frame, "assert" )
end -- .assert()
end -- .assert()


Line 1,184: Line 1,173:


function p.check( frame )
function p.check( frame )
-- Check validity of template parameters
-- Check validity of template parameters
-- Precondition:
-- Precondition:
-- frame -- object; #invoke environment
-- frame -- object; #invoke environment
-- Postcondition:
-- Postcondition:
-- Return string with error message or ""
-- Return string with error message or ""
-- Uses:
-- Uses:
-- form()
-- form()
-- fill()
-- fill()
local options = { optional = { "all",
local options = { optional = { "all",
"opt",
"opt",
"cat",
"cat",
"errNS",
"errNS",
"low",
"low",
"format",
"format",
"preview",
"preview",
"template" },
"template" },
template = "&#35;invoke:TemplatePar|check|"
template = "&#35;invoke:TemplatePar|check|"
}
}
local r = form( false, options, frame )
local r = form( false, options, frame )
if not r then
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 ),
cat = frame.args.cat,
cat = frame.args.cat,
errNS = frame.args.errNS,
errNS = frame.args.errNS,
low = frame.args.low,
low = frame.args.low,
format = frame.args.format,
format = frame.args.format,
preview = frame.args.preview,
preview = frame.args.preview,
template = frame.args.template
template = frame.args.template
}
}
r = form( true, options, frame )
r = form( true, options, frame )
end
end
return r or ""
return r or ""
end -- .check()
end -- .check()


Line 1,221: Line 1,210:


function p.count( frame )
function p.count( frame )
-- Count number of template parameters
-- Count number of template parameters
-- Postcondition:
-- Postcondition:
-- Return string with digits including "0"
-- Return string with digits including "0"
-- Uses:
-- Uses:
-- TemplatePar.count()
-- TemplatePar.count()
return tostring( TemplatePar.count() )
return tostring( TemplatePar.count() )
end -- .count()
end -- .count()


Line 1,232: Line 1,221:


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:
-- Postcondition:
-- Return string with digits including "0"
-- Return string with digits including "0"
-- Uses:
-- Uses:
-- TemplatePar.countNotEmpty()
-- TemplatePar.countNotEmpty()
return tostring( TemplatePar.countNotEmpty() )
return tostring( TemplatePar.countNotEmpty() )
end -- .countNotEmpty()
end -- .countNotEmpty()


Line 1,243: Line 1,232:


function p.match( frame )
function p.match( frame )
-- Combined analysis of parameters and their values
-- Combined analysis of parameters and their values
-- Postcondition:
-- Postcondition:
-- Return string with error message or ""
-- Return string with error message or ""
-- Uses:
-- Uses:
-- mw.text.trim()
-- mw.text.trim()
-- mw.ustring.lower()
-- mw.ustring.lower()
-- failure()
-- failure()
-- form()
-- form()
-- TemplatePar.downcase()
-- TemplatePar.downcase()
-- figure()
-- figure()
-- feasible()
-- feasible()
-- fault()
-- fault()
-- finalize()
-- finalize()
local r = false
local r = false
local options = { cat = frame.args.cat,
local options = { cat = frame.args.cat,
errNS = frame.args.errNS,
errNS = frame.args.errNS,
low = frame.args.low,
low = frame.args.low,
format = frame.args.format,
format = frame.args.format,
preview = frame.args.preview,
preview = frame.args.preview,
template = frame.args.template
template = frame.args.template
}
}
local k, v, s
local k, v, s
local params = { }
local params = { }
for k, v in pairs( frame.args ) do
for k, v in pairs( frame.args ) do
if type( k ) == "number" then
if type( k ) == "number" then
s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
if s then
if s then
s = mw.text.trim( s )
s = mw.text.trim( s )
if s == "" then
if s == "" then
s = false
s = false
end
end
end
end
if s then
if s then
if options.low then
if options.low then
s = mw.ustring.lower( s )
s = mw.ustring.lower( s )
end
end
if params[ s ] then
if params[ s ] then
s = params[ s ]
s = params[ s ]
s[ #s + 1 ] = v
s[ #s + 1 ] = v
else
else
params[ s ] = { v }
params[ s ] = { v }
end
end
else
else
r = failure( "invalidPar", tostring( k ), options )
r = failure( "invalidPar", tostring( k ), options )
break -- for k, v
break -- for k, v
end
end
end
end
end -- for k, v
end -- for k, v
if not r then
if not r then
s = { }
s = { }
for k, v in pairs( params ) do
for k, v in pairs( params ) do
s[ #s + 1 ] = k
s[ #s + 1 ] = k
end -- for k, v
end -- for k, v
options.optional = s
options.optional = s
r = form( true, options, frame )
r = form( true, options, frame )
end
end
if not r then
if not r then
local errMiss, errValues, lack, rule
local errMiss, errValues, lack, rule
local targs = frame:getParent().args
local targs = frame:getParent().args
options.optional = nil
options.optional = nil
if options.low then
if options.low then
targs = TemplatePar.downcase()
targs = TemplatePar.downcase()
else
else
targs = frame:getParent().args
targs = frame:getParent().args
end
end
errMiss = false
errMiss = false
errValues = false
errValues = false
for k, v in pairs( params ) do
for k, v in pairs( params ) do
options.say = k
options.say = k
errValue = false
errValue = false
s = targs[ k ]
s = targs[ k ]
if s then
if s then
if s == "" then
if s == "" then
lack = true
lack = true
else
else
lack = false
lack = false
end
end
else
else
s = ""
s = ""
lack = true
lack = true
end
end
for r, rule in pairs( v ) do
for r, rule in pairs( v ) do
options = figure( rule, options )
options = figure( rule, options )
r = feasible( s, options, true )
r = feasible( s, options, true )
if r then
if r then
if lack then
if lack then
if errMiss then
if errMiss then
errMiss = string.format( "%s, '%s'",
errMiss = string.format( "%s, '%s'",
errMiss, k )
errMiss, k )
else
else
errMiss = string.format( "'%s'", k )
errMiss = string.format( "'%s'", k )
end
end
elseif not errMiss then
elseif not errMiss then
errValues = fault( errValues, r )
errValues = fault( errValues, r )
end
end
break -- for r, rule
break -- for r, rule
end
end
end -- for s, rule
end -- for s, rule
end -- for k, v
end -- for k, v
r = ( errMiss or errValues )
r = ( errMiss or errValues )
if r then
if r then
if errMiss then
if errMiss then
r = failure( "undefined", errMiss, options )
r = failure( "undefined", errMiss, options )
else
else
r = failure( "invalid", errValues, options )
r = failure( "invalid", errValues, options )
end
end
r = finalize( r, options, frame )
r = finalize( r, options, frame )
end
end
end
end
return r or ""
return r or ""
end -- .match()
end -- .match()


Line 1,358: Line 1,347:


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


Line 1,371: Line 1,360:


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



Latest revision as of 22:36, 2 April 2023

--[=[ TemplatePar 2015-02-14
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",
	missing	 = "&#35;invoke:TemplatePar missing library",
	multiSpell  = "Error in template * multiple spelling of parameter",
	noMSGnoCAT  = "&#35;invoke:TemplatePar neither message nor category",
	noname	  = "&#35;invoke:TemplatePar missing parameter name",
	notFound	= "Error in template * missing page",
	tooLong	 = "Error in template * parameter too long",
	tooShort	= "Error in template * parameter too short",
	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",
	noMSGnoCAT  = "&#35;invoke:TemplatePar weder Meldung noch Kategorie",
	noname	  = "&#35;invoke:TemplatePar Parameter nicht angegeben",
	notFound	= "Fehler bei Vorlage * Seite fehlt",
	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]*$",
	[ "w" ]		= "^%S*$",
	[ "w+" ]	   = "^%S+$",
	[ "base64" ]   = "^[A-Za-z0-9%+/]*$",
	[ "base64+" ]  = "^[A-Za-z0-9%+/]+$",
	[ "aa" ]	   = "[%a%a].*[%a%a]",
	[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",
									1, 31, 127 ),
	[ "+" ]		= "%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
	patternCJK = patternCJK or mw.ustring.char(91,
									   13312, 45,  40959,
									  131072, 45, 178207,
									  93 )
	if mw.ustring.match( s, patternCJK ) then
		r = true
	end
	return r
end -- containsCJK()



local function facility( accept, attempt )
	-- Check string as possible file name or other source page
	-- Precondition:
	--	 accept   -- string; requirement
	--						 file
	--						 file+
	--						 file:
	--						 file:+
	--						 image
	--						 image+
	--						 image:
	--						 image:+
	--	 attempt  -- string; to be tested
	-- Postcondition:
	--	 Return error keyword, or false
	-- Uses:
	--	 Module:FileMedia
	--	 FileMedia.isType()
	local r
	if attempt and attempt ~= "" then
		local lucky, FileMedia = pcall( require, "Module:FileMedia" )
		if type( FileMedia ) == "table" then
			FileMedia = FileMedia.FileMedia()
			local s, live = accept:match( "^([a-z]+)(:?)%+?$" )
			if live then
				if FileMedia.isType( attempt, s ) then
					if FileMedia.isFile( attempt ) then
						r = false
					else
						r = "notFound"
					end
				else
					r = "invalid"
				end
			elseif FileMedia.isType( attempt, s ) then
				r = false
			else
				r = "invalid"
			end
		else
			r = "missing"
		end
	elseif accept:match( "%+$" ) then
		r = "empty"
	else
		r = false
	end
	return r
end -- facility()



local function factory( say )
	-- Retrieve localized message string in content language
	-- Precondition:
	--	 say  -- string; message ID
	-- Postcondition:
	--	 Return some message string
	-- Uses:
	--	 >  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 ] or L10nDef[ "en" ]
		r = l10n[ say ]
	else
		m:inLanguage( c )
		r = m:plain()
	end
	r = r or string.format( "(((%s)))", say )
	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 = string.format( "%s (%s)", r, options.template )
			end
		end
	end
	if suspect then
		r = string.format( "%s: %s", r, suspect )
	end
	return r
end -- failure()



local function fault( store, key )
	-- Add key to collection string and insert separator
	-- Precondition:
	--	 store  -- string or nil or false; collection string
	--	 key	-- string or number; to be appended
	-- Postcondition:
	--	 Return string; extended
	local r
	local s
	if type( key ) == "number" then
		s = tostring( key )
	else
		s = key
	end
	if store then
		r = string.format( "%s; %s", store, s )
	else
		r = s
	end
	return r
end -- fault()



local function feasible( analyze, options, abbr )
	-- Check content of a value
	-- Precondition:
	--	 analyze  -- string to be analyzed
	--	 options  -- table or nil; optional details
	--				 options.pattern
	--				 options.key
	--				 options.say
	--	 abbr	 -- true: abbreviated error message
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid or no answer permitted
	-- Uses:
	--	 >  Patterns
	--	 failure()
	--	 mw.text.trim()
	--	 facility()
	--	 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]" )  and
				   not analyze:match( "^%s*$" ) then
					scan = false
					if options.say then
						show = string.format( "'%s'", 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 )
						elseif op == "!=" then
							i = ( i ~= n )
						else
							n = false
						end
					end
					if not i then
						r = "invalid"
					end
				elseif plus then
					r = "undefined"
				end
			elseif s:match( "^image%+?:?$" )  or
				   s:match( "^file%+?:?$" ) then
				r = facility( s, analyze )
				n = true
			elseif s:match( "langW?%+?" ) then
				n = "lang"
-- lang lang+
-- langW langW+
			end
			if not n and not r then
				r = "unknownRule"
			end
			if r then
				if options.say then
					show = string.format( "'%s' %s", options.say, s )
				else
					show = s
				end
				if abbr then
					r = show
				else
					r = failure( r, show, options )
				end
			end
		end
	end
	if scan then
		local legal, got = pcall( 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 = string.format( "'%s'", options.say )
					end
					if abbr then
						r = show
					else
						r = failure( "invalid", show, options )
					end
				end
			end
		else
			r = failure( "badPattern",
						 string.format( "%s *** %s", scan, got ),
						 options )
		end
	end
	return r
end -- feasible()



local function fed( haystack, needle )
	-- Find needle in haystack map
	-- Precondition:
	--	 haystack  -- table; map of key values
	--	 needle	-- any; identifier
	-- Postcondition:
	--	 Return true iff found
	local k, v
	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, frame )
	-- Finalize message
	-- Precondition:
	--	 submit   -- string or false or nil; non-empty error message
	--	 options  -- table or nil; optional details
	--				 options.format
	--				 options.preview
	--				 options.cat
	--				 options.template
	--	 frame	-- object, or false
	-- Postcondition:
	--	 Return string or false
	-- Uses:
	--	 factory()
	local r = false
	if submit then
		local opt, s
		local lazy = false
		local show = false
		if type( options ) == "table" then
			opt  = options
			show = opt.format
			lazy = ( show == ""  or  show == "0"  or  show == "-" )
			s	= opt.preview
			if type( s ) == "string"  and
				s ~= ""  and  s ~= "0"  and  s ~= "-" then
				if lazy then
					show = ""
					lazy = false
				end
				frame = frame or mw.getCurrentFrame()
				if frame:preprocess( "{{REVISIONID}}" ) == "" then
					if s == "1" then
						show = "*"
					else
						show = s
					end
				end
			end
		else
			opt = { }
		end
		if lazy then
			if not opt.cat then
				r = string.format( "%s %s",
								   submit,  factory( "noMSGnoCAT" ) )
			end
		else
			r = submit
		end
		if r  and  not lazy then
			local i
			if not show  or  show == "*" then
				show = "<span class=\"error\">@@@</span>"
			end
			i = show:find( "@@@", 1, true )
			if i then
				-- No gsub() since r might contain "%3" (e.g. URL)
				r = string.format( "%s%s%s",
								   show:sub( 1,  i - 1 ),
								   r,
								   show:sub( i + 3 ) )
			else
				r = show
			end
		end
		s = opt.cat
		if type( s ) == "string" then
			if opt.errNS then
				local ns = mw.title.getCurrentTitle().namespace
				local st = type( opt.errNS )
				if st == "string" then
					local space  = string.format( ".*%%s%d%%s.*", ns )
					local spaces = string.format( " %s ", opt.errNS )
					if spaces:match( space ) then
						opt.errNS = false
					end
				elseif st == "table" then
					for i = 1, #opt.errNS do
						if opt.errNS[ i ] == ns then
							opt.errNS = false
							break	-- for i
						end
					end -- for i
				end
			end
			if opt.errNS then
				r = ""
			else
				r = r or ""
				if s:find( "@@@" ) then
					if type( opt.template ) == "string" then
						s = s:gsub( "@@@", opt.template )
					end
				end
				local i
				local cats = mw.text.split( s, "%s*#%s*" )
				for i = 1, #cats do
					s = mw.text.trim( cats[ i ] )
					if #s > 0 then
						r = string.format( "%s[[Category:%s]]", r, s )
					end
				end -- for i
			end
		end
	end
	return r
end -- finalize()



local function finder( haystack, needle )
	-- Find needle in haystack sequence
	-- Precondition:
	--	 haystack  -- table; sequence of key names, downcased if low
	--	 needle	-- any; key name
	-- Postcondition:
	--	 Return true iff found
	local i
	for i = 1, #haystack do
		if haystack[ i ] == needle then
			return true
		end
	end -- for i
	return false
end -- finder()



local function fix( valid, duty, got, options )
	-- Perform parameter analysis
	-- Precondition:
	--	 valid	-- table; unique sequence of known parameters
	--	 duty	 -- table; sequence of mandatory parameters
	--	 got	  -- table; sequence of current parameters
	--	 options  -- table or nil; optional details
	-- Postcondition:
	--	 Return string as configured; empty if valid
	-- Uses:
	--	 finder()
	--	 fault()
	--	 failure()
	--	 fed()
	local 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",
					 string.format( "'%s'", r ),
					 options )
	else -- all names valid
		local i, s
		for i = 1, #duty do
			s = duty[ i ]
			if not fed( got, s ) then
				r = fault( r, s )
			end
		end -- for i
		if r then
			r = failure( "undefined", r, options )
		else -- all mandatory present
			for i = 1, #duty do
				s = duty[ i ]
				if not got[ s ] then
					r = fault( r, s )
				end
			end -- for i
			if r then
				r = failure( "empty", r, options )
			end
		end
	end
	return r
end -- fix()



local function flat( collection, options )
	-- Return all table elements with downcased string
	-- Precondition:
	--	 collection  -- table; k=v pairs
	--	 options	 -- table or nil; optional messaging details
	-- Postcondition:
	--	 Return table, may be empty; or string with error message.
	-- Uses:
	--	 mw.ustring.lower()
	--	 fault()
	--	 failure()
	local k, v
	local r = { }
	local e = false
	for k, v in pairs( collection ) do
		if type ( k ) == "string" then
			k = mw.ustring.lower( k )
			if r[ k ] then
				e = fault( e, k )
			end
		end
		r[ k ] = v
	end -- for k, v
	if e then
		r = failure( "multiSpell", e, options )
	end
	return r
end -- flat()



local function fold( options )
	-- Merge two tables, create new sequence if both not empty
	-- Precondition:
	--	 options  -- table; details
	--				 options.mandatory   sequence to keep unchanged
	--				 options.optional	sequence to be appended
	--				 options.low		 downcased expected
	-- Postcondition:
	--	 Return merged table, or message string if error
	-- Uses:
	--	 finder()
	--	 fault()
	--	 failure()
	--	 flat()
	local i, e, r, s
	local base   = options.mandatory
	local extend = options.optional
	if #base == 0 then
		if #extend == 0 then
			r = { }
		else
			r = extend
		end
	else
		if #extend == 0 then
			r = base
		else
			e = false
			for i = 1, #extend do
				s = extend[ i ]
				if finder( base, s ) then
					e = fault( e, s )
				end
			end -- for i
			if e then
				r = failure( "dupOpt", e, options )
			else
				r = { }
				for i = 1, #base do
					table.insert( r, base[ i ] )
				end -- for i
				for i = 1, #extend do
					table.insert( r, extend[ i ] )
				end -- for i
			end
		end
	end
	if options.low  and  type( r ) == "table" then
		r = flat( r, options )
	end
	return r
end -- fold()



local function form( light, options, frame )
	-- Run parameter analysis on current environment
	-- Precondition:
	--	 light	-- true: template transclusion;  false: #invoke
	--	 options  -- table or nil; optional details
	--				 options.mandatory
	--				 options.optional
	--	 frame	-- object, or false
	-- 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, frame )
end -- form()



local function format( analyze, options )
	-- Check validity of a value
	-- Precondition:
	--	 analyze  -- string to be analyzed
	--	 options  -- table or nil; optional details
	--				 options.say
	--				 options.min
	--				 options.max
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid or no answer permitted
	-- Uses:
	--	 feasible()
	--	 failure()
	local r = feasible( analyze, options, false )
	local show
	if options.min  and  not r then
		if type( options.min ) == "number" then
			if type( options.max ) == "number" then
				if options.max < options.min then
					r = failure( "minmax",
								 string.format( "%d > %d",
												options.min,
												options.max ),
								 options )
				end
			end
			if #analyze < options.min  and  not r then
				show = " <" .. options.min
				if options.say then
					show = string.format( "%s '%s'", show, options.say )
				end
				r = failure( "tooShort", show, options )
			end
		else
			r = failure( "invalidPar", "min", options )
		end
	end
	if options.max  and  not r then
		if type( options.max ) == "number" then
			if #analyze > options.max then
				show = " >" .. options.max
				if options.say then
					show = string.format( "%s '%s'", show, options.say )
				end
				r = failure( "tooLong", show, options )
			end
		else
			r = failure( "invalidPar", "max", options )
		end
	end
	return r
end -- format()



local function formatted( assignment, access, options )
	-- Check validity of one particular parameter in a collection
	-- Precondition:
	--	 assignment  -- collection
	--	 access	  -- id of parameter in collection
	--	 options	 -- table or nil; optional details
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid or no answer permitted
	-- Uses:
	--	 mw.text.trim() 
	--	 format()
	--	 failure()
	local r = false
	if type( assignment ) == "table" then
		local story = assignment.args[ access ] or ""
		if type( access ) == "number" then
			story = mw.text.trim( story ) 
		end
		if type( options ) ~= "table" then
			options = { }
		end
		options.say = access
		r = format( story, options )
	end
	return r
end -- formatted()



local function furnish( frame, action )
	-- Prepare #invoke evaluation of .assert() or .valid()
	-- Precondition:
	--	 frame	-- object; #invoke environment
	--	 action   -- "assert" or "valid"
	-- Postcondition:
	--	 Return string with error message or ""
	-- Uses:
	--	 form()
	--	 failure()
	--	 finalize()
	--	 TemplatePar.valid()
	--	 TemplatePar.assert()
	local options = { mandatory = { "1" },
					  optional  = { "2",
									"cat",
									"errNS",
									"low",
									"max",
									"min",
									"format",
									"preview",
									"template" },
					  template  = string.format( "&#35;invoke:%s|%s|",
												 "TemplatePar",
												 action )
					}
	local r	   = form( false, options, frame )
	if not r then
		local s
		options = { cat	  = frame.args.cat,
					errNS	= frame.args.errNS,
					low	  = frame.args.low,
					format   = frame.args.format,
					preview  = frame.args.preview,
					template = frame.args.template
				  }
		options = figure( frame.args[ 2 ], options )
		if type( frame.args.min ) == "string" then
			s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
			if s then
				options.min = tonumber( s )
			else
				r = failure( "invalidPar",
							 "min=" .. frame.args.min,
							 options )
			end
		end
		if type( frame.args.max ) == "string" then
			s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
			if s then
				options.max = tonumber( s )
			else
				r = failure( "invalidPar",
							 "max=" .. frame.args.max,
							 options )
			end
		end
		if r then
			r = finalize( r, options, frame )
		else
			s = frame.args[ 1 ] or ""
			r = tonumber( s )
			if ( r ) then
				s = r
			end
			if action == "valid" then
				r = TemplatePar.valid( s, options, frame )
			elseif action == "assert" then
				r = TemplatePar.assert( s, "", options )
			end
		end
	end
	return r or ""
end -- furnish()



TemplatePar.assert = function ( analyze, append, options )
	-- Perform parameter analysis on a single string
	-- Precondition:
	--	 analyze  -- string to be analyzed
	--	 append   -- string: append error message, prepending <br />
	--				 false or nil: throw error with message
	--	 options  -- table; optional details
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid
	-- Uses:
	--	 format()
	local r = format( analyze, options )
	if ( r ) then
		if ( type( append ) == "string" ) then
			if ( append ~= "" ) then
				r = string.format( "%s<br />%s", append, r )
			end
		else
			error( r, 0 )
		end
	end
	return r
end -- TemplatePar.assert()



TemplatePar.check = function ( options )
	-- Run parameter analysis on current template environment
	-- Precondition:
	--	 options  -- table or nil; optional details
	--				 options.mandatory
	--				 options.optional
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid
	-- Uses:
	--	 form()
	return form( true, options, false )
end -- TemplatePar.check()



TemplatePar.count = function ()
	-- Return number of template parameters
	-- Postcondition:
	--	 Return number, starting at 0
	-- Uses:
	--	 mw.getCurrentFrame()
	--	 frame:getParent()
	local k, v
	local r = 0
	local t = mw.getCurrentFrame():getParent()
	local o = t.args
	for k, v in pairs( o ) do
		r = r + 1
	end -- for k, v
	return r
end -- TemplatePar.count()



TemplatePar.countNotEmpty = function ()
	-- Return number of template parameters with more than whitespace
	-- Postcondition:
	--	 Return number, starting at 0
	-- Uses:
	--	 mw.getCurrentFrame()
	--	 frame:getParent()
	local k, v
	local r = 0
	local t = mw.getCurrentFrame():getParent()
	local o = t.args
	for k, v in pairs( o ) do
		if not v:match( "^%s*$" ) then
			r = r + 1
		end
	end -- for k, v
	return r
end -- TemplatePar.countNotEmpty()



TemplatePar.downcase = function ( options )
	-- Return all template parameters with downcased name
	-- Precondition:
	--	 options  -- table or nil; optional messaging details
	-- Postcondition:
	--	 Return table, may be empty; or string with error message.
	-- Uses:
	--	 mw.getCurrentFrame()
	--	 frame:getParent()
	--	 flat()
	local t = mw.getCurrentFrame():getParent()
	return flat( t.args, options )
end -- TemplatePar.downcase()



TemplatePar.valid = function ( access, options, frame )
	-- Check validity of one particular template parameter
	-- Precondition:
	--	 access   -- id of parameter in template transclusion
	--				 string or number
	--	 options  -- table or nil; optional details
	--	 frame	-- object; #invoke environment
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid or no answer permitted
	-- Uses:
	--	 mw.text.trim()
	--	 TemplatePar.downcase()
	--	 frame:getParent()
	--	 formatted()
	--	 failure()
	--	 finalize()
	local r = type( access )
	if r == "string" then
		r = mw.text.trim( access )
		if #r == 0 then
			r = false
		end
	elseif r == "number" then
		r = access
	else
		r = false
	end
	if r then
		local params
		if type( options ) ~= "table" then
			options = { }
		end
		if options.low then
			params = TemplatePar.downcase( options )
		else
			params = frame:getParent()
		end
		r = formatted( params, access, options )
	else
		r = failure( "noname", false, options )
	end
	return finalize( r, options, frame )
end -- TemplatePar.valid()



TemplatePar.verify = function ( options )
	-- Perform #invoke parameter analysis
	-- Precondition:
	--	 options  -- table or nil; optional details
	-- Postcondition:
	--	 Return string with error message as configured;
	--			false if valid
	-- Uses:
	--	 form()
	return form( false, options, false )
end -- TemplatePar.verify()



-- 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",
									"errNS",
									"low",
									"format",
									"preview",
									"template" },
					  template  = "&#35;invoke:TemplatePar|check|"
					}
	local r = form( false, options, frame )
	if not r then
		options = { mandatory = fill( frame.args.all ),
					optional  = fill( frame.args.opt ),
					cat	   = frame.args.cat,
					errNS	 = frame.args.errNS,
					low	   = frame.args.low,
					format	= frame.args.format,
					preview   = frame.args.preview,
					template  = frame.args.template
				  }
		r	   = form( true, options, frame )
	end
	return r or ""
end -- .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,
					  errNS	= frame.args.errNS,
					  low	  = frame.args.low,
					  format   = frame.args.format,
					  preview  = frame.args.preview,
					  template = frame.args.template
					}
	local k, v, s
	local params = { }
	for k, v in pairs( frame.args ) do
		if type( k ) == "number" then
			s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )
			if s then
				s = mw.text.trim( s )
				if s == "" then
					s = false
				end
			end
			if s then
				if options.low then
					s = mw.ustring.lower( s )
				end
				if params[ s ] then
					s = params[ s ]
					s[ #s + 1 ] = v
				else
					params[ s ] = { v }
				end
			else
				r = failure( "invalidPar",  tostring( k ),  options )
				break -- for k, v
			end
		end
	end -- for k, v
	if not r then
		s = { }
		for k, v in pairs( params ) do
			s[ #s + 1 ] = k
		end -- for k, v
		options.optional = s
		r = form( true, options, frame )
	end
	if not r then
		local errMiss, errValues, lack, rule
		local targs = frame:getParent().args
		options.optional = nil
		if options.low then
			targs = TemplatePar.downcase()
		else
			targs = frame:getParent().args
		end
		errMiss   = false
		errValues = false
		for k, v in pairs( params ) do
			options.say = k
			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 = string.format( "%s, '%s'",
													 errMiss, k )
						else
							errMiss = string.format( "'%s'", 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, frame )
		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