Modul:TemplatePar: Unterschied zwischen den Versionen
te>PerfektesChaos (+pagename) |
te>PerfektesChaos (updates + match) |
||
Zeile 1: | Zeile 1: | ||
− | --[=[ TemplatePar 2013- | + | --[=[ TemplatePar 2013-07-09 |
Template parameter utility | Template parameter utility | ||
* assert | * assert | ||
Zeile 6: | Zeile 6: | ||
* countNotEmpty | * countNotEmpty | ||
* downcase | * downcase | ||
+ | * match | ||
* valid | * valid | ||
* verify | * verify | ||
Zeile 18: | Zeile 19: | ||
local l10nDef = {} | local l10nDef = {} | ||
l10nDef[ "en" ] = { | l10nDef[ "en" ] = { | ||
− | badPattern = "#invoke:TemplatePar | + | badPattern = "#invoke:TemplatePar pattern syntax error", |
− | dupOpt = "#invoke:TemplatePar | + | dupOpt = "#invoke:TemplatePar repeated optional parameter", |
− | dupRule = "#invoke:TemplatePar | + | 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 | + | invalidPar = "#invoke:TemplatePar invalid parameter", |
− | minmax = "#invoke:TemplatePar | + | minmax = "#invoke:TemplatePar min > max", |
multiSpell = "Error in template * multiple spelling of parameter", | multiSpell = "Error in template * multiple spelling of parameter", | ||
− | noErrorCat = "#invoke:TemplatePar | + | noErrorCat = "#invoke:TemplatePar noError and missing category", |
− | noname = "#invoke:TemplatePar | + | noname = "#invoke:TemplatePar missing parameter name", |
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 | + | unknownRule = "#invoke:TemplatePar unknown rule" |
} | } | ||
l10nDef[ "de" ] = { | l10nDef[ "de" ] = { | ||
− | badPattern = "#invoke:TemplatePar | + | badPattern = "#invoke:TemplatePar Syntaxfehler des pattern", |
− | dupOpt = "#invoke:TemplatePar | + | dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt", |
− | dupRule = "#invoke:TemplatePar | + | 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 | + | invalidPar = "#invoke:TemplatePar Ungültiger Parameter", |
− | minmax = "#invoke:TemplatePar | + | minmax = "#invoke:TemplatePar min > max", |
multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen", | multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen", | ||
− | noErrorCat = "#invoke:TemplatePar | + | noErrorCat = "#invoke:TemplatePar noError und keine Kategorie", |
− | noname = "#invoke:TemplatePar | + | noname = "#invoke:TemplatePar Parameter nicht angegeben", |
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 | + | unknownRule = "#invoke:TemplatePar Unbekannte Regel" |
} | } | ||
local Patterns = { | local Patterns = { | ||
Zeile 209: | Zeile 210: | ||
return r | return r | ||
end -- fault() | end -- fault() | ||
+ | |||
+ | |||
+ | |||
+ | local function feasible( analyze, options, abbr ) | ||
+ | -- Check content of a value | ||
+ | -- Precondition: | ||
+ | -- analyze -- string to be analyzed | ||
+ | -- options -- table or nil; optional details | ||
+ | -- options.pattern | ||
+ | -- options.key | ||
+ | -- options.say | ||
+ | -- abbr -- true: abbreviated error message | ||
+ | -- Postcondition: | ||
+ | -- Return string with error message as configured; | ||
+ | -- false if valid or no answer permitted | ||
+ | -- Uses: | ||
+ | -- > Patterns | ||
+ | -- failure() | ||
+ | -- mw.text.trim() | ||
+ | -- failsafe() | ||
+ | -- containsCJK() | ||
+ | local r = false | ||
+ | local s = false | ||
+ | local show = nil | ||
+ | local scan = false | ||
+ | if type( options.pattern ) == "string" then | ||
+ | if options.key then | ||
+ | r = failure( "dupRule", false, options ) | ||
+ | else | ||
+ | scan = options.pattern | ||
+ | end | ||
+ | else | ||
+ | if type( options.key ) == "string" then | ||
+ | s = mw.text.trim( options.key ) | ||
+ | else | ||
+ | s = "+" | ||
+ | end | ||
+ | if s ~= "*" then | ||
+ | scan = Patterns[ s ] | ||
+ | end | ||
+ | if type( scan ) == "string" then | ||
+ | if s == "n" or s == "0,0" or s == "0.0" then | ||
+ | if not analyze:match( "[0-9]" ) then | ||
+ | scan = false | ||
+ | if options.say then | ||
+ | show = "'" .. options.say .. "'" | ||
+ | end | ||
+ | if abbr then | ||
+ | r = show | ||
+ | else | ||
+ | r = failure( "invalid", show, options ) | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | elseif s ~= "*" then | ||
+ | local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" ) | ||
+ | if op then | ||
+ | n = tonumber( n ) | ||
+ | if n then | ||
+ | local i = tonumber( analyze ) | ||
+ | if i then | ||
+ | if op == "<" then | ||
+ | i = ( i < n ) | ||
+ | elseif op == "<=" then | ||
+ | i = ( i <= n ) | ||
+ | elseif op == ">" then | ||
+ | i = ( i > n ) | ||
+ | elseif op == ">=" then | ||
+ | i = ( i >= n ) | ||
+ | elseif op == "!=" then | ||
+ | i = ( i ~= n ) | ||
+ | else | ||
+ | n = false | ||
+ | end | ||
+ | end | ||
+ | if not i then | ||
+ | r = "invalid" | ||
+ | end | ||
+ | elseif plus then | ||
+ | r = "undefined" | ||
+ | end | ||
+ | end | ||
+ | if not n and not r then | ||
+ | r = "unknownRule" | ||
+ | end | ||
+ | if r then | ||
+ | if options.say then | ||
+ | show = "'" .. options.say .. "' " .. s | ||
+ | else | ||
+ | show = s | ||
+ | end | ||
+ | if abbr then | ||
+ | r = show | ||
+ | else | ||
+ | r = failure( r, show, options ) | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | if scan then | ||
+ | local legal, got = pcall( failsafe, analyze, scan ) | ||
+ | if legal then | ||
+ | if not got then | ||
+ | if s == "aa" then | ||
+ | got = containsCJK( analyze ) | ||
+ | end | ||
+ | if not got then | ||
+ | if options.say then | ||
+ | show = "'" .. options.say .. "'" | ||
+ | end | ||
+ | if abbr then | ||
+ | r = show | ||
+ | else | ||
+ | r = failure( "invalid", show, options ) | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | else | ||
+ | r = failure( "badPattern", | ||
+ | scan .. " *** " .. got, | ||
+ | options ) | ||
+ | end | ||
+ | end | ||
+ | return r | ||
+ | end -- feasible() | ||
Zeile 273: | Zeile 399: | ||
return r | return r | ||
end -- fetch() | 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() | ||
Zeile 390: | Zeile 545: | ||
end -- for k, v | end -- for k, v | ||
if r then | if r then | ||
− | r = failure( "unknown", r, options ) | + | r = failure( "unknown", "'" .. r .. "'", options ) |
else -- all names valid | else -- all names valid | ||
local i, s | local i, s | ||
Zeile 516: | Zeile 671: | ||
-- Uses: | -- Uses: | ||
-- fold() | -- fold() | ||
− | |||
-- fetch() | -- fetch() | ||
-- fix() | -- fix() | ||
Zeile 553: | Zeile 707: | ||
-- 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 | ||
Zeile 562: | Zeile 714: | ||
-- false if valid or no answer permitted | -- false if valid or no answer permitted | ||
-- Uses: | -- Uses: | ||
− | -- | + | -- feasible() |
-- failure() | -- failure() | ||
− | + | local r = feasible( analyze, options, false ) | |
− | + | local show | |
− | |||
− | local r = | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
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 | ||
Zeile 668: | Zeile 768: | ||
-- false if valid or no answer permitted | -- false if valid or no answer permitted | ||
-- Uses: | -- Uses: | ||
− | |||
− | |||
− | |||
-- format() | -- format() | ||
-- failure() | -- failure() | ||
Zeile 688: | Zeile 785: | ||
return r | return r | ||
end -- formatted() | end -- formatted() | ||
− | |||
Zeile 702: | Zeile 798: | ||
-- form() | -- form() | ||
-- mw.text.trim() | -- mw.text.trim() | ||
+ | -- failure() | ||
-- TemplatePar.assert() | -- TemplatePar.assert() | ||
-- TemplatePar.valid() | -- TemplatePar.valid() | ||
Zeile 712: | Zeile 809: | ||
"noError", | "noError", | ||
"template" }, | "template" }, | ||
− | template = "#invoke:TemplatePar|".. action .. "|" | + | template = "#invoke:TemplatePar|".. action .. "|" |
} | } | ||
local r = form( false, options ) | local r = form( false, options ) | ||
if not r then | if not r then | ||
− | local s | + | local s |
options = { cat = frame.args.cat, | options = { cat = frame.args.cat, | ||
low = frame.args.low, | low = frame.args.low, | ||
Zeile 722: | Zeile 819: | ||
template = frame.args.template | template = frame.args.template | ||
} | } | ||
− | + | 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*$" ) | ||
Zeile 861: | Zeile 948: | ||
-- mw.getCurrentFrame() | -- mw.getCurrentFrame() | ||
-- frame:getParent() | -- frame:getParent() | ||
− | -- | + | -- flat() |
− | |||
− | |||
local t = mw.getCurrentFrame():getParent() | local t = mw.getCurrentFrame():getParent() | ||
return flat( t.args, options ) | return flat( t.args, options ) | ||
Zeile 959: | Zeile 1.044: | ||
"noError", | "noError", | ||
"template" }, | "template" }, | ||
− | template = "#invoke:TemplatePar|check|" | + | template = "#invoke:TemplatePar|check|" |
} | } | ||
local r = form( false, options ) | local r = form( false, options ) | ||
Zeile 996: | Zeile 1.081: | ||
return tostring( TemplatePar.countNotEmpty() ) | return tostring( TemplatePar.countNotEmpty() ) | ||
end -- .countNotEmpty() | end -- .countNotEmpty() | ||
+ | |||
+ | |||
+ | |||
+ | function p.match( frame ) | ||
+ | -- Combined analysis of parameters and their values | ||
+ | -- Postcondition: | ||
+ | -- Return string with error message or "" | ||
+ | -- Uses: | ||
+ | -- mw.text.trim() | ||
+ | -- mw.ustring.lower() | ||
+ | -- failure() | ||
+ | -- form() | ||
+ | -- TemplatePar.downcase() | ||
+ | -- figure() | ||
+ | -- feasible() | ||
+ | -- fault() | ||
+ | -- finalize() | ||
+ | local r = false | ||
+ | local options = { cat = frame.args.cat, | ||
+ | low = frame.args.low, | ||
+ | noError = frame.args.noError, | ||
+ | template = frame.args.template | ||
+ | } | ||
+ | local k, v, s | ||
+ | local params = { } | ||
+ | for k, v in pairs( frame.args ) do | ||
+ | if type( k ) == "number" then | ||
+ | s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" ) | ||
+ | if s then | ||
+ | s = mw.text.trim( s ) | ||
+ | if s == "" then | ||
+ | s = false | ||
+ | end | ||
+ | end | ||
+ | if s then | ||
+ | if options.low then | ||
+ | s = mw.ustring.lower( s ) | ||
+ | end | ||
+ | if params[ s ] then | ||
+ | s = params[ s ] | ||
+ | s[ #s + 1 ] = v | ||
+ | else | ||
+ | params[ s ] = { v } | ||
+ | end | ||
+ | else | ||
+ | r = failure( "invalidPar", tostring( k ), options ) | ||
+ | break -- for k, v | ||
+ | end | ||
+ | end | ||
+ | end -- for k, v | ||
+ | if not r then | ||
+ | s = { } | ||
+ | for k, v in pairs( params ) do | ||
+ | s[ #s + 1 ] = k | ||
+ | end -- for k, v | ||
+ | options.optional = s | ||
+ | r = form( true, options ) | ||
+ | end | ||
+ | if not r then | ||
+ | local errMiss, errValues, lack, rule | ||
+ | local targs = frame:getParent().args | ||
+ | options.optional = nil | ||
+ | if options.low then | ||
+ | targs = TemplatePar.downcase() | ||
+ | else | ||
+ | targs = frame:getParent().args | ||
+ | end | ||
+ | errMiss = false | ||
+ | errValues = false | ||
+ | for k, v in pairs( params ) do | ||
+ | options.say = k | ||
+ | errValue = false | ||
+ | s = targs[ k ] | ||
+ | if s then | ||
+ | if s == "" then | ||
+ | lack = true | ||
+ | else | ||
+ | lack = false | ||
+ | end | ||
+ | else | ||
+ | s = "" | ||
+ | lack = true | ||
+ | end | ||
+ | for r, rule in pairs( v ) do | ||
+ | options = figure( rule, options ) | ||
+ | r = feasible( s, options, true ) | ||
+ | if r then | ||
+ | if lack then | ||
+ | if errMiss then | ||
+ | errMiss = errMiss .. ", '" .. k .. "'" | ||
+ | else | ||
+ | errMiss = "'" .. k .. "'" | ||
+ | end | ||
+ | elseif not errMiss then | ||
+ | errValues = fault( errValues, r ) | ||
+ | end | ||
+ | break -- for r, rule | ||
+ | end | ||
+ | end -- for s, rule | ||
+ | end -- for k, v | ||
+ | r = ( errMiss or errValues ) | ||
+ | if r then | ||
+ | if errMiss then | ||
+ | r = failure( "undefined", errMiss, options ) | ||
+ | else | ||
+ | r = failure( "invalid", errValues, options ) | ||
+ | end | ||
+ | r = finalize( r, options ) | ||
+ | end | ||
+ | end | ||
+ | return r or "" | ||
+ | end -- .match() | ||
Version vom 10. Juli 2013, 08:42 Uhr
--[=[ TemplatePar 2013-07-09 Template parameter utility
- assert
- check
- count
- countNotEmpty
- downcase
- match
- valid
- verify
- TemplatePar()
]=]
-- Module globals local TemplatePar = { } local messagePrefix = "lua-module-TemplatePar-" local l10nDef = {} l10nDef[ "en" ] = {
badPattern = "#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", multiSpell = "Error in template * multiple spelling of parameter", noErrorCat = "#invoke:TemplatePar noError and missing category", noname = "#invoke:TemplatePar missing parameter name", tooLong = "Error in template * parameter too long", tooShort = "Error in template * parameter too short", undefined = "Error in template * mandatory parameter missing", unknown = "Error in template * unknown parameter name", unknownRule = "#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", noErrorCat = "#invoke:TemplatePar noError und keine Kategorie", noname = "#invoke:TemplatePar Parameter nicht angegeben", tooLong = "Fehler bei Vorlage * Parameter zu lang", tooShort = "Fehler bei Vorlage * Parameter zu kurz", undefined = "Fehler bei Vorlage * Pflichtparameter fehlt", unknown = "Fehler bei Vorlage * Parametername unbekannt", unknownRule = "#invoke:TemplatePar Unbekannte Regel"
} local Patterns = {
[ "ASCII" ] = "^[ -~]*$", [ "ASCII+" ] = "^[ -~]+$", [ "ASCII+1" ] = "^[!-~]+$", [ "n" ] = "^%-?[0-9]*$", [ "n>0" ] = "^[0-9]*[1-9][0-9]*$", [ "N+" ] = "^%-?[1-9][0-9]*$", [ "N>0" ] = "^[1-9][0-9]*$", [ "x" ] = "^[0-9A-Fa-f]*$", [ "x+" ] = "^[0-9A-Fa-f]+$", [ "X" ] = "^[0-9A-F]*$", [ "X+" ] = "^[0-9A-F]+$", [ "0,0" ] = "^%-?[0-9]*,?[0-9]*$", [ "0,0+" ] = "^%-?[0-9]+,[0-9]+$", [ "0,0+?" ] = "^%-?[0-9]+,?[0-9]*$", [ "0.0" ] = "^%-?[0-9]*%.?[0-9]*$", [ "0.0+" ] = "^%-?[0-9]+%.[0-9]+$", [ "0.0+?" ] = "^%-?[0-9]+%.?[0-9]*$", [ ".0+" ] = "^%-?[0-9]*%.?[0-9]+$", [ "ID" ] = "^[A-Za-z]?[A-Za-z_0-9]*$", [ "ID+" ] = "^[A-Za-z][A-Za-z_0-9]*$", [ "ABC" ] = "^[A-Z]*$", [ "ABC+" ] = "^[A-Z]+$", [ "Abc" ] = "^[A-Z]*[a-z]*$", [ "Abc+" ] = "^[A-Z][a-z]+$", [ "abc" ] = "^[a-z]*$", [ "abc+" ] = "^[a-z]+$", [ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$", [ "base64" ] = "^[A-Za-z0-9%+/]*$", [ "base64+" ] = "^[A-Za-z0-9%+/]+$", [ "aa" ] = "[%a%a].*[%a%a]", [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%s%-s%s]+$", 1, 31, 127 ), [ "+" ] = "%S"
} local patternCJK = false
local function containsCJK( s )
-- Is any CJK character present? -- Precondition: -- s -- string -- Postcondition: -- Return false iff no CJK present -- Uses: -- >< patternCJK -- mw.ustring.char() -- mw.ustring.match() local r = false if not patternCJK then patternCJK = mw.ustring.char( 91, 13312, 45, 40959, 131072, 45, 178207, 93 ) end if mw.ustring.match( s, patternCJK ) then r = true end return r
end -- containsCJK()
local function factory( say )
-- Retrieve localized message string in content language -- Precondition: -- say -- string; message ID -- Postcondition: -- Return some message string -- Uses: -- > messagePrefix -- > l10nDef -- mw.language.getContentLanguage() -- mw.message.new() local c = mw.language.getContentLanguage():getCode() local m = mw.message.new( messagePrefix .. say ) local r = false if m:isBlank() then local l10n = l10nDef[ c ] if not l10n then l10n = l10nDef[ "en" ] end r = l10n[ say ] else m:inLanguage( c ) r = m:plain() end if not r then r = "(((".. say .. ")))" end return r
end -- factory()
local function failsafe( story, scan )
-- Test for match (possibly user-defined with syntax error) -- Precondition: -- story -- string; parameter value -- scan -- string; pattern -- Postcondition: -- Return nil, if not matching, else non-nil -- Uses: -- mw.ustring.match() return mw.ustring.match( story, scan )
end -- failsafe()
local function failure( spec, suspect, options )
-- Submit localized error message -- Precondition: -- spec -- string; message ID -- suspect -- string or nil; additional information -- options -- table or nil; optional details -- options.template -- Postcondition: -- Return string -- Uses: -- factory() local r = factory( spec ) if type( options ) == "table" then if type( options.template ) == "string" then if #options.template > 0 then r = r .. " (" .. options.template .. ")" end end end if suspect then r = r .. ": " .. suspect end return r
end -- failure()
local function fault( store, key )
-- Add key to collection string and insert separator -- Precondition: -- store -- string or nil or false; collection string -- key -- string or number; to be appended -- Postcondition: -- Return string; extended local r local s if type( key ) == "number" then s = tostring( key ) else s = key end if store then r = store .. "; " .. s else r = s end return r
end -- fault()
local function feasible( analyze, options, abbr )
-- Check content of a value -- Precondition: -- analyze -- string to be analyzed -- options -- table or nil; optional details -- options.pattern -- options.key -- options.say -- abbr -- true: abbreviated error message -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- > Patterns -- failure() -- mw.text.trim() -- failsafe() -- containsCJK() local r = false local s = false local show = nil local scan = false if type( options.pattern ) == "string" then if options.key then r = failure( "dupRule", false, options ) else scan = options.pattern end else if type( options.key ) == "string" then s = mw.text.trim( options.key ) else s = "+" end if s ~= "*" then scan = Patterns[ s ] end if type( scan ) == "string" then if s == "n" or s == "0,0" or s == "0.0" then if not analyze:match( "[0-9]" ) then scan = false if options.say then show = "'" .. options.say .. "'" end if abbr then r = show else r = failure( "invalid", show, options ) end end end elseif s ~= "*" then local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" ) if op then n = tonumber( n ) if n then local i = tonumber( analyze ) if i then if op == "<" then i = ( i < n ) elseif op == "<=" then i = ( i <= n ) elseif op == ">" then i = ( i > n ) elseif op == ">=" then i = ( i >= n ) elseif op == "!=" then i = ( i ~= n ) else n = false end end if not i then r = "invalid" end elseif plus then r = "undefined" end end if not n and not r then r = "unknownRule" end if r then if options.say then show = "'" .. options.say .. "' " .. s else show = s end if abbr then r = show else r = failure( r, show, options ) end end end end if scan then local legal, got = pcall( failsafe, analyze, scan ) if legal then if not got then if s == "aa" then got = containsCJK( analyze ) end if not got then if options.say then show = "'" .. options.say .. "'" end if abbr then r = show else r = failure( "invalid", show, options ) end end end else r = failure( "badPattern", scan .. " *** " .. got, options ) end end return r
end -- feasible()
local function fed( haystack, needle )
-- Find needle in haystack map -- Precondition: -- haystack -- table; map of key values -- needle -- any; identifier -- Postcondition: -- Return true iff found local k, v for k, v in pairs( haystack ) do if k == needle then return true end end -- for k, v return false
end -- fed()
local function fetch( light, options )
-- Return regular table with all parameters -- Precondition: -- light -- true: template transclusion; false: #invoke -- options -- table; optional details -- options.low -- Postcondition: -- Return table; whitespace-only values as false -- Uses: -- TemplatePar.downcase() -- mw.getCurrentFrame() -- frame:getParent() local g, k, v local r = { } if options.low then g = TemplatePar.downcase( options ) else g = mw.getCurrentFrame() if light then g = g:getParent() end g = g.args end if type( g ) == "table" then r = { } for k, v in pairs( g ) do if type( v ) == "string" then if v:match( "^%s*$" ) then v = false end else v = false end if type( k ) == "number" then k = tostring( k ) end r[ k ] = v end -- for k, v else r = g end return r
end -- fetch()
local function figure( append, options )
-- Extend options by rule from #invoke strings -- Precondition: -- append -- string or nil; requested rule -- options -- table; details -- ++ .key -- ++ .pattern -- Postcondition: -- Return sequence table local r = options if type( append ) == "string" then local story = mw.text.trim( append ) local sub = story:match( "^/(.*%S)/$" ) if type( sub ) == "string" then sub = sub:gsub( "%%!", "|" ) sub = sub:gsub( "%%%(%(", "{{" ) sub = sub:gsub( "%%%)%)", "}}" ) options.pattern = sub options.key = nil else options.key = story options.pattern = nil end end return r
end -- figure()
local function fill( specified )
-- Split requirement string separated by '=' -- Precondition: -- specified -- string or nil; requested parameter set -- Postcondition: -- Return sequence table -- Uses: -- mw.text.split() local r if specified then local i, s r = mw.text.split( specified, "%s*=%s*" ) for i = #r, 1, -1 do s = r[ i ] if #s == 0 then table.remove( r, i ) end end -- for i, -1 else r = { } end return r
end -- fill()
local function finalize( submit, options )
-- Finalize message
-- Precondition:
-- submit -- string or false or nil; non-empty error message
-- options -- table or nil; optional details
-- options.noError
-- options.cat
-- options.template
-- Postcondition:
-- Return string or false
-- Uses:
-- factory()
local r = false
if submit then
local opt, s
if type( options ) == "table" then
opt = options
else
opt = { }
end
if opt.noError then
if not opt.cat then
r = submit .. " " .. factory( "noErrorCat" )
end
else
r = submit
end
if r then
r = "" .. r .. ""
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 .. ""
end
end
return r
end -- finalize()
local function finder( haystack, needle )
-- Find needle in haystack sequence -- Precondition: -- haystack -- table; sequence of key names, downcased if low -- needle -- any; key name -- Postcondition: -- Return true iff found local i for i = 1, #haystack do if haystack[ i ] == needle then return true end end -- for i return false
end -- finder()
local function fix( valid, duty, got, options )
-- Perform parameter analysis -- Precondition: -- valid -- table; unique sequence of known parameters -- duty -- table; sequence of mandatory parameters -- got -- table; sequence of current parameters -- options -- table or nil; optional details -- Postcondition: -- Return string as configured; empty if valid -- Uses: -- finder() -- fault() -- failure() -- fed() local k, v local r = false for k, v in pairs( got ) do if not finder( valid, k ) then r = fault( r, k ) end end -- for k, v if r then r = failure( "unknown", "'" .. r .. "'", options ) else -- all names valid local i, s for i = 1, #duty do s = duty[ i ] if not fed( got, s ) then r = fault( r, s ) end end -- for i if r then r = failure( "undefined", r, options ) else -- all mandatory present for i = 1, #duty do s = duty[ i ] if not got[ s ] then r = fault( r, s ) end end -- for i if r then r = failure( "empty", r, options ) end end end return r
end -- fix()
local function flat( collection, options )
-- Return all table elements with downcased string -- Precondition: -- collection -- table; k=v pairs -- options -- table or nil; optional messaging details -- Postcondition: -- Return table, may be empty; or string with error message. -- Uses: -- mw.ustring.lower() -- fault() -- failure() local k, v local r = { } local e = false for k, v in pairs( collection ) do if type ( k ) == "string" then k = mw.ustring.lower( k ) if r[ k ] then e = fault( e, k ) end end r[ k ] = v end -- for k, v if e then r = failure( "multiSpell", e, options ) end return r
end -- flat()
local function fold( options )
-- Merge two tables, create new sequence if both not empty -- Precondition: -- options -- table; details -- options.mandatory sequence to keep unchanged -- options.optional sequence to be appended -- options.low downcased expected -- Postcondition: -- Return merged table, or message string if error -- Uses: -- finder() -- fault() -- failure() -- flat() local i, e, r, s local base = options.mandatory local extend = options.optional if #base == 0 then if #extend == 0 then r = { } else r = extend end else if #extend == 0 then r = base else e = false for i = 1, #extend do s = extend[ i ] if finder( base, s ) then e = fault( e, s ) end end -- for i if e then r = failure( "dupOpt", e, options ) else r = { } for i = 1, #base do table.insert( r, base[ i ] ) end -- for i for i = 1, #extend do table.insert( r, extend[ i ] ) end -- for i end end end if options.low and type( r ) == "table" then r = flat( r, options ) end return r
end -- fold()
local function form( light, options )
-- Run parameter analysis on current environment -- Precondition: -- light -- true: template transclusion; false: #invoke -- options -- table or nil; optional details -- options.mandatory -- options.optional -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- fold() -- fetch() -- fix() -- finalize() local duty, r if type( options ) == "table" then if type( options.mandatory ) ~= "table" then options.mandatory = { } end duty = options.mandatory if type( options.optional ) ~= "table" then options.optional = { } end r = fold( options ) else options = { } duty = { } r = { } end if type( r ) == "table" then local got = fetch( light, options ) if type( got ) == "table" then r = fix( r, duty, got, options ) else r = got end end return finalize( r, options )
end -- form()
local function format( analyze, options )
-- Check validity of a value -- Precondition: -- analyze -- string to be analyzed -- options -- table or nil; optional details -- options.say -- options.min -- options.max -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- feasible() -- failure() local r = feasible( analyze, options, false ) local show if options.min and not r then if type( options.min ) == "number" then if type( options.max ) == "number" then if options.max < options.min then r = failure( "minmax", tostring( options.min ) .. " > " .. tostring( options.max ), options ) end end if #analyze < options.min and not r then show = " <" .. options.min if options.say then show = show .. " '" .. options.say .. "'" end r = failure( "tooShort", show, options ) end else r = failure( "invalidPar", "min", options ) end end if options.max and not r then if type( options.max ) == "number" then if #analyze > options.max then show = " >" .. options.max if options.say then show = show .. " '" .. options.say .. "'" end r = failure( "tooLong", show, options ) end else r = failure( "invalidPar", "max", options ) end end return r
end -- format()
local function formatted( assignment, access, options )
-- Check validity of one particular parameter in a collection -- Precondition: -- assignment -- collection -- access -- id of parameter in collection -- options -- table or nil; optional details -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- format() -- failure() local r = false if type( assignment ) == "table" then local story = assignment.args[ access ] if type( story ) == "string" then if type( options ) ~= "table" then options = { } end options.say = access r = format( story, options ) else r = failure( "invalid", access, options ) end end return r
end -- formatted()
local function furnish( frame, action )
-- Prepare #invoke evaluation of .assert() or .valid() -- Precondition: -- frame -- object; #invoke environment -- action -- "assert" or "valid" -- Postcondition: -- Return string with error message or "" -- Uses: -- form() -- mw.text.trim() -- failure() -- TemplatePar.assert() -- TemplatePar.valid() local options = { mandatory = { "1" }, optional = { "2", "cat", "low", "max", "min", "noError", "template" }, template = "#invoke:TemplatePar|".. action .. "|" } local r = form( false, options ) if not r then local s options = { cat = frame.args.cat, low = frame.args.low, noError = frame.args.noError, template = frame.args.template } options = figure( frame.args[ 2 ], options ) if type( frame.args.min ) == "string" then s = frame.args.min:match( "^%s*([0-9]+)%s*$" ) if s then options.min = tonumber( s ) else r = failure( "invalidPar", "min=" .. frame.args.min, options ) end end if type( frame.args.max ) == "string" then s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" ) if s then options.max = tonumber( s ) else r = failure( "invalidPar", "max=" .. frame.args.max, options ) end end if r then r = finalize( r, options ) else s = frame.args[ 1 ] or "" r = tonumber( s ) if ( r ) then s = r end r = TemplatePar[ action ]( s, options ) end end return r or ""
end -- furnish()
TemplatePar.assert = function ( analyze, append, options )
-- Perform parameter analysis on a single string -- Precondition: -- analyze -- string to be analyzed -- append -- string: append error message, prepending
-- false or nil: throw error with message -- options -- table; optional details -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- format() local r = format( analyze, options ) if ( r ) then if ( type( append ) == "string" ) then if ( append ~= "" ) then r = append .. "
" .. r end else error( r, 0 ) end end return r
end -- TemplatePar.assert()
TemplatePar.check = function ( options )
-- Run parameter analysis on current template environment -- Precondition: -- options -- table or nil; optional details -- options.mandatory -- options.optional -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- form() return form( true, options )
end -- TemplatePar.check()
TemplatePar.count = function ()
-- Return number of template parameters -- Postcondition: -- Return number, starting at 0 -- Uses: -- mw.getCurrentFrame() -- frame:getParent() local k, v local r = 0 local t = mw.getCurrentFrame():getParent() local o = t.args for k, v in pairs( o ) do r = r + 1 end -- for k, v return r
end -- TemplatePar.count()
TemplatePar.countNotEmpty = function ()
-- Return number of template parameters with more than whitespace -- Postcondition: -- Return number, starting at 0 -- Uses: -- mw.getCurrentFrame() -- frame:getParent() local k, v local r = 0 local t = mw.getCurrentFrame():getParent() local o = t.args for k, v in pairs( o ) do if not v:match( "^%s*$" ) then r = r + 1 end end -- for k, v return r
end -- TemplatePar.countNotEmpty()
TemplatePar.downcase = function ( options )
-- Return all template parameters with downcased name -- Precondition: -- options -- table or nil; optional messaging details -- Postcondition: -- Return table, may be empty; or string with error message. -- Uses: -- mw.getCurrentFrame() -- frame:getParent() -- flat() local t = mw.getCurrentFrame():getParent() return flat( t.args, options )
end -- TemplatePar.downcase()
TemplatePar.valid = function ( access, options )
-- Check validity of one particular template parameter -- Precondition: -- access -- id of parameter in template transclusion -- options -- table or nil; optional details -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- mw.text.trim() -- TemplatePar.downcase() -- mw.getCurrentFrame() -- frame:getParent() -- formatted() -- failure() -- finalize() local r if type( access ) == "string" then r = mw.text.trim( access ) if #r == 0 then r = false end end if r then local params if type( options ) ~= "table" then options = { } end if options.low then params = TemplatePar.downcase( options ) else params = mw.getCurrentFrame():getParent() end r = formatted( params, access, options ) else r = failure( "noname", false, options ) end return finalize( r, options )
end -- TemplatePar.valid()
TemplatePar.verify = function ( options )
-- Perform #invoke parameter analysis -- Precondition: -- options -- table or nil; optional details -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- form() return form( false, options )
end -- TemplatePar.verify()
-- Provide external access local p = {}
function p.assert( frame )
-- Perform parameter analysis on some single string -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- furnish() return furnish( frame, "assert" )
end -- .assert()
function p.check( frame )
-- Check validity of template parameters -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- form() -- fill() local options = { optional = { "all", "opt", "cat", "low", "noError", "template" }, template = "#invoke:TemplatePar|check|" } local r = form( false, options ) if not r then options = { mandatory = fill( frame.args.all ), optional = fill( frame.args.opt ), cat = frame.args.cat, low = frame.args.low, noError = frame.args.noError, template = frame.args.template } r = form( true, options ) end return r or ""
end -- .check()
function p.count( frame )
-- Count number of template parameters -- Postcondition: -- Return string with digits including "0" -- Uses: -- TemplatePar.count() return tostring( TemplatePar.count() )
end -- .count()
function p.countNotEmpty( frame )
-- Count number of template parameters which are not empty -- Postcondition: -- Return string with digits including "0" -- Uses: -- TemplatePar.countNotEmpty() return tostring( TemplatePar.countNotEmpty() )
end -- .countNotEmpty()
function p.match( frame )
-- Combined analysis of parameters and their values -- Postcondition: -- Return string with error message or "" -- Uses: -- mw.text.trim() -- mw.ustring.lower() -- failure() -- form() -- TemplatePar.downcase() -- figure() -- feasible() -- fault() -- finalize() local r = false local options = { cat = frame.args.cat, low = frame.args.low, noError = frame.args.noError, template = frame.args.template } local k, v, s local params = { } for k, v in pairs( frame.args ) do if type( k ) == "number" then s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" ) if s then s = mw.text.trim( s ) if s == "" then s = false end end if s then if options.low then s = mw.ustring.lower( s ) end if params[ s ] then s = params[ s ] s[ #s + 1 ] = v else params[ s ] = { v } end else r = failure( "invalidPar", tostring( k ), options ) break -- for k, v end end end -- for k, v if not r then s = { } for k, v in pairs( params ) do s[ #s + 1 ] = k end -- for k, v options.optional = s r = form( true, options ) end if not r then local errMiss, errValues, lack, rule local targs = frame:getParent().args options.optional = nil if options.low then targs = TemplatePar.downcase() else targs = frame:getParent().args end errMiss = false errValues = false for k, v in pairs( params ) do options.say = k errValue = false s = targs[ k ] if s then if s == "" then lack = true else lack = false end else s = "" lack = true end for r, rule in pairs( v ) do options = figure( rule, options ) r = feasible( s, options, true ) if r then if lack then if errMiss then errMiss = errMiss .. ", '" .. k .. "'" else errMiss = "'" .. k .. "'" end elseif not errMiss then errValues = fault( errValues, r ) end break -- for r, rule end end -- for s, rule end -- for k, v r = ( errMiss or errValues ) if r then if errMiss then r = failure( "undefined", errMiss, options ) else r = failure( "invalid", errValues, options ) end r = finalize( r, options ) end end return r or ""
end -- .match()
function p.valid( frame )
-- Check validity of one particular template parameter -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- furnish() return furnish( frame, "valid" )
end -- .valid()
function p.TemplatePar()
-- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplatePar
end -- .TemplatePar()
return p