Modul:TemplatePar
Version vom 4. Mai 2013, 10:10 Uhr von te>PerfektesChaos (update)
Die Dokumentation für dieses Modul kann unter Modul:TemplatePar/Doku erstellt werden
--[=[ TemplatePar 2013-05-03
Template parameter utility
* check
* count
* valid
* TemplatePar()
]=]
-- Module globals
local TemplatePar = { }
local messagePrefix = "lua-module-TemplatePar-"
local l10nDef = {}
l10nDef[ "en" ] = {
dupOpt = "TemplatePar#invoke: repeated optional parameter",
empty = "Error in template: undefined value for mandatory",
invalid = "Error in template: invalid parameter",
invalidPar = "TemplatePar#invoke: invalid parameter",
noname = "TemplatePar#invoke: missing parameter name",
tooLong = "Error in template: parameter too long",
tooShort = "Error in template: parameter too short",
undefined = "Error in template: mandatory parameter missing",
unknown = "Error in template: unknown parameter name",
unknownRule = "TemplatePar#invoke: unknown rule"
}
l10nDef[ "de" ] = {
dupOpt = "TemplatePar#invoke: Optionsparameter wiederholt",
empty = "Fehler bei Vorlage: Pflichtparameter ohne Wert",
invalid = "Fehler bei Vorlage: Parameter ungültig",
invalidPar = "TemplatePar#invoke: Ungültiger Parameter",
noname = "TemplatePar#invoke: Parametername nicht angegeben",
tooLong = "Fehler bei Vorlage: Parameter zu lang",
tooShort = "Fehler bei Vorlage: Parameter zu kurz",
undefined = "Fehler bei Vorlage: Pflichtparameter fehlt",
unknown = "Fehler bei Vorlage: Parametername unbekannt",
unknownRule = "TemplatePar#invoke: Unbekannte Regel"
}
local Patterns = {
[ "ASCII" ] = "^[ -~]*$",
[ "ASCII+" ] = "^[ -~]+$",
[ "ASCII+1" ] = "^[!-~]+$",
[ "n" ] = "^%-?[0-9]*$", -- einzelnes Minus ausschließen
[ "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]+$",
[ "+" ] = "%S"
}
function trim( s )
-- Trim string
-- Precondition:
-- s -- string or nil; to be trimmed
-- Postcondition:
-- Return trimmed string or nil
local r = s
if type( s ) == "string" then
if s:match( "^%s*$" ) then
r = ""
else
r = s:match( "^%s*(%S.*)$" )
r = r:match( "^(.*%S)%s*$" )
end
end
return r
end -- trim()
local function factory( say )
-- Retrieve localized message string in content language
-- Precondition:
-- say -- string; message ID
-- Postcondition:
-- Return some message string
-- Uses:
-- > messagePrefix
-- > l10nDef
local c = mw.language.getContentLanguage():getCode()
local m = mw.message.new( messagePrefix .. say )
local r = false
if m:isBlank() then
local l10n = l10nDef[ c ]
if not l10n then
l10n = l10nDef[ "en" ]
end
r = l10n[ say ]
else
m:inLanguage( c )
r = m:plain()
end
if not r then
r = "(((".. say .. ")))"
end
return r
end -- factory()
local function failure( spec, suspect, options )
-- Submit localized error message
-- Precondition:
-- spec -- string; message ID
-- suspect -- string or nil; additional information
-- options -- table or nil; optional details
-- options.template
-- Postcondition:
-- Return string
-- Uses:
-- factory()
local r = factory( spec )
if type( options ) == "table" then
if type( options.template ) == "string" then
if #options.template > 0 then
r = r .. " (" .. options.template .. ")"
end
end
end
if suspect then
r = r .. " " .. suspect
end
return r
end -- failure()
local function fault( store, key )
-- Add key to collection string and insert separator
-- Precondition:
-- store -- string or nil or false; collection string
-- key -- string or number; to be appended
-- Postcondition:
-- Return string; extended
local r
local s
if type( key ) == "number" then
s = tostring( key )
else
s = key
end
if store then
r = store .. "; " .. s
else
r = s
end
return r
end -- fault()
local function fed( haystack, needle )
-- Find needle in haystack map
-- Precondition:
-- haystack -- table; map of key values
-- needle -- any; identifier
-- Postcondition:
-- Return true iff found
local k, v
for k, v in pairs( haystack ) do
if k == needle then
return true
end
end -- for k, v
return false
end -- fed()
local function fetch()
-- Return regular table with all template transclusion parameters
-- Postcondition:
-- Return table; whitespace-only values as false
-- Uses:
-- mw.getCurrentFrame()
-- frame:getParent()
local k, v
local r = { }
local t = mw.getCurrentFrame():getParent()
local o = t.args
for k, v in pairs( o ) do
if type( v ) == "string" then
if v:match( "^%s*$" ) then
v = false
end
else
v = false
end
if type( k ) == "number" then
k = tostring( k )
end
r[ k ] = v
end -- for k, v
return r
end -- fetch()
local function fill( specified )
-- Split requirement string separated by '='
-- Precondition:
-- specified -- string or nil; requested parameter set
-- Postcondition:
-- Return sequence table
local r
if specified then
local i, s
r = mw.text.split( specified, "%s*=%s*" )
for i = #r, 1, -1 do
s = r[ i ]
if #s == 0 then
table.remove( r, i )
end
end -- for i, -1
else
r = { }
end
return r
end -- fill()
local function finalize( submit, options )
-- Finalize message
-- Precondition:
-- submit -- string or false or nil; non-empty error message
-- options -- table or nil; optional details
-- options.noError
-- options.cat
-- options.template
-- Postcondition:
-- Return string or false
local r = false
if submit then
local opt, s
if type( options ) == "table" then
opt = options
else
opt = { }
end
if opt.noError then
r = false
else
r = "<span class='error'>" .. submit .. "</span>"
end
s = opt.cat
if type( s ) == "string" then
if not r then
r = ""
end
if s:find( "@@@" ) then
if type( opt.template ) == "string" then
s = s:gsub( "@@@", opt.template )
end
end
r = r .. "[[Category:" .. s .. "]]"
end
end
return r
end -- finalize()
local function finder( haystack, needle )
-- Find needle in haystack sequence
-- Precondition:
-- haystack -- table; sequence of key names
-- needle -- any; key name
-- Postcondition:
-- Return true iff found
local i
for i = 1, #haystack do
if haystack[ i ] == needle then
return true
end
end -- for i
return false
end -- finder()
local function fit( base, extend )
-- Merge two tables, create new sequence if both not empty
-- Precondition:
-- base -- table; sequence kept unchanged
-- extend -- table; sequence to be appended
-- Postcondition:
-- Return merged table, or message string if duplicated entries
-- Uses:
-- finder()
-- fault()
local r
if #base == 0 then
if #extend == 0 then
r = { }
else
r = extend
end
else
if #extend == 0 then
r = base
else
local i, s
r = false
for i = 1, #extend do
s = extend[ i ]
if finder( base, s ) then
r = fault( r, s )
end
end -- for i
if not r then
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
return r
end -- fit()
local function fix( valid, duty, options )
-- Perform transclusion parameter analysis
-- Precondition:
-- valid -- table; unique sequence of known parameters
-- duty -- table; sequence of mandatory parameters
-- options -- table or nil; optional details
-- Postcondition:
-- Return string as configured; empty if valid
-- Uses:
-- fetch()
-- finder()
-- fault()
-- failure()
-- fed()
local k, v
local r = false
local got = fetch()
for k, v in pairs( got ) do
if not finder( valid, k ) then
r = fault( r, k )
end
end -- for k, v
if r then
r = failure( "unknown", r, options )
else -- all names valid
local i, s
for i = 1, #duty do
s = duty[ i ]
if not fed( got, s ) then
r = fault( r, s )
end
end -- for i
if r then
r = failure( "undefined", r, options )
else -- all mandatory present
for i = 1, #duty do
s = duty[ i ]
if not got[ s ] then
r = fault( r, s )
end
end -- for i
if r then
r = failure( "empty", r, options )
end
end
end
return r
end -- fix()
local function format( seek, options )
-- Check validity of one particular template parameter
-- Precondition:
-- seek -- string non-empty; name of template parameter
-- options -- table or nil; optional details
-- options.pattern
-- options.key
-- options.min
-- options.max
-- Postcondition:
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- Uses:
-- > Patterns
-- failure()
-- mw.ustring.match()
-- frame:getParent()
local r = false
local s
local scan = false
local story = mw.getCurrentFrame():getParent()
story = ( story.args[ seek ] or "" )
if type( options.pattern ) == "string" then
if options.key then
r = failure( "duplicatedRule", false, options )
else
scan = options.pattern
end
else
if type( options.key ) == "string" then
s = trim( options.key )
else
s = "+"
end
scan = Patterns[ s ]
if type( scan ) == "string" then
if s == "n" then
if trim( story ) == "-" then
scan = false
r = failure( "invalid",
"'" .. seek .. "'",
options )
end
end
else
r = failure( "unknownRule", s, options )
end
end
if scan then
if not mw.ustring.match( story, scan ) then
r = failure( "invalid", "'" .. seek .. "'", options )
end
end
if options.min and not r then
if type( options.min ) == "number" then
if #story < options.min then
r = failure( "tooShort",
" <" .. options.min .. " '" .. seek .. "'",
options )
end
else
r = failure( "invalidPar", "min", options )
end
end
if options.max and not r then
if type( options.max ) == "number" then
if #story > options.max then
r = failure( "tooLong",
" >" .. options.max .. " '" .. seek .. "'",
options )
end
else
r = failure( "invalidPar", "max", options )
end
end
return r
end -- format()
TemplatePar.check = function ( options )
-- Run parameter analysis
-- Precondition:
-- options -- table or nil; optional details
-- options.mandatory
-- options.optional
-- Postcondition:
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- Uses:
-- fit()
-- failure()
-- fix()
-- finalize()
local duty, r
if type( options ) == "table" then
if type( options.mandatory ) == "table" then
duty = options.mandatory
else
duty = { }
end
if type( options.optional ) ~= "table" then
options.optional = { }
end
r = fit( duty, options.optional )
else
duty = { }
r = { }
end
if type( r ) == "string" then
r = failure( "dupOpt", r, options )
else
r = fix( r, duty, options )
end
return finalize( r, options )
end -- TemplatePar.check()
TemplatePar.count = function ()
-- Return number of template parameters
-- Postcondition:
-- Return number, starting at 0
-- Uses:
-- mw.getCurrentFrame()
-- frame:getParent()
local k, v
local r = 0
local t = mw.getCurrentFrame():getParent()
local o = t.args
for k, v in pairs( o ) do
r = r + 1
end -- for k, v
return r
end -- TemplatePar.count()
TemplatePar.valid = function ( seek, options )
-- Check validity of one particular template parameter
-- Precondition:
-- seek -- string; name of template parameter
-- options -- table or nil; optional details
-- Postcondition:
-- Return string with error message as configured;
-- false if valid or no answer permitted
-- Uses:
-- trim()
-- format()
-- failure()
-- finalize()
local r
if type( seek ) == "string" then
r = trim( seek )
if #r == 0 then
r = false
end
end
if r then
r = format( seek, options )
else
r = failure( "noname", false, options )
end
return finalize( r, options )
end -- TemplatePar.valid()
-- Provide external access
local p = {}
function p.check( frame )
-- Check validity of template parameters
-- Precondition:
-- frame -- object; #invoke environment
-- Postcondition:
-- Return string with error message or ""
-- Uses:
-- fill()
-- TemplatePar.check()
local options = { mandatory = fill( frame.args[ 1 ] ),
optional = fill( frame.args[ 2 ] ),
cat = frame.args.cat,
noError = frame.args.noError,
template = frame.args.template
}
return TemplatePar.check( options ) or ""
end -- .check()
function p.count( frame )
-- Count number of template parameters
-- Postcondition:
-- Return string with digits including "0"
-- Uses:
-- TemplatePar.count()
return tostring( TemplatePar.count() )
end -- .count()
function p.valid( frame )
-- Check validity of one particular template parameter
-- Precondition:
-- frame -- object; #invoke environment
-- Postcondition:
-- Return string with error message or ""
-- Uses:
-- trim()
-- TemplatePar.valid()
local r = false
local s
local options = { cat = frame.args.cat,
noError = frame.args.noError,
template = frame.args.template
}
s = trim( frame.args[ 2 ] )
if type( s ) == "string" then
local sub = s:match( "^/(.*%S)/$" )
if type( sub ) == "string" then
options.pattern = sub
else
options.key = s
end
end
if type( frame.args.min ) == "string" then
s = frame.args.min:match( "^%s*([0-9]+)%s*$" )
if s then
options.min = tonumber( s )
else
r = failure( "invalidPar",
"min=" .. frame.args.min,
options )
end
end
if type( frame.args.max ) == "string" then
s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )
if s then
options.max = tonumber( s )
else
r = failure( "invalidPar",
"max=" .. frame.args.max,
options )
end
end
if r then
r = finalize( r, options )
else
s = frame.args[ 1 ] or ""
r = TemplatePar.valid( s, options ) or ""
end
return r
end -- .valid()
function p.TemplatePar()
-- Retrieve function access for modules
-- Postcondition:
-- Return table with functions
return TemplatePar
end -- .TemplatePar()
return p