Module:TemplatePar and Module:TemplatePar/sandbox: Difference between pages
(Difference between pages)
imported>Leyo update from global upstream version, as requested by the developer; pre-view tested |
imported>Lemondoge mild simplifications, replace quintuple spaces with tabs |
||
| Line 1: | Line 1: | ||
--[=[ TemplatePar 2015-02-14 | |||
-- | |||
Template parameter utility | Template parameter utility | ||
* assert | * assert | ||
| Line 14: | Line 6: | ||
* countNotEmpty | * countNotEmpty | ||
* downcase() | * downcase() | ||
* match | * match | ||
* valid | * valid | ||
* verify() | * verify() | ||
* TemplatePar() | * TemplatePar() | ||
]=] | ]=] | ||
-- Module globals | -- Module globals | ||
local TemplatePar = { } | |||
local MessagePrefix = "lua-module-TemplatePar-" | |||
local L10nDef = {} | |||
L10nDef.en = { | |||
badPattern = "#invoke:TemplatePar pattern syntax error", | |||
dupOpt = "#invoke:TemplatePar repeated optional parameter", | |||
dupRule = "#invoke:TemplatePar conflict key/pattern", | |||
empty = "Error in template * undefined value for mandatory", | |||
invalid = "Error in template * invalid parameter", | |||
invalidPar = "#invoke:TemplatePar invalid parameter", | |||
minmax = "#invoke:TemplatePar min > max", | |||
missing = "#invoke:TemplatePar missing library", | |||
multiSpell = "Error in template * multiple spelling of parameter", | |||
noMSGnoCAT = "#invoke:TemplatePar neither message nor category", | |||
noname = "#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 = "#invoke:TemplatePar unknown rule" | |||
} | |||
L10nDef.de = { | |||
badPattern = "#invoke:TemplatePar Syntaxfehler des pattern", | |||
dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt", | |||
dupRule = "#invoke:TemplatePar Konflikt key/pattern", | |||
empty = "Fehler bei Vorlage * Pflichtparameter ohne Wert", | |||
invalid = "Fehler bei Vorlage * Parameter ungültig", | |||
invalidPar = "#invoke:TemplatePar Ungültiger Parameter", | |||
minmax = "#invoke:TemplatePar min > max", | |||
multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen", | |||
noMSGnoCAT = "#invoke:TemplatePar weder Meldung noch Kategorie", | |||
noname = "#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 = "#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( | 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() | end -- containsCJK() | ||
| Line 216: | Line 120: | ||
local function facility( accept, attempt ) | 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() | end -- facility() | ||
| Line 270: | Line 172: | ||
local function factory( say ) | 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() | end -- factory() | ||
local function | 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() | |||
end -- | |||
local function failure( spec, suspect, options ) | 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() | end -- failure() | ||
local function fault( store, key ) | 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() | end -- fault() | ||
| Line 575: | Line 264: | ||
local function feasible( analyze, options, abbr ) | 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() | end -- feasible() | ||
| Line 728: | Line 401: | ||
local function fed( haystack, needle ) | 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() | end -- fed() | ||
| Line 746: | Line 419: | ||
local function fetch( light, options ) | 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() | end -- fetch() | ||
| Line 792: | Line 465: | ||
local function figure( append, options ) | 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() | end -- figure() | ||
| Line 822: | Line 494: | ||
local function fill( specified ) | 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() | end -- fill() | ||
local function finalize( submit, options ) | 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() | end -- finalize() | ||
| Line 988: | Line 631: | ||
local function finder( haystack, needle ) | 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() | end -- finder() | ||
| Line 1,006: | Line 649: | ||
local function fix( valid, duty, got, options ) | 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() | end -- fix() | ||
| Line 1,063: | Line 701: | ||
local function flat( collection, options ) | 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() | end -- flat() | ||
| Line 1,094: | Line 732: | ||
local function fold( options ) | 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() | end -- fold() | ||
| Line 1,149: | Line 787: | ||
local function form( light, options, frame ) | 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() | end -- form() | ||
| Line 1,197: | Line 831: | ||
local function format( analyze, options ) | 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() | end -- format() | ||
| Line 1,253: | Line 887: | ||
local function formatted( assignment, access, options ) | 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() | end -- formatted() | ||
| Line 1,283: | Line 917: | ||
local function furnish( frame, action ) | 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( "#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() | end -- furnish() | ||
| Line 1,361: | Line 995: | ||
TemplatePar.assert = function ( analyze, append, options ) | 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() | end -- TemplatePar.assert() | ||
| Line 1,388: | Line 1,022: | ||
TemplatePar.check = function ( options ) | 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() | end -- TemplatePar.check() | ||
| Line 1,404: | Line 1,038: | ||
TemplatePar.count = function () | 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() | end -- TemplatePar.count() | ||
| Line 1,423: | Line 1,057: | ||
TemplatePar.countNotEmpty = function () | 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() | end -- TemplatePar.countNotEmpty() | ||
| Line 1,444: | Line 1,078: | ||
TemplatePar.downcase = function ( options ) | 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() | end -- TemplatePar.downcase() | ||
TemplatePar.valid = function ( access, options ) | 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() | end -- TemplatePar.valid() | ||
| Line 1,507: | Line 1,141: | ||
TemplatePar.verify = function ( options ) | 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() | end -- TemplatePar.verify() | ||
| Line 1,609: | Line 1,160: | ||
function p.assert( frame ) | 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 -- | end -- .assert() | ||
function p.check( frame ) | 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 = "#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 -- | end -- .check() | ||
function p.count( frame ) | function p.count( frame ) | ||
-- Count number of template parameters | |||
-- Postcondition: | |||
-- Return string with digits including "0" | |||
-- Uses: | |||
-- TemplatePar.count() | |||
return tostring( TemplatePar.count() ) | |||
end -- | end -- .count() | ||
function p.countNotEmpty( frame ) | 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 -- | end -- .countNotEmpty() | ||
function p.match( frame ) | 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() | |||
end -- | |||
function p.valid( frame ) | 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 -- | end -- .valid() | ||
function p.TemplatePar() | function p.TemplatePar() | ||
-- Retrieve function access for modules | |||
-- Postcondition: | |||
-- Return table with functions | |||
return TemplatePar | |||
end -- | end -- .TemplatePar() | ||
return p | return p | ||