imported>Odyssee |
imported>Joachim Stiller |
Zeile 1: |
Zeile 1: |
| --[=[ TemplatePar 2015-02-14
| | == Lizenz == |
| Template parameter utility
| | {{Bild-PD-Schöpfungshöhe}} |
| * assert
| |
| * check
| |
| * count
| |
| * countNotEmpty
| |
| * downcase()
| |
| * match
| |
| * valid
| |
| * verify()
| |
| * TemplatePar()
| |
| ]=]
| |
| | |
| | |
| | |
| -- Module globals
| |
| local TemplatePar = { }
| |
| local MessagePrefix = "lua-module-TemplatePar-"
| |
| local L10nDef = {}
| |
| L10nDef.en = {
| |
| badPattern = "#invoke:TemplatePar pattern syntax error",
| |
| dupOpt = "#invoke:TemplatePar repeated optional parameter",
| |
| dupRule = "#invoke:TemplatePar 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( s )
| |
| -- Is any CJK character present?
| |
| -- Precondition:
| |
| -- s -- string
| |
| -- Postcondition:
| |
| -- Return false iff no CJK present
| |
| -- Uses:
| |
| -- >< patternCJK
| |
| -- mw.ustring.char()
| |
| -- mw.ustring.match()
| |
| local r = false
| |
| if not patternCJK then
| |
| patternCJK = mw.ustring.char( 91,
| |
| 13312, 45, 40959,
| |
| 131072, 45, 178207,
| |
| 93 )
| |
| end
| |
| if mw.ustring.match( s, patternCJK ) then
| |
| r = true
| |
| end
| |
| return r
| |
| end -- containsCJK()
| |
| | |
| | |
| | |
| local function 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 ]
| |
| if not l10n then
| |
| l10n = L10nDef[ "en" ]
| |
| end
| |
| r = l10n[ say ]
| |
| else
| |
| m:inLanguage( c )
| |
| r = m:plain()
| |
| end
| |
| if not r then
| |
| r = string.format( "(((%s)))", say )
| |
| end
| |
| return r
| |
| end -- factory()
| |
| | |
| | |
| | |
| local function failsafe( story, scan )
| |
| -- Test for match (possibly user-defined with syntax error)
| |
| -- Precondition:
| |
| -- story -- string; parameter value
| |
| -- scan -- string; pattern
| |
| -- Postcondition:
| |
| -- Return nil, if not matching, else non-nil
| |
| -- Uses:
| |
| -- mw.ustring.match()
| |
| return mw.ustring.match( story, scan )
| |
| end -- failsafe()
| |
| | |
| | |
| | |
| local function failure( spec, suspect, options )
| |
| -- Submit localized error message
| |
| -- Precondition:
| |
| -- spec -- string; message ID
| |
| -- suspect -- string or nil; additional information
| |
| -- options -- table or nil; optional details
| |
| -- options.template
| |
| -- Postcondition:
| |
| -- Return string
| |
| -- Uses:
| |
| -- factory()
| |
| local r = factory( spec )
| |
| if type( options ) == "table" then
| |
| if type( options.template ) == "string" then
| |
| if #options.template > 0 then
| |
| r = 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
| |
| if not frame then
| |
| frame = mw.getCurrentFrame()
| |
| end
| |
| 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
| |
| if not r then
| |
| r = ""
| |
| end
| |
| 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( "#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 = "#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
| |