Modul:TemplatePar: Unterschied zwischen den Versionen
te>Leyo (neue Version von PerfektesChaos) |
Admin (Diskussion | Beiträge) K (25 Versionen importiert) |
||
(2 dazwischenliegende Versionen von einem anderen Benutzer 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 MessagePrefix = "lua-module-TemplatePar-" | local MessagePrefix = "lua-module-TemplatePar-" | ||
local L10nDef = {} | local L10nDef = {} | ||
Zeile 36: | 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 90: | 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 137: | Zeile 128: | ||
-- Return error keyword, or false | -- Return error keyword, or false | ||
-- Uses: | -- Uses: | ||
+ | -- >< TemplatePar.extern.FileMedia | ||
-- Module:FileMedia | -- Module:FileMedia | ||
-- FileMedia.isType() | -- FileMedia.isType() | ||
local r | local r | ||
if attempt and attempt ~= "" then | if attempt and attempt ~= "" then | ||
− | local lucky, FileMedia = pcall( require, "Module:FileMedia" ) | + | 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 | if type( FileMedia ) == "table" then | ||
− | |||
local s, live = accept:match( "^([a-z]+)(:?)%+?$" ) | local s, live = accept:match( "^([a-z]+)(:?)%+?$" ) | ||
if live then | if live then | ||
Zeile 190: | Zeile 191: | ||
local l10n = L10nDef[ c ] | local l10n = L10nDef[ c ] | ||
if not l10n then | if not l10n then | ||
− | l10n = L10nDef[ "en | + | 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 202: | Zeile 215: | ||
return r | return r | ||
end -- factory() | end -- factory() | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Zeile 243: | Zeile 242: | ||
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 287: | Zeile 406: | ||
-- mw.text.trim() | -- mw.text.trim() | ||
-- facility() | -- facility() | ||
− | -- | + | -- fair() |
-- containsCJK() | -- containsCJK() | ||
local r = false | local r = false | ||
Zeile 356: | Zeile 475: | ||
r = facility( s, analyze ) | r = facility( s, analyze ) | ||
n = true | n = true | ||
− | elseif s:match( " | + | elseif s:match( "langs?W?%+?" ) then |
− | n = " | + | 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 379: | 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 414: | 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 442: | 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 486: | 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 526: | Zeile 652: | ||
− | local function finalize( submit, options | + | local function finalize( submit, options ) |
-- Finalize message | -- Finalize message | ||
-- Precondition: | -- Precondition: | ||
Zeile 535: | Zeile 661: | ||
-- options.cat | -- options.cat | ||
-- options.template | -- options.template | ||
− | |||
-- Postcondition: | -- Postcondition: | ||
-- Return string or false | -- Return string or false | ||
Zeile 542: | 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 | ||
Zeile 551: | Zeile 677: | ||
s = opt.preview | s = opt.preview | ||
if type( s ) == "string" and | if type( s ) == "string" and | ||
− | + | s ~= "" and s ~= "0" and s ~= "-" then | |
+ | local sniffer = "{{REVISIONID}}" | ||
if lazy then | if lazy then | ||
show = "" | show = "" | ||
lazy = false | lazy = false | ||
end | end | ||
− | if not frame then | + | if not TemplatePar.frame then |
− | frame = mw.getCurrentFrame() | + | TemplatePar.frame = mw.getCurrentFrame() |
end | end | ||
− | if frame:preprocess( | + | if TemplatePar.frame:preprocess( sniffer ) == "" then |
if s == "1" then | if s == "1" then | ||
show = "*" | show = "*" | ||
Zeile 565: | Zeile 692: | ||
show = s | show = s | ||
end | end | ||
+ | learn = true | ||
end | end | ||
end | end | ||
Zeile 581: | Zeile 709: | ||
local i | local i | ||
if not show or show == "*" then | 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 | end | ||
i = show:find( "@@@", 1, true ) | i = show:find( "@@@", 1, true ) | ||
Zeile 593: | Zeile 742: | ||
r = show | r = show | ||
end | 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 | ||
+ | local link | ||
if opt.errNS then | if opt.errNS then | ||
local ns = mw.title.getCurrentTitle().namespace | local ns = mw.title.getCurrentTitle().namespace | ||
Zeile 603: | Zeile 756: | ||
local spaces = string.format( " %s ", opt.errNS ) | local spaces = string.format( " %s ", opt.errNS ) | ||
if spaces:match( space ) then | if spaces:match( space ) then | ||
− | + | link = true | |
end | end | ||
elseif st == "table" then | elseif st == "table" then | ||
for i = 1, #opt.errNS do | for i = 1, #opt.errNS do | ||
if opt.errNS[ i ] == ns then | if opt.errNS[ i ] == ns then | ||
− | + | link = true | |
break -- for i | break -- for i | ||
end | end | ||
end -- for i | end -- for i | ||
end | end | ||
+ | else | ||
+ | link = true | ||
end | end | ||
− | if | + | if link then |
− | + | local cats, i | |
− | |||
if not r then | if not r then | ||
r = "" | r = "" | ||
Zeile 625: | Zeile 779: | ||
end | end | ||
end | end | ||
− | + | cats = mw.text.split( s, "%s*#%s*" ) | |
− | |||
for i = 1, #cats do | for i = 1, #cats do | ||
s = mw.text.trim( cats[ i ] ) | s = mw.text.trim( cats[ i ] ) | ||
Zeile 804: | Zeile 957: | ||
-- options.mandatory | -- options.mandatory | ||
-- options.optional | -- options.optional | ||
− | -- frame -- object, or false | + | -- 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 814: | 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 836: | Zeile 995: | ||
end | end | ||
end | end | ||
− | return finalize( r, options | + | return finalize( r, options ) |
end -- form() | end -- form() | ||
Zeile 907: | Zeile 1.066: | ||
-- false if valid or no answer permitted | -- false if valid or no answer permitted | ||
-- Uses: | -- Uses: | ||
− | -- mw.text.trim() | + | -- mw.text.trim() |
-- format() | -- format() | ||
-- failure() | -- failure() | ||
Zeile 914: | Zeile 1.073: | ||
local story = assignment.args[ access ] or "" | local story = assignment.args[ access ] or "" | ||
if type( access ) == "number" then | if type( access ) == "number" then | ||
− | story = mw.text.trim( story ) | + | story = mw.text.trim( story ) |
end | end | ||
if type( options ) ~= "table" then | if type( options ) ~= "table" then | ||
Zeile 986: | Zeile 1.145: | ||
end | end | ||
if r then | if r then | ||
− | r = finalize( r, options | + | r = finalize( r, options ) |
else | else | ||
s = frame.args[ 1 ] or "" | s = frame.args[ 1 ] or "" | ||
Zeile 994: | Zeile 1.153: | ||
end | end | ||
if action == "valid" then | if action == "valid" then | ||
− | r = TemplatePar.valid( s, options | + | r = TemplatePar.valid( s, options ) |
elseif action == "assert" then | elseif action == "assert" then | ||
r = TemplatePar.assert( s, "", options ) | r = TemplatePar.assert( s, "", options ) | ||
Zeile 1.104: | Zeile 1.263: | ||
− | TemplatePar.valid = function ( access, options | + | 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 | -- Check validity of one particular template parameter | ||
-- Precondition: | -- Precondition: | ||
Zeile 1.110: | Zeile 1.305: | ||
-- string or number | -- string or number | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
− | |||
-- Postcondition: | -- Postcondition: | ||
-- Return string with error message as configured; | -- Return string with error message as configured; | ||
-- 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() | ||
Zeile 1.140: | Zeile 1.335: | ||
params = TemplatePar.downcase( options ) | params = TemplatePar.downcase( options ) | ||
else | else | ||
− | params = frame:getParent() | + | 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.179: | Zeile 1.377: | ||
-- furnish() | -- furnish() | ||
return furnish( frame, "assert" ) | return furnish( frame, "assert" ) | ||
− | end -- .assert() | + | end -- p.assert() |
Zeile 1.216: | Zeile 1.414: | ||
end | end | ||
return r or "" | return r or "" | ||
− | end -- .check() | + | end -- p.check() |
Zeile 1.227: | Zeile 1.425: | ||
-- TemplatePar.count() | -- TemplatePar.count() | ||
return tostring( TemplatePar.count() ) | return tostring( TemplatePar.count() ) | ||
− | end -- .count() | + | end -- p.count() |
Zeile 1.238: | Zeile 1.436: | ||
-- TemplatePar.countNotEmpty() | -- TemplatePar.countNotEmpty() | ||
return tostring( TemplatePar.countNotEmpty() ) | return tostring( TemplatePar.countNotEmpty() ) | ||
− | end -- .countNotEmpty() | + | end -- p.countNotEmpty() |
Zeile 1.244: | 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.266: | Zeile 1.467: | ||
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.349: | Zeile 1.551: | ||
r = failure( "invalid", errValues, options ) | r = failure( "invalid", errValues, options ) | ||
end | end | ||
− | r = finalize( r, options | + | r = finalize( r, options ) |
end | end | ||
end | end | ||
return r or "" | return r or "" | ||
− | end -- .match() | + | end -- p.match() |
Zeile 1.366: | 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.375: | 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
Die Dokumentation für dieses Modul kann unter Modul:TemplatePar/Doku erstellt werden
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 = "{{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
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|%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[[Category:%s]]", r, s )
end
end -- for i
end
end
end
return r
end -- finalize()
local function finder( haystack, needle )
-- Find needle in haystack sequence
-- Precondition:
-- haystack -- table; sequence of key names, downcased if low
-- needle -- any; key name
-- Postcondition:
-- Return true iff found
local i
for i = 1, #haystack do
if haystack[ i ] == needle then
return true
end
end -- for i
return false
end -- finder()
local function fix( valid, duty, got, options )
-- Perform parameter analysis
-- Precondition:
-- valid -- table; unique sequence of known parameters
-- duty -- table; sequence of mandatory parameters
-- got -- table; sequence of current parameters
-- options -- table or nil; optional details
-- Postcondition:
-- Return string as configured; empty if valid
-- Uses:
-- finder()
-- fault()
-- failure()
-- fed()
local k, v
local r = false
for k, v in pairs( got ) do
if not finder( valid, k ) then
r = fault( r, k )
end
end -- for k, v
if r then
r = failure( "unknown",
string.format( "'%s'", r ),
options )
else -- all names valid
local i, s
for i = 1, #duty do
s = duty[ i ]
if not fed( got, s ) then
r = fault( r, s )
end
end -- for i
if r then
r = failure( "undefined", r, options )
else -- all mandatory present
for i = 1, #duty do
s = duty[ i ]
if not got[ s ] then
r = fault( r, s )
end
end -- for i
if r then
r = failure( "empty", r, options )
end
end
end
return r
end -- fix()
local function flat( collection, options )
-- Return all table elements with downcased string
-- Precondition:
-- collection -- table; k=v pairs
-- options -- table or nil; optional messaging details
-- Postcondition:
-- Return table, may be empty; or string with error message.
-- Uses:
-- mw.ustring.lower()
-- fault()
-- failure()
local k, v
local r = { }
local e = false
for k, v in pairs( collection ) do
if type ( k ) == "string" then
k = mw.ustring.lower( k )
if r[ k ] then
e = fault( e, k )
end
end
r[ k ] = v
end -- for k, v
if e then
r = failure( "multiSpell", e, options )
end
return r
end -- flat()
local function fold( options )
-- Merge two tables, create new sequence if both not empty
-- Precondition:
-- options -- table; details
-- options.mandatory sequence to keep unchanged
-- options.optional sequence to be appended
-- options.low downcased expected
-- Postcondition:
-- Return merged table, or message string if error
-- Uses:
-- finder()
-- fault()
-- failure()
-- flat()
local i, e, r, s
local base = options.mandatory
local extend = options.optional
if #base == 0 then
if #extend == 0 then
r = { }
else
r = extend
end
else
if #extend == 0 then
r = base
else
e = false
for i = 1, #extend do
s = extend[ i ]
if finder( base, s ) then
e = fault( e, s )
end
end -- for i
if e then
r = failure( "dupOpt", e, options )
else
r = { }
for i = 1, #base do
table.insert( r, base[ i ] )
end -- for i
for i = 1, #extend do
table.insert( r, extend[ i ] )
end -- for i
end
end
end
if options.low and type( r ) == "table" then
r = flat( r, options )
end
return r
end -- fold()
local function form( light, options, frame )
-- Run parameter analysis on current environment
-- Precondition:
-- light -- true: template transclusion; false: #invoke
-- options -- table or nil; optional details
-- options.mandatory
-- options.optional
-- frame -- object; #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 <br />
-- false or nil: throw error with message
-- options -- table; optional details
-- Postcondition:
-- Return string with error message as configured;
-- false if valid
-- Uses:
-- format()
local r = format( analyze, options )
if ( r ) then
if ( type( append ) == "string" ) then
if ( append ~= "" ) then
r = string.format( "%s<br />%s", append, r )
end
else
error( r, 0 )
end
end
return r
end -- TemplatePar.assert()
TemplatePar.check = function ( options )
-- Run parameter analysis on current template environment
-- Precondition:
-- options -- table or nil; optional details
-- options.mandatory
-- options.optional
-- Postcondition:
-- Return string with error message as configured;
-- false if valid
-- Uses:
-- form()
return form( true, options, false )
end -- TemplatePar.check()
TemplatePar.count = function ()
-- Return number of template parameters
-- Postcondition:
-- Return number, starting at 0
-- Uses:
-- mw.getCurrentFrame()
-- frame:getParent()
local k, v
local r = 0
local t = mw.getCurrentFrame():getParent()
local o = t.args
for k, v in pairs( o ) do
r = r + 1
end -- for k, v
return r
end -- TemplatePar.count()
TemplatePar.countNotEmpty = function ()
-- Return number of template parameters with more than whitespace
-- Postcondition:
-- Return number, starting at 0
-- Uses:
-- mw.getCurrentFrame()
-- frame:getParent()
local k, v
local r = 0
local t = mw.getCurrentFrame():getParent()
local o = t.args
for k, v in pairs( o ) do
if not v:match( "^%s*$" ) then
r = r + 1
end
end -- for k, v
return r
end -- TemplatePar.countNotEmpty()
TemplatePar.downcase = function ( options )
-- Return all template parameters with downcased name
-- Precondition:
-- options -- table or nil; optional messaging details
-- Postcondition:
-- Return table, may be empty; or string with error message.
-- Uses:
-- mw.getCurrentFrame()
-- frame:getParent()
-- flat()
local t = mw.getCurrentFrame():getParent()
return flat( t.args, options )
end -- TemplatePar.downcase()
TemplatePar.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