Modul:TemplatePar: Unterschied zwischen den Versionen
te>PerfektesChaos (bugfix (valid unnamed) + kl. updates) |
K (25 Versionen importiert) |
||
(7 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
− | -- | + | local TemplatePar = { serial = "2018-08-10", |
+ | suite = "TemplatePar", | ||
+ | item = 15393417, | ||
+ | extern = { }, | ||
+ | frame = false } | ||
+ | --[=[ | ||
Template parameter utility | Template parameter utility | ||
* assert | * assert | ||
Zeile 6: | Zeile 11: | ||
* countNotEmpty | * countNotEmpty | ||
* downcase() | * downcase() | ||
+ | * duplicates | ||
* match | * match | ||
* valid | * valid | ||
* verify() | * verify() | ||
* TemplatePar() | * TemplatePar() | ||
+ | * failsafe() | ||
]=] | ]=] | ||
Zeile 15: | Zeile 22: | ||
-- Module globals | -- Module globals | ||
− | local | + | local MessagePrefix = "lua-module-TemplatePar-" |
− | + | local L10nDef = {} | |
− | local | + | L10nDef.en = { |
− | |||
badPattern = "#invoke:TemplatePar pattern syntax error", | badPattern = "#invoke:TemplatePar pattern syntax error", | ||
dupOpt = "#invoke:TemplatePar repeated optional parameter", | dupOpt = "#invoke:TemplatePar repeated optional parameter", | ||
Zeile 26: | Zeile 32: | ||
invalidPar = "#invoke:TemplatePar invalid parameter", | invalidPar = "#invoke:TemplatePar invalid parameter", | ||
minmax = "#invoke:TemplatePar min > max", | minmax = "#invoke:TemplatePar min > max", | ||
+ | missing = "#invoke:TemplatePar missing library", | ||
multiSpell = "Error in template * multiple spelling of parameter", | multiSpell = "Error in template * multiple spelling of parameter", | ||
− | + | noMSGnoCAT = "#invoke:TemplatePar neither message nor category", | |
noname = "#invoke:TemplatePar missing parameter name", | noname = "#invoke:TemplatePar missing parameter name", | ||
+ | notFound = "Error in template * missing page", | ||
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", | ||
Zeile 34: | Zeile 42: | ||
unknown = "Error in template * unknown parameter name", | unknown = "Error in template * unknown parameter name", | ||
unknownRule = "#invoke:TemplatePar unknown rule" | unknownRule = "#invoke:TemplatePar unknown rule" | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
local Patterns = { | local Patterns = { | ||
Zeile 80: | Zeile 71: | ||
[ "abc+" ] = "^[a-z]+$", | [ "abc+" ] = "^[a-z]+$", | ||
[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$", | [ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$", | ||
+ | [ "w" ] = "^%S*$", | ||
+ | [ "w+" ] = "^%S+$", | ||
[ "base64" ] = "^[A-Za-z0-9%+/]*$", | [ "base64" ] = "^[A-Za-z0-9%+/]*$", | ||
[ "base64+" ] = "^[A-Za-z0-9%+/]+$", | [ "base64+" ] = "^[A-Za-z0-9%+/]+$", | ||
Zeile 85: | Zeile 78: | ||
[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$", | [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$", | ||
1, 31, 127 ), | 1, 31, 127 ), | ||
+ | [ "ref" ] = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c", | ||
+ | 127, 34, "%-", "%-", "%-", "%x+", | ||
+ | "%-", 34, 127 ), | ||
[ "+" ] = "%S" | [ "+" ] = "%S" | ||
} | } | ||
Zeile 113: | Zeile 109: | ||
return r | return r | ||
end -- containsCJK() | 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: | ||
+ | -- >< TemplatePar.extern.FileMedia | ||
+ | -- Module:FileMedia | ||
+ | -- FileMedia.isType() | ||
+ | local r | ||
+ | if attempt and attempt ~= "" then | ||
+ | local FileMedia | ||
+ | if TemplatePar.extern.FileMedia then | ||
+ | FileMedia = TemplatePar.extern.FileMedia | ||
+ | else | ||
+ | local lucky | ||
+ | lucky, FileMedia = pcall( require, "Module:FileMedia" ) | ||
+ | if type( FileMedia ) == "table" then | ||
+ | FileMedia = FileMedia.FileMedia() | ||
+ | TemplatePar.extern.FileMedia = FileMedia | ||
+ | end | ||
+ | end | ||
+ | if type( FileMedia ) == "table" then | ||
+ | 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() | ||
Zeile 123: | Zeile 181: | ||
-- Return some message string | -- Return some message string | ||
-- Uses: | -- Uses: | ||
− | -- > | + | -- > MessagePrefix |
− | -- > | + | -- > L10nDef |
-- mw.language.getContentLanguage() | -- mw.language.getContentLanguage() | ||
-- mw.message.new() | -- mw.message.new() | ||
local c = mw.language.getContentLanguage():getCode() | local c = mw.language.getContentLanguage():getCode() | ||
− | local m = mw.message.new( | + | local m = mw.message.new( MessagePrefix .. say ) |
local r = false | local r = false | ||
if m:isBlank() then | if m:isBlank() then | ||
− | local l10n = | + | local l10n = L10nDef[ c ] |
if not l10n then | if not l10n then | ||
− | l10n = | + | local lucky |
+ | lucky, l10n = pcall( mw.loadData, | ||
+ | string.format( "Module:%s/%s", | ||
+ | TemplatePar.suite, c ) ) | ||
+ | if type( l10n ) == "table" then | ||
+ | L10nDef[ c ] = l10n | ||
+ | end | ||
+ | end | ||
+ | if type( l10n ) ~= "table" then | ||
+ | l10n = L10nDef.en | ||
end | end | ||
r = l10n[ say ] | r = l10n[ say ] | ||
+ | if not r then | ||
+ | r = L10nDef.en[ say ] | ||
+ | end | ||
else | else | ||
m:inLanguage( c ) | m:inLanguage( c ) | ||
Zeile 141: | Zeile 211: | ||
end | end | ||
if not r then | if not r then | ||
− | r = "((( | + | r = string.format( "(((%s)))", say ) |
end | end | ||
return r | return r | ||
end -- factory() | end -- factory() | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Zeile 177: | Zeile 233: | ||
if type( options.template ) == "string" then | if type( options.template ) == "string" then | ||
if #options.template > 0 then | if #options.template > 0 then | ||
− | r = | + | r = string.format( "%s (%s)", r, options.template ) |
end | end | ||
end | end | ||
end | end | ||
if suspect then | if suspect then | ||
− | r = | + | r = string.format( "%s: %s", r, suspect ) |
end | end | ||
return r | return r | ||
end -- failure() | end -- failure() | ||
+ | |||
+ | |||
+ | |||
+ | local function fair( 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 -- fair() | ||
+ | |||
+ | |||
+ | |||
+ | local function familiar( accept, attempt ) | ||
+ | -- Check string as possible language name or list | ||
+ | -- Precondition: | ||
+ | -- accept -- string; requirement | ||
+ | -- lang | ||
+ | -- langs | ||
+ | -- langW | ||
+ | -- langsW | ||
+ | -- lang+ | ||
+ | -- langs+ | ||
+ | -- langW+ | ||
+ | -- langsW+ | ||
+ | -- attempt -- string; to be tested | ||
+ | -- Postcondition: | ||
+ | -- Return error keyword, or false | ||
+ | -- Uses: | ||
+ | -- >< TemplatePar.extern.Multilingual | ||
+ | -- Module:Multilingual | ||
+ | -- Multilingual.isType() | ||
+ | local r | ||
+ | if attempt and attempt ~= "" then | ||
+ | local Multilingual | ||
+ | if TemplatePar.extern.Multilingual then | ||
+ | Multilingual = TemplatePar.extern.Multilingual | ||
+ | else | ||
+ | local lucky | ||
+ | lucky, Multilingual = pcall( require, "Module:Multilingual" ) | ||
+ | if type( Multilingual ) == "table" then | ||
+ | Multilingual = Multilingual.Multilingual() | ||
+ | TemplatePar.extern.Multilingual = Multilingual | ||
+ | end | ||
+ | end | ||
+ | if type( Multilingual ) == "table" then | ||
+ | local lazy = accept:find( "W", 1, true ) | ||
+ | if accept:find( "s", 1, true ) then | ||
+ | local group = mw.text.split( attempt, "%s+" ) | ||
+ | r = false | ||
+ | for i = 1, #group do | ||
+ | if not Multilingual.isLang( group[ i ], lazy ) then | ||
+ | r = "invalid" | ||
+ | break -- for i | ||
+ | end | ||
+ | end -- for i | ||
+ | elseif Multilingual.isLang( attempt, lazy ) then | ||
+ | r = false | ||
+ | else | ||
+ | r = "invalid" | ||
+ | end | ||
+ | else | ||
+ | r = "missing" | ||
+ | end | ||
+ | elseif accept:find( "+", 1, true ) then | ||
+ | r = "empty" | ||
+ | else | ||
+ | r = false | ||
+ | end | ||
+ | return r | ||
+ | end -- familiar() | ||
+ | |||
+ | |||
+ | |||
+ | local function far( accept, attempt ) | ||
+ | -- Check string as possible URL | ||
+ | -- Precondition: | ||
+ | -- accept -- string; requirement | ||
+ | -- url | ||
+ | -- url+ | ||
+ | -- attempt -- string; to be tested | ||
+ | -- Postcondition: | ||
+ | -- Return error keyword, or false | ||
+ | -- Uses: | ||
+ | -- >< TemplatePar.extern.Multilingual | ||
+ | -- Module:Multilingual | ||
+ | -- Multilingual.isType() | ||
+ | local r | ||
+ | if attempt and attempt ~= "" then | ||
+ | local URLutil | ||
+ | if TemplatePar.extern.URLutil then | ||
+ | URLutil = TemplatePar.extern.URLutil | ||
+ | else | ||
+ | local lucky | ||
+ | lucky, URLutil = pcall( require, "Module:URLutil" ) | ||
+ | if type( URLutil ) == "table" then | ||
+ | URLutil = URLutil.URLutil() | ||
+ | TemplatePar.extern.URLutil = URLutil | ||
+ | end | ||
+ | end | ||
+ | if type( URLutil ) == "table" then | ||
+ | if URLutil.isWebURL( attempt ) then | ||
+ | r = false | ||
+ | else | ||
+ | r = "invalid" | ||
+ | end | ||
+ | else | ||
+ | r = "missing" | ||
+ | end | ||
+ | elseif accept:find( "+", 1, true ) then | ||
+ | r = "empty" | ||
+ | else | ||
+ | r = false | ||
+ | end | ||
+ | return r | ||
+ | end -- far() | ||
Zeile 204: | Zeile 380: | ||
end | end | ||
if store then | if store then | ||
− | r = | + | r = string.format( "%s; %s", store, s ) |
else | else | ||
r = s | r = s | ||
Zeile 229: | Zeile 405: | ||
-- failure() | -- failure() | ||
-- mw.text.trim() | -- mw.text.trim() | ||
− | -- | + | -- facility() |
+ | -- fair() | ||
-- containsCJK() | -- containsCJK() | ||
local r = false | local r = false | ||
Zeile 252: | Zeile 429: | ||
if type( scan ) == "string" then | if type( scan ) == "string" then | ||
if s == "n" or s == "0,0" or s == "0.0" then | if s == "n" or s == "0,0" or s == "0.0" then | ||
− | if not analyze:match( "[0-9]" ) then | + | if not analyze:match( "[0-9]" ) and |
+ | not analyze:match( "^%s*$" ) then | ||
scan = false | scan = false | ||
if options.say then | if options.say then | ||
− | show = "'" | + | show = string.format( "'%s'", options.say ) |
end | end | ||
if abbr then | if abbr then | ||
Zeile 293: | Zeile 471: | ||
r = "undefined" | r = "undefined" | ||
end | end | ||
+ | elseif s:match( "^image%+?:?$" ) or | ||
+ | s:match( "^file%+?:?$" ) then | ||
+ | r = facility( s, analyze ) | ||
+ | n = true | ||
+ | elseif s:match( "langs?W?%+?" ) then | ||
+ | r = familiar( s, analyze ) | ||
+ | n = true | ||
+ | elseif s:match( "url%+?" ) then | ||
+ | r = far( s, analyze ) | ||
+ | n = true | ||
end | end | ||
+ | -- datetime+ | ||
+ | -- iso8631+ | ||
+ | -- line+ | ||
if not n and not r then | if not n and not r then | ||
r = "unknownRule" | r = "unknownRule" | ||
Zeile 299: | Zeile 490: | ||
if r then | if r then | ||
if options.say then | if options.say then | ||
− | show = "'" | + | show = string.format( "'%s' %s", options.say, s ) |
else | else | ||
show = s | show = s | ||
Zeile 312: | Zeile 503: | ||
end | end | ||
if scan then | if scan then | ||
− | local legal, got = pcall( | + | local legal, got = pcall( fair, analyze, scan ) |
if legal then | if legal then | ||
if not got then | if not got then | ||
Zeile 320: | Zeile 511: | ||
if not got then | if not got then | ||
if options.say then | if options.say then | ||
− | show = "'" | + | show = string.format( "'%s'", options.say ) |
end | end | ||
if abbr then | if abbr then | ||
Zeile 331: | Zeile 522: | ||
else | else | ||
r = failure( "badPattern", | r = failure( "badPattern", | ||
− | + | string.format( "%s *** %s", scan, got ), | |
options ) | options ) | ||
end | end | ||
Zeile 347: | Zeile 538: | ||
-- Postcondition: | -- Postcondition: | ||
-- Return true iff found | -- Return true iff found | ||
− | local k, v | + | local k, v, r |
for k, v in pairs( haystack ) do | for k, v in pairs( haystack ) do | ||
if k == needle then | if k == needle then | ||
− | + | r = true | |
end | end | ||
end -- for k, v | end -- for k, v | ||
− | return false | + | return r or false |
end -- fed() | end -- fed() | ||
Zeile 375: | Zeile 566: | ||
g = TemplatePar.downcase( options ) | g = TemplatePar.downcase( options ) | ||
else | else | ||
− | + | TemplatePar.frame = mw.getCurrentFrame() | |
+ | g = TemplatePar.frame | ||
if light then | if light then | ||
g = g:getParent() | g = g:getParent() | ||
Zeile 419: | Zeile 611: | ||
if type( sub ) == "string" then | if type( sub ) == "string" then | ||
sub = sub:gsub( "%%!", "|" ) | sub = sub:gsub( "%%!", "|" ) | ||
− | + | :gsub( "%%%(%(", "{{" ) | |
− | + | :gsub( "%%%)%)", "}}" ) | |
+ | :gsub( "\\n", string.char( 10 ) ) | ||
options.pattern = sub | options.pattern = sub | ||
options.key = nil | options.key = nil | ||
Zeile 464: | Zeile 657: | ||
-- submit -- string or false or nil; non-empty error message | -- submit -- string or false or nil; non-empty error message | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
− | -- options. | + | -- options.format |
+ | -- options.preview | ||
-- options.cat | -- options.cat | ||
-- options.template | -- options.template | ||
Zeile 473: | Zeile 667: | ||
local r = false | local r = false | ||
if submit then | if submit then | ||
+ | local lazy = false | ||
+ | local learn = false | ||
+ | local show = false | ||
local opt, s | local opt, s | ||
if type( options ) == "table" then | if type( options ) == "table" then | ||
− | opt = options | + | 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 | ||
+ | local sniffer = "{{REVISIONID}}" | ||
+ | if lazy then | ||
+ | show = "" | ||
+ | lazy = false | ||
+ | end | ||
+ | if not TemplatePar.frame then | ||
+ | TemplatePar.frame = mw.getCurrentFrame() | ||
+ | end | ||
+ | if TemplatePar.frame:preprocess( sniffer ) == "" then | ||
+ | if s == "1" then | ||
+ | show = "*" | ||
+ | else | ||
+ | show = s | ||
+ | end | ||
+ | learn = true | ||
+ | end | ||
+ | end | ||
else | else | ||
opt = { } | opt = { } | ||
end | end | ||
− | if | + | if lazy then |
if not opt.cat then | if not opt.cat then | ||
− | r = | + | r = string.format( "%s %s", |
+ | submit, factory( "noMSGnoCAT" ) ) | ||
end | end | ||
else | else | ||
r = submit | r = submit | ||
end | end | ||
− | if r then | + | if r and not lazy then |
− | + | local i | |
+ | if not show or show == "*" then | ||
+ | local e = mw.html.create( "span" ) | ||
+ | :attr( "class", "error" ) | ||
+ | :wikitext( "@@@" ) | ||
+ | if learn then | ||
+ | local max = 1000000000 | ||
+ | local id = math.floor( os.clock() * max ) | ||
+ | local sign = string.format( "error_%d", id ) | ||
+ | local btn = mw.html.create( "span" ) | ||
+ | local top = mw.html.create( "div" ) | ||
+ | e:attr( "id", sign ) | ||
+ | btn:css( { ["background"] = "#FFFF00", | ||
+ | ["border"] = "#FF0000 3px solid", | ||
+ | ["font-weight"] = "bold", | ||
+ | ["padding"] = "2px", | ||
+ | ["text-decoration"] = "none" } ) | ||
+ | :wikitext( ">>>" ) | ||
+ | sign = string.format( "[[#%s|%s]]", | ||
+ | sign, tostring( btn ) ) | ||
+ | top:wikitext( sign, " ", submit ) | ||
+ | mw.addWarning( tostring( top ) ) | ||
+ | end | ||
+ | show = tostring( e ) | ||
+ | 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 | ||
+ | if learn and r then | ||
+ | -- r = fatal( r ) | ||
end | end | ||
s = opt.cat | s = opt.cat | ||
if type( s ) == "string" then | if type( s ) == "string" then | ||
− | if | + | local link |
− | + | 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 | ||
+ | link = true | ||
+ | end | ||
+ | elseif st == "table" then | ||
+ | for i = 1, #opt.errNS do | ||
+ | if opt.errNS[ i ] == ns then | ||
+ | link = true | ||
+ | break -- for i | ||
+ | end | ||
+ | end -- for i | ||
+ | end | ||
+ | else | ||
+ | link = true | ||
end | end | ||
− | if | + | if link then |
− | if | + | local cats, i |
− | + | if not r then | |
+ | r = "" | ||
end | end | ||
+ | if s:find( "@@@" ) then | ||
+ | if type( opt.template ) == "string" then | ||
+ | s = s:gsub( "@@@", opt.template ) | ||
+ | end | ||
+ | end | ||
+ | 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 | end | ||
end | end | ||
Zeile 547: | Zeile 834: | ||
end -- for k, v | end -- for k, v | ||
if r then | if r then | ||
− | r = failure( "unknown", | + | r = failure( "unknown", |
+ | string.format( "'%s'", r ), | ||
+ | options ) | ||
else -- all names valid | else -- all names valid | ||
local i, s | local i, s | ||
Zeile 661: | Zeile 950: | ||
− | local function form( light, options ) | + | local function form( light, options, frame ) |
-- Run parameter analysis on current environment | -- Run parameter analysis on current environment | ||
-- Precondition: | -- Precondition: | ||
Zeile 668: | Zeile 957: | ||
-- options.mandatory | -- options.mandatory | ||
-- options.optional | -- options.optional | ||
+ | -- frame -- object; #invoke environment, or false | ||
-- Postcondition: | -- Postcondition: | ||
-- Return string with error message as configured; | -- Return string with error message as configured; | ||
-- false if valid | -- false if valid | ||
-- Uses: | -- Uses: | ||
+ | -- < TemplatePar.frame | ||
-- fold() | -- fold() | ||
-- fetch() | -- fetch() | ||
Zeile 677: | Zeile 968: | ||
-- finalize() | -- finalize() | ||
local duty, r | local duty, r | ||
+ | if frame then | ||
+ | TemplatePar.frame = frame | ||
+ | else | ||
+ | TemplatePar.frame = mw.getCurrentFrame() | ||
+ | end | ||
if type( options ) == "table" then | if type( options ) == "table" then | ||
if type( options.mandatory ) ~= "table" then | if type( options.mandatory ) ~= "table" then | ||
Zeile 718: | Zeile 1.014: | ||
-- feasible() | -- feasible() | ||
-- failure() | -- failure() | ||
− | local r | + | local r = feasible( analyze, options, false ) |
local show | local show | ||
if options.min and not r then | if options.min and not r then | ||
Zeile 725: | Zeile 1.021: | ||
if options.max < options.min then | if options.max < options.min then | ||
r = failure( "minmax", | r = failure( "minmax", | ||
− | + | string.format( "%d > %d", | |
− | + | options.min, | |
− | + | options.max ), | |
options ) | options ) | ||
end | end | ||
Zeile 734: | Zeile 1.030: | ||
show = " <" .. options.min | show = " <" .. options.min | ||
if options.say then | if options.say then | ||
− | show = | + | show = string.format( "%s '%s'", show, options.say ) |
end | end | ||
r = failure( "tooShort", show, options ) | r = failure( "tooShort", show, options ) | ||
Zeile 747: | Zeile 1.043: | ||
show = " >" .. options.max | show = " >" .. options.max | ||
if options.say then | if options.say then | ||
− | show = | + | show = string.format( "%s '%s'", show, options.say ) |
end | end | ||
r = failure( "tooLong", show, options ) | r = failure( "tooLong", show, options ) | ||
Zeile 770: | Zeile 1.066: | ||
-- false if valid or no answer permitted | -- false if valid or no answer permitted | ||
-- Uses: | -- Uses: | ||
+ | -- mw.text.trim() | ||
-- format() | -- format() | ||
-- failure() | -- failure() | ||
local r = false | local r = false | ||
if type( assignment ) == "table" then | if type( assignment ) == "table" then | ||
− | local story = assignment.args[ access ] | + | local story = assignment.args[ access ] or "" |
− | if type( | + | if type( access ) == "number" then |
− | if type( options ) ~= "table" then | + | story = mw.text.trim( story ) |
− | + | end | |
− | + | if type( options ) ~= "table" then | |
− | + | options = { } | |
− | |||
− | |||
− | |||
end | end | ||
+ | options.say = access | ||
+ | r = format( story, options ) | ||
end | end | ||
return r | return r | ||
Zeile 799: | Zeile 1.095: | ||
-- Uses: | -- Uses: | ||
-- form() | -- form() | ||
− | |||
-- failure() | -- failure() | ||
+ | -- finalize() | ||
+ | -- TemplatePar.valid() | ||
-- TemplatePar.assert() | -- TemplatePar.assert() | ||
− | |||
local options = { mandatory = { "1" }, | local options = { mandatory = { "1" }, | ||
optional = { "2", | optional = { "2", | ||
"cat", | "cat", | ||
+ | "errNS", | ||
"low", | "low", | ||
"max", | "max", | ||
"min", | "min", | ||
− | " | + | "format", |
+ | "preview", | ||
"template" }, | "template" }, | ||
− | template = "#invoke: | + | template = string.format( "#invoke:%s|%s|", |
+ | "TemplatePar", | ||
+ | action ) | ||
} | } | ||
− | local r = form( false, options ) | + | local r = form( false, options, frame ) |
if not r then | if not r then | ||
local s | local s | ||
options = { cat = frame.args.cat, | options = { cat = frame.args.cat, | ||
+ | errNS = frame.args.errNS, | ||
low = frame.args.low, | low = frame.args.low, | ||
− | + | format = frame.args.format, | |
+ | preview = frame.args.preview, | ||
template = frame.args.template | template = frame.args.template | ||
} | } | ||
Zeile 878: | Zeile 1.180: | ||
if ( type( append ) == "string" ) then | if ( type( append ) == "string" ) then | ||
if ( append ~= "" ) then | if ( append ~= "" ) then | ||
− | r = | + | r = string.format( "%s<br />%s", append, r ) |
end | end | ||
else | else | ||
Zeile 900: | Zeile 1.202: | ||
-- Uses: | -- Uses: | ||
-- form() | -- form() | ||
− | return form( true, options ) | + | return form( true, options, false ) |
end -- TemplatePar.check() | end -- TemplatePar.check() | ||
Zeile 958: | Zeile 1.260: | ||
return flat( t.args, options ) | return flat( t.args, options ) | ||
end -- TemplatePar.downcase() | end -- TemplatePar.downcase() | ||
+ | |||
+ | |||
+ | |||
+ | TemplatePar.failsafe = function ( assert ) | ||
+ | -- Retrieve versioning and check for compliance | ||
+ | -- Precondition: | ||
+ | -- assert -- string, with required version or "wikidata", | ||
+ | -- or false | ||
+ | -- Postcondition: | ||
+ | -- Returns string with appropriate version, or false | ||
+ | local r | ||
+ | if since == "wikidata" then | ||
+ | local item = TemplatePar.item | ||
+ | since = false | ||
+ | if type( item ) == "number" and item > 0 then | ||
+ | local ent = mw.wikibase.getEntity( string.format( "Q%d", | ||
+ | item ) ) | ||
+ | if type( ent ) == "table" then | ||
+ | local vsn = ent:formatPropertyValues( "P348" ) | ||
+ | if type( vsn ) == "table" and | ||
+ | type( vsn.value) == "string" and | ||
+ | vsn.value ~= "" then | ||
+ | r = vsn.value | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | if not r then | ||
+ | if not since or since <= TemplatePar.serial then | ||
+ | r = TemplatePar.serial | ||
+ | else | ||
+ | r = false | ||
+ | end | ||
+ | end | ||
+ | return r | ||
+ | end -- TemplatePar.failsafe() | ||
Zeile 971: | Zeile 1.309: | ||
-- false if valid or no answer permitted | -- false if valid or no answer permitted | ||
-- Uses: | -- Uses: | ||
+ | -- >< TemplatePar.frame | ||
-- mw.text.trim() | -- mw.text.trim() | ||
-- TemplatePar.downcase() | -- TemplatePar.downcase() | ||
− | |||
-- frame:getParent() | -- frame:getParent() | ||
-- formatted() | -- formatted() | ||
Zeile 997: | Zeile 1.335: | ||
params = TemplatePar.downcase( options ) | params = TemplatePar.downcase( options ) | ||
else | else | ||
− | + | if not TemplatePar.frame then | |
+ | TemplatePar.frame = mw.getCurrentFrame() | ||
+ | end | ||
+ | params = TemplatePar.frame:getParent() | ||
end | end | ||
r = formatted( params, access, options ) | r = formatted( params, access, options ) | ||
Zeile 1.003: | Zeile 1.344: | ||
r = failure( "noname", false, options ) | r = failure( "noname", false, options ) | ||
end | end | ||
− | return finalize( r, options ) | + | return finalize( r, options, frame ) |
end -- TemplatePar.valid() | end -- TemplatePar.valid() | ||
Zeile 1.017: | Zeile 1.358: | ||
-- Uses: | -- Uses: | ||
-- form() | -- form() | ||
− | return form( false, options ) | + | return form( false, options, false ) |
end -- TemplatePar.verify() | end -- TemplatePar.verify() | ||
Zeile 1.036: | Zeile 1.377: | ||
-- furnish() | -- furnish() | ||
return furnish( frame, "assert" ) | return furnish( frame, "assert" ) | ||
− | end -- .assert() | + | end -- p.assert() |
Zeile 1.052: | Zeile 1.393: | ||
"opt", | "opt", | ||
"cat", | "cat", | ||
+ | "errNS", | ||
"low", | "low", | ||
− | " | + | "format", |
+ | "preview", | ||
"template" }, | "template" }, | ||
template = "#invoke:TemplatePar|check|" | template = "#invoke:TemplatePar|check|" | ||
} | } | ||
− | local r = form( false, options ) | + | local r = form( false, options, frame ) |
if not r then | if not r then | ||
options = { mandatory = fill( frame.args.all ), | options = { mandatory = fill( frame.args.all ), | ||
optional = fill( frame.args.opt ), | optional = fill( frame.args.opt ), | ||
cat = frame.args.cat, | cat = frame.args.cat, | ||
+ | errNS = frame.args.errNS, | ||
low = frame.args.low, | low = frame.args.low, | ||
− | + | format = frame.args.format, | |
+ | preview = frame.args.preview, | ||
template = frame.args.template | template = frame.args.template | ||
} | } | ||
− | r = form( true, options ) | + | r = form( true, options, frame ) |
end | end | ||
return r or "" | return r or "" | ||
− | end -- .check() | + | end -- p.check() |
Zeile 1.080: | Zeile 1.425: | ||
-- TemplatePar.count() | -- TemplatePar.count() | ||
return tostring( TemplatePar.count() ) | return tostring( TemplatePar.count() ) | ||
− | end -- .count() | + | end -- p.count() |
Zeile 1.091: | Zeile 1.436: | ||
-- TemplatePar.countNotEmpty() | -- TemplatePar.countNotEmpty() | ||
return tostring( TemplatePar.countNotEmpty() ) | return tostring( TemplatePar.countNotEmpty() ) | ||
− | end -- .countNotEmpty() | + | end -- p.countNotEmpty() |
Zeile 1.097: | Zeile 1.442: | ||
function p.match( frame ) | function p.match( frame ) | ||
-- Combined analysis of parameters and their values | -- Combined analysis of parameters and their values | ||
+ | -- Precondition: | ||
+ | -- frame -- object; #invoke environment | ||
-- Postcondition: | -- Postcondition: | ||
-- Return string with error message or "" | -- Return string with error message or "" | ||
-- Uses: | -- Uses: | ||
+ | -- < TemplatePar.frame | ||
-- mw.text.trim() | -- mw.text.trim() | ||
-- mw.ustring.lower() | -- mw.ustring.lower() | ||
Zeile 1.110: | Zeile 1.458: | ||
-- finalize() | -- finalize() | ||
local r = false | local r = false | ||
− | local options = { cat | + | local options = { cat = frame.args.cat, |
− | low | + | errNS = frame.args.errNS, |
− | + | low = frame.args.low, | |
− | template | + | format = frame.args.format, |
+ | preview = frame.args.preview, | ||
+ | template = frame.args.template | ||
} | } | ||
local k, v, s | local k, v, s | ||
local params = { } | local params = { } | ||
+ | TemplatePar.frame = frame | ||
for k, v in pairs( frame.args ) do | for k, v in pairs( frame.args ) do | ||
if type( k ) == "number" then | if type( k ) == "number" then | ||
Zeile 1.148: | Zeile 1.499: | ||
end -- for k, v | end -- for k, v | ||
options.optional = s | options.optional = s | ||
− | r = form( true, options ) | + | r = form( true, options, frame ) |
end | end | ||
if not r then | if not r then | ||
Zeile 1.181: | Zeile 1.532: | ||
if lack then | if lack then | ||
if errMiss then | if errMiss then | ||
− | errMiss = | + | errMiss = string.format( "%s, '%s'", |
+ | errMiss, k ) | ||
else | else | ||
− | errMiss = "'" | + | errMiss = string.format( "'%s'", k ) |
end | end | ||
elseif not errMiss then | elseif not errMiss then | ||
Zeile 1.203: | Zeile 1.555: | ||
end | end | ||
return r or "" | return r or "" | ||
− | end -- .match() | + | end -- p.match() |
Zeile 1.216: | Zeile 1.568: | ||
-- furnish() | -- furnish() | ||
return furnish( frame, "valid" ) | return furnish( frame, "valid" ) | ||
− | end -- .valid() | + | end -- p.valid() |
+ | |||
+ | |||
+ | |||
+ | p.failsafe = function ( frame ) | ||
+ | -- Check or retrieve version information | ||
+ | -- Precondition: | ||
+ | -- frame -- object; #invoke environment | ||
+ | -- Postcondition: | ||
+ | -- Return string with error message or "" | ||
+ | -- Uses: | ||
+ | -- TemplatePar.failsafe() | ||
+ | local s = type( frame ) | ||
+ | local since | ||
+ | if s == "table" then | ||
+ | since = frame.args[ 1 ] | ||
+ | elseif s == "string" then | ||
+ | since = frame | ||
+ | end | ||
+ | if since then | ||
+ | since = mw.text.trim( since ) | ||
+ | if since == "" then | ||
+ | since = false | ||
+ | end | ||
+ | end | ||
+ | return TemplatePar.failsafe( since ) or "" | ||
+ | end -- p.failsafe() | ||
Zeile 1.225: | Zeile 1.603: | ||
-- Return table with functions | -- Return table with functions | ||
return TemplatePar | return TemplatePar | ||
− | end -- .TemplatePar() | + | end -- p.TemplatePar() |
return p | return p |
Aktuelle Version vom 6. September 2019, 12:27 Uhr
local TemplatePar = { serial = "2018-08-10",
suite = "TemplatePar", item = 15393417, extern = { }, frame = false }
--[=[ Template parameter utility
- assert
- check
- count
- countNotEmpty
- downcase()
- duplicates
- match
- valid
- verify()
- TemplatePar()
- failsafe()
]=]
-- Module globals 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"
} 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 ), [ "ref" ] = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c", 127, 34, "%-", "%-", "%-", "%x+", "%-", 34, 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: -- >< TemplatePar.extern.FileMedia -- Module:FileMedia -- FileMedia.isType() local r if attempt and attempt ~= "" then local FileMedia if TemplatePar.extern.FileMedia then FileMedia = TemplatePar.extern.FileMedia else local lucky lucky, FileMedia = pcall( require, "Module:FileMedia" ) if type( FileMedia ) == "table" then FileMedia = FileMedia.FileMedia() TemplatePar.extern.FileMedia = FileMedia end end if type( FileMedia ) == "table" then 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 local lucky lucky, l10n = pcall( mw.loadData, string.format( "Module:%s/%s", TemplatePar.suite, c ) ) if type( l10n ) == "table" then L10nDef[ c ] = l10n end end if type( l10n ) ~= "table" then l10n = L10nDef.en end r = l10n[ say ] if not r then r = L10nDef.en[ say ] end else m:inLanguage( c ) r = m:plain() end if not r then r = string.format( "(((%s)))", 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 = 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 fair( 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 -- fair()
local function familiar( accept, attempt )
-- Check string as possible language name or list -- Precondition: -- accept -- string; requirement -- lang -- langs -- langW -- langsW -- lang+ -- langs+ -- langW+ -- langsW+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- >< TemplatePar.extern.Multilingual -- Module:Multilingual -- Multilingual.isType() local r if attempt and attempt ~= "" then local Multilingual if TemplatePar.extern.Multilingual then Multilingual = TemplatePar.extern.Multilingual else local lucky lucky, Multilingual = pcall( require, "Module:Multilingual" ) if type( Multilingual ) == "table" then Multilingual = Multilingual.Multilingual() TemplatePar.extern.Multilingual = Multilingual end end if type( Multilingual ) == "table" then local lazy = accept:find( "W", 1, true ) if accept:find( "s", 1, true ) then local group = mw.text.split( attempt, "%s+" ) r = false for i = 1, #group do if not Multilingual.isLang( group[ i ], lazy ) then r = "invalid" break -- for i end end -- for i elseif Multilingual.isLang( attempt, lazy ) then r = false else r = "invalid" end else r = "missing" end elseif accept:find( "+", 1, true ) then r = "empty" else r = false end return r
end -- familiar()
local function far( accept, attempt )
-- Check string as possible URL -- Precondition: -- accept -- string; requirement -- url -- url+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- >< TemplatePar.extern.Multilingual -- Module:Multilingual -- Multilingual.isType() local r if attempt and attempt ~= "" then local URLutil if TemplatePar.extern.URLutil then URLutil = TemplatePar.extern.URLutil else local lucky lucky, URLutil = pcall( require, "Module:URLutil" ) if type( URLutil ) == "table" then URLutil = URLutil.URLutil() TemplatePar.extern.URLutil = URLutil end end if type( URLutil ) == "table" then if URLutil.isWebURL( attempt ) then r = false else r = "invalid" end else r = "missing" end elseif accept:find( "+", 1, true ) then r = "empty" else r = false end return r
end -- far()
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() -- fair() -- 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( "langs?W?%+?" ) then r = familiar( s, analyze ) n = true elseif s:match( "url%+?" ) then r = far( s, analyze ) n = true end
-- datetime+ -- iso8631+ -- line+
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( fair, 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, r for k, v in pairs( haystack ) do if k == needle then r = true end end -- for k, v return r or 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 TemplatePar.frame = mw.getCurrentFrame() g = TemplatePar.frame 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( "%%!", "|" ) :gsub( "%%%(%(", "{{" ) :gsub( "%%%)%)", "}}" ) :gsub( "\\n", string.char( 10 ) ) 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.format -- options.preview -- options.cat -- options.template -- Postcondition: -- Return string or false -- Uses: -- factory() local r = false if submit then local lazy = false local learn = false local show = false local opt, s 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 local sniffer = "3926" if lazy then show = "" lazy = false end if not TemplatePar.frame then TemplatePar.frame = mw.getCurrentFrame() end if TemplatePar.frame:preprocess( sniffer ) == "" then if s == "1" then show = "*" else show = s end learn = true 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 local e = mw.html.create( "span" ) :attr( "class", "error" ) :wikitext( "@@@" ) if learn then local max = 1000000000 local id = math.floor( os.clock() * max ) local sign = string.format( "error_%d", id ) local btn = mw.html.create( "span" ) local top = mw.html.create( "div" ) e:attr( "id", sign ) btn:css( { ["background"] = "#FFFF00", ["border"] = "#FF0000 3px solid", ["font-weight"] = "bold", ["padding"] = "2px", ["text-decoration"] = "none" } ) :wikitext( ">>>" ) sign = string.format( "%s", sign, tostring( btn ) ) top:wikitext( sign, " ", submit ) mw.addWarning( tostring( top ) ) end show = tostring( e ) 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 if learn and r then -- r = fatal( r ) end s = opt.cat if type( s ) == "string" then local link 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 link = true end elseif st == "table" then for i = 1, #opt.errNS do if opt.errNS[ i ] == ns then link = true break -- for i end end -- for i end else link = true end if link then local cats, i if not r then r = "" end if s:find( "@@@" ) then if type( opt.template ) == "string" then s = s:gsub( "@@@", opt.template ) end end 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", 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; #invoke environment, or false -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- < TemplatePar.frame -- fold() -- fetch() -- fix() -- finalize() local duty, r if frame then TemplatePar.frame = frame else TemplatePar.frame = mw.getCurrentFrame() end 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", 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 ) else s = frame.args[ 1 ] or "" r = tonumber( s ) if ( r ) then s = r end if action == "valid" then r = TemplatePar.valid( s, options ) 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
-- 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
%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.failsafe = function ( assert )
-- Retrieve versioning and check for compliance -- Precondition: -- assert -- string, with required version or "wikidata", -- or false -- Postcondition: -- Returns string with appropriate version, or false local r if since == "wikidata" then local item = TemplatePar.item since = false if type( item ) == "number" and item > 0 then local ent = mw.wikibase.getEntity( string.format( "Q%d", item ) ) if type( ent ) == "table" then local vsn = ent:formatPropertyValues( "P348" ) if type( vsn ) == "table" and type( vsn.value) == "string" and vsn.value ~= "" then r = vsn.value end end end end if not r then if not since or since <= TemplatePar.serial then r = TemplatePar.serial else r = false end end return r
end -- TemplatePar.failsafe()
TemplatePar.valid = function ( access, options )
-- Check validity of one particular template parameter -- Precondition: -- access -- id of parameter in template transclusion -- string or number -- options -- table or nil; optional details -- Postcondition: -- Return string with error message as configured; -- false if valid or no answer permitted -- Uses: -- >< TemplatePar.frame -- 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 if not TemplatePar.frame then TemplatePar.frame = mw.getCurrentFrame() end params = TemplatePar.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 -- p.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 -- p.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 -- p.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 -- p.countNotEmpty()
function p.match( frame )
-- Combined analysis of parameters and their values -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- < TemplatePar.frame -- 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 = { } TemplatePar.frame = frame 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 ) end end return r or ""
end -- p.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 -- p.valid()
p.failsafe = function ( frame )
-- Check or retrieve version information -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- TemplatePar.failsafe() local s = type( frame ) local since if s == "table" then since = frame.args[ 1 ] elseif s == "string" then since = frame end if since then since = mw.text.trim( since ) if since == "" then since = false end end return TemplatePar.failsafe( since ) or ""
end -- p.failsafe()
function p.TemplatePar()
-- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplatePar
end -- p.TemplatePar()
return p