Module:TemplatePar: Difference between revisions
From Neodyland Wiki
More actions
mNo edit summary |
No edit summary |
||
| (5 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
-- | local TemplatePar = { serial = "2023-03-20", | ||
* countNotEmpty* downcase* match* valid* verify* TemplatePar()]=] | suite = "TemplatePar", | ||
item = 15393417, | |||
local messagePrefix = "lua-module-TemplatePar-" | globals = { DateTime = 20652535, | ||
FileMedia = 24765326, | |||
Multilingual = 47541920, | |||
TemplUtl = 52364930, | |||
URLutil = 10859193 } } | |||
--[=[ | |||
Template parameter utility | |||
* assert | |||
* check | |||
* count | |||
* countNotEmpty | |||
* downcase() | |||
* duplicates | |||
* match | |||
* valid | |||
* verify() | |||
* TemplatePar() | |||
* failsafe() | |||
]=] | |||
local Local = { frame = false } | |||
local Failsafe = TemplatePar | |||
local GlobalMod = Local | |||
-- Module globals | |||
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", | ||
| Line 11: | Line 40: | ||
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", | ||
unavailable = "Error in template * parameter name missing", | |||
undefined = "Error in template * mandatory parameter missing", | undefined = "Error in template * mandatory parameter missing", | ||
unknown = "Error in template * unknown parameter name", | unknown = "Error in template * unknown parameter name", | ||
unknownRule = "#invoke:TemplatePar unknown rule"} | unknownRule = "#invoke:TemplatePar unknown rule" | ||
} | |||
Local.patterns = { | |||
[ "ASCII" ] = "^[ -~]*$", | |||
[ "ASCII+" ] = "^[ -~]+$", | |||
[ "ASCII+1" ] = "^[!-~]+$", | |||
[ "n" ] = "^[%-]?[0-9]*$", | |||
[ "ASCII" ] | |||
[ "ASCII+1" ] = "^[!-~]+$", | |||
[ "n>0" ] = "^[0-9]*[1-9][0-9]*$", | [ "n>0" ] = "^[0-9]*[1-9][0-9]*$", | ||
[ "N+" ] = "^%-?[1-9][0-9]*$", | [ "N+" ] = "^[%-]?[1-9][0-9]*$", | ||
[ "x" ] = "^[0-9A-Fa-f]*$", | [ "N>0" ] = "^[1-9][0-9]*$", | ||
[ "X" ] = "^[0-9A-F]*$", | [ "x" ] = "^[0-9A-Fa-f]*$", | ||
[ "0,0" ] = "^%-?[0-9]*,?[0-9]*$", | [ "x+" ] = "^[0-9A-Fa-f]+$", | ||
[ "0,0+" ] = "^%-?[0-9]+,[0-9]+$", | [ "X" ] = "^[0-9A-F]*$", | ||
[ "0,0+?" ] = "^%-?[0-9]+,?[0-9]*$", | [ "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]+$", | [ "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]*$", | ||
[ "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]+$", | ||
[ "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%+/]*$", | ||
[ "base64+" ] = "^[A-Za-z0-9%+/]+$", | [ "base64+" ] = "^[A-Za-z0-9%+/]+$", | ||
[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}% | [ "aa" ] = "[%a%a].*[%a%a]", | ||
1, 31, 127 ), | [ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$", | ||
local | 1, 31, 127 ), | ||
-- Is any CJK character present? | [ "ref" ] = string.format( "%c'%c`UNIQ%s%sref%s%s%sQINU`%c'%c", | ||
-- Postcondition: | 127, 34, "%-", "%-", "%-", "%x+", | ||
-- >< patternCJK | "%-", 34, 127 ), | ||
-- mw.ustring.match() | [ "+" ] = "%S" | ||
patternCJK = mw.ustring.char( 91, | } | ||
Local.boolean = { ["1"] = true, | |||
["true"] = true, | |||
y = true, | |||
yes = true, | |||
on = true, | |||
["0"] = true, | |||
["false"] = true, | |||
["-"] = true, | |||
n = true, | |||
no = true, | |||
off = true } | |||
Local.patternCJK = false | |||
local foreignModule = function ( access, advanced, append, alt, alert ) | |||
-- Fetch global module | |||
-- Precondition: | |||
-- access -- string, with name of base module | |||
-- advanced -- true, for require(); else mw.loadData() | |||
-- append -- string, with subpage part, if any; or false | |||
-- alt -- number, of wikidata item of root; or false | |||
-- alert -- true, for throwing error on data problem | |||
-- Postcondition: | |||
-- Returns whatever, probably table | |||
-- 2020-01-01 | |||
local storage = access | |||
local finer = function () | |||
if append then | |||
storage = string.format( "%s/%s", | |||
storage, | |||
append ) | |||
end | |||
end | |||
local fun, lucky, r, suited | |||
if advanced then | |||
fun = require | |||
else | |||
fun = mw.loadData | |||
end | |||
GlobalMod.globalModules = GlobalMod.globalModules or { } | |||
suited = GlobalMod.globalModules[ access ] | |||
if not suited then | |||
finer() | |||
lucky, r = pcall( fun, "Module:" .. storage ) | |||
end | |||
if not lucky then | |||
if not suited and | |||
type( alt ) == "number" and | |||
alt > 0 then | |||
suited = string.format( "Q%d", alt ) | |||
suited = mw.wikibase.getSitelink( suited ) | |||
GlobalMod.globalModules[ access ] = suited or true | |||
end | |||
if type( suited ) == "string" then | |||
storage = suited | |||
finer() | |||
lucky, r = pcall( fun, storage ) | |||
end | |||
if not lucky and alert then | |||
error( "Missing or invalid page: " .. storage ) | |||
end | |||
end | |||
return r | |||
end -- foreignModule() | |||
local function Foreign( access ) | |||
-- Access standardized library | |||
-- Precondition: | |||
-- access -- string, with name of base module | |||
-- Postcondition: | |||
-- Return library table, or not | |||
-- Uses: | |||
local r | |||
if Local[ access ] then | |||
r = Local[ access ] | |||
else | |||
local bib = foreignModule( access, | |||
true, | |||
false, | |||
TemplatePar.globals[ access ], | |||
false ) | |||
if type( bib ) == "table" and | |||
type( bib[ access ] ) == "function" then | |||
bib = bib[ access ]() | |||
if type( bib ) == "table" then | |||
r = bib | |||
Local[ access ] = bib | |||
end | |||
end | |||
end | |||
return r | |||
end -- Foreign() | |||
local function containsCJK( analyse ) | |||
-- Is any CJK character present? | |||
-- Precondition: | |||
-- analyse -- string | |||
-- Postcondition: | |||
-- Return false iff no CJK present | |||
-- Uses: | |||
-- >< Local.patternCJK | |||
-- mw.ustring.char() | |||
-- mw.ustring.match() | |||
local r = false | |||
if not Local.patternCJK then | |||
Local.patternCJK = mw.ustring.char( 91, | |||
13312, 45, 40959, | 13312, 45, 40959, | ||
131072, 45, 178207, | 131072, 45, 178207, | ||
93 ) | 93 ) | ||
if mw.ustring.match( | end | ||
end -- containsCJK()local function factory( say ) | if mw.ustring.match( analyse, Local.patternCJK ) then | ||
-- Retrieve localized message string in content language | r = true | ||
-- say -- string; message ID | end | ||
-- Return some message string | return r | ||
-- > | end -- containsCJK() | ||
local function facility( accept, attempt ) | |||
-- Check string as possible file name or other source page | |||
-- Precondition: | |||
-- accept -- string; requirement | |||
-- file | |||
-- file+ | |||
-- file: | |||
-- file:+ | |||
-- image | |||
-- image+ | |||
-- image: | |||
-- image:+ | |||
-- attempt -- string; to be tested | |||
-- Postcondition: | |||
-- Return error keyword, or false | |||
-- Uses: | |||
-- Module:FileMedia | |||
-- Foreign() | |||
-- FileMedia.isFile() | |||
-- FileMedia.isType() | |||
local r | |||
if attempt and attempt ~= "" then | |||
local FileMedia = Foreign( "FileMedia" ) | |||
if FileMedia and type( FileMedia.isFile ) == "function" | |||
and type( FileMedia.isType ) == "function" 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: | |||
-- > Local.messagePrefix | |||
-- > Local.L10nDef | |||
-- mw.message.new() | -- mw.message.new() | ||
-- mw.language.getContentLanguage() | |||
local m = mw.message.new( messagePrefix .. say ) | -- Module:Multilingual | ||
if m:isBlank() then | -- Foreign() | ||
l10n = | -- TemplatePar.framing() | ||
m:inLanguage( c ) | -- Multilingual.tabData() | ||
r = "((( | local m = mw.message.new( Local.messagePrefix .. say ) | ||
local function | local r = false | ||
-- | if m:isBlank() then | ||
-- Precondition: | local c = mw.language.getContentLanguage():getCode() | ||
-- | local l10n = Local.L10nDef[ c ] | ||
-- Return | if l10n then | ||
-- mw. | r = l10n[ say ] | ||
end -- | else | ||
-- Submit localized error message | local MultiL = Foreign( "Multilingual" ) | ||
if MultiL and type( MultiL.tabData ) == "function" then | |||
local lang | |||
r, lang = MultiL.tabData( "I18n/Module:TemplatePar", | |||
say, | |||
false, | |||
TemplatePar.framing() ) | |||
end | |||
end | |||
if not r then | |||
r = Local.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 faculty( accept, attempt ) | |||
-- Check string as possible boolean | |||
-- Precondition: | |||
-- accept -- string; requirement | |||
-- boolean | |||
-- boolean+ | |||
-- attempt -- string; to be tested | |||
-- Postcondition: | |||
-- Return error keyword, or false | |||
-- Uses: | |||
-- Module:TemplUtl | |||
-- Foreign() | |||
-- TemplUtl.faculty() | |||
local r | |||
r = mw.text.trim( attempt ):lower() | |||
if r == "" then | |||
if accept == "boolean+" then | |||
r = "empty" | |||
else | |||
r = false | |||
end | |||
elseif Local.boolean[ r ] or r:match( "^[01%-]+$" ) then | |||
r = false | |||
else | |||
local TemplUtl = Foreign( "TemplUtl" ) | |||
if TemplUtl and type( TemplUtl.faculty ) == "function" then | |||
r = TemplUtl.faculty( r, "-" ) | |||
if r == "-" then | |||
r = "invalid" | |||
else | |||
r = false | |||
end | |||
else | |||
r = "invalid" | |||
end | |||
end | |||
return r | |||
end -- faculty() | |||
local function failure( spec, suspect, options ) | |||
-- Submit localized error message | |||
-- Precondition: | |||
-- spec -- string; message ID | -- spec -- string; message ID | ||
-- suspect -- string or nil; additional information | -- suspect -- string or nil; additional information | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
-- options.template | -- options.template | ||
-- Return string | -- Postcondition: | ||
local r = factory( spec ) | -- Return string | ||
-- Uses: | |||
-- factory() | |||
local r = factory( spec ) | |||
if type( options ) == "table" then | |||
if type( options.template ) == "string" then | if type( options.template ) == "string" then | ||
if #options.template > 0 then | if #options.template > 0 then | ||
r = r .. " (" .. | r = string.format( "%s (%s)", r, options.template ) | ||
end | end | ||
return | end | ||
-- Add key to collection string and insert separator | 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: | |||
-- Module:Multilingual | |||
-- Foreign() | |||
-- Multilingual.isLang() | |||
local r | |||
if attempt and attempt ~= "" then | |||
local MultiL = Foreign( "Multilingual" ) | |||
if MultiL and type( MultiL.isLang ) == "function" 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 MultiL.isLang( group[ i ], lazy ) then | |||
r = "invalid" | |||
break -- for i | |||
end | |||
end -- for i | |||
elseif MultiL.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: | |||
-- Module:URLutil | |||
-- Foreign() | |||
-- URLutil.isWebURL() | |||
local r | |||
if attempt and attempt ~= "" then | |||
local URLutil = Foreign( "URLutil" ) | |||
if URLutil and type( URLutil.isWebURL ) == "function" 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 fast( accept, attempt ) | |||
-- Check string as possible date or time | |||
-- Precondition: | |||
-- accept -- string; requirement | |||
-- datetime | |||
-- datetime+ | |||
-- datetime/y | |||
-- datetime/y+ | |||
-- datetime/ym | |||
-- datetime/ym+ | |||
-- datetime/ymd | |||
-- datetime/ymd+ | |||
-- attempt -- string; to be tested | |||
-- Postcondition: | |||
-- Return error keyword, or false | |||
-- Uses: | |||
-- Module:DateTime | |||
-- Foreign() | |||
-- DateTime.DateTime() | |||
local r | |||
r = mw.text.trim( attempt ) | |||
if r == "" then | |||
if accept:find( "+", 1, true ) then | |||
r = "empty" | |||
else | |||
r = false | |||
end | |||
else | |||
local DateTime = Foreign( "DateTime" ) | |||
if type( DateTime ) == "table" then | |||
local d = DateTime( attempt ) | |||
if type( d ) == "table" then | |||
if accept:find( "/", 1, true ) then | |||
r = "invalid" | |||
if accept:sub( 1, 10 ) == "datetime/y" then | |||
if d.year then | |||
r = false | |||
if accept:sub( 1, 11 ) == "datetime/ym" then | |||
if d.month then | |||
if accept:sub( 1, 12 ) | |||
== "datetime/ymd" then | |||
if not d.dom then | |||
r = "invalid" | |||
end | |||
end | |||
else | |||
r = "invalid" | |||
end | |||
end | |||
end | |||
end | |||
else | |||
r = false | |||
end | |||
else | |||
r = "invalid" | |||
end | |||
else | |||
r = "invalid" | |||
end | |||
end | |||
return r | |||
end -- fast() | |||
local function fault( store, key ) | |||
-- Add key to collection string and insert separator | |||
-- Precondition: | |||
-- store -- string or nil or false; collection string | -- store -- string or nil or false; collection string | ||
-- key -- string or number; to be appended | -- key -- string or number; to be appended | ||
-- Return string; extended | -- Postcondition: | ||
if type( key ) == "number" then | -- Return string; extended | ||
s = key | local r | ||
r = s | local s | ||
local function feasible( analyze, options, abbr ) | if type( key ) == "number" then | ||
-- Precondition: | 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 -- table or nil; optional details | ||
-- options.pattern | -- options.pattern | ||
-- options.key | |||
-- options.say | -- options.say | ||
-- abbr -- true: abbreviated error message | -- abbr -- true: abbreviated error message | ||
-- 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: | ||
-- | -- > Local.patterns | ||
local s | -- failure() | ||
if type( options.pattern ) == "string" then | -- mw.text.trim() | ||
r = failure( "dupRule", false, options ) | -- faculty() | ||
scan = options.pattern | -- fast() | ||
-- facility() | |||
-- familiar() | |||
-- far() | |||
-- fair() | |||
-- containsCJK() | |||
local r = false | |||
local s = false | |||
local show = nil | |||
local scan = false | |||
local stuff = mw.text.trim( analyze ) | |||
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 | if type( options.key ) == "string" then | ||
s = mw.text.trim( options.key ) | s = mw.text.trim( options.key ) | ||
end | else | ||
s = "+" | |||
end | |||
if s ~= "*" then | |||
scan = Local.patterns[ s ] | |||
end | |||
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 | if not stuff:match( "[0-9]" ) and | ||
scan = false | not stuff:match( "^%s*$" ) then | ||
show = " | scan = false | ||
if abbr then | if options.say then | ||
show = string.format( ""%s"", options.say ) | |||
end | |||
if abbr then | |||
r = show | |||
else | else | ||
r = failure( "invalid", show, options ) | r = failure( "invalid", show, options ) | ||
end | end | ||
end | |||
end | |||
elseif s ~= "*" then | elseif s ~= "*" then | ||
local op, n, plus = s:match( "([<!>]=?)([-0-9][%S]*)(+?)" ) | local op, n, plus = s:match( "([<!=>]=?)([-0-9][%S]*)(+?)" ) | ||
if op then | if op then | ||
local i = tonumber( | n = tonumber( n ) | ||
if n then | |||
local i = tonumber( stuff ) | |||
if i then | |||
if op == "<" then | if op == "<" then | ||
i = ( i < n ) | i = ( i < n ) | ||
| Line 142: | Line 647: | ||
elseif op == ">=" then | elseif op == ">=" then | ||
i = ( i >= n ) | i = ( i >= n ) | ||
elseif op == "==" then | |||
i = ( i == n ) | |||
elseif op == "!=" then | elseif op == "!=" then | ||
i = ( i ~= n ) | i = ( i ~= n ) | ||
n = false | else | ||
end | n = false | ||
r = "invalid" | end | ||
elseif plus then | end | ||
end | if not i then | ||
r = "unknownRule" | r = "invalid" | ||
end | |||
elseif plus then | |||
r = "undefined" | |||
end | |||
elseif s:match( "^boolean%+?$" ) then | |||
r = faculty( s, stuff ) | |||
n = true | |||
elseif s:match( "^datetime/?y?m?d?%+?$" ) then | |||
r = fast( s, stuff ) | |||
n = true | |||
elseif s:match( "^image%+?:?$" ) or | |||
s:match( "^file%+?:?$" ) then | |||
r = facility( s, stuff ) | |||
n = true | |||
elseif s:match( "langs?W?%+?" ) then | |||
r = familiar( s, stuff ) | |||
n = true | |||
elseif s:match( "url%+?" ) then | |||
r = far( s, stuff ) | |||
n = true | |||
end | |||
-- datetime+ | |||
-- iso8631+ | |||
-- line+ | |||
if not n and not r then | |||
r = "unknownRule" | |||
end | |||
if r then | |||
if options.say then | if options.say then | ||
show = " | show = string.format( ""%s" %s", options.say, s ) | ||
show = s | else | ||
r = show | show = s | ||
r = failure( r, show, options ) | end | ||
end | if abbr then | ||
local legal, got = pcall( | r = show | ||
if not got then | else | ||
got = containsCJK( | r = failure( r, show, options ) | ||
if not got then | end | ||
show = " | end | ||
if abbr then | end | ||
end | |||
if scan then | |||
local legal, got = pcall( fair, stuff, scan ) | |||
if legal then | |||
if not got then | |||
if s == "aa" then | |||
got = containsCJK( stuff ) | |||
end | |||
if not got then | |||
if options.say then | |||
show = string.format( ""%s"", options.say ) | |||
end | |||
if abbr then | |||
r = show | |||
else | else | ||
r = failure( "invalid", show, options ) | r = failure( "invalid", show, options ) | ||
end | end | ||
end | |||
end | |||
else | |||
r = failure( "badPattern", | r = failure( "badPattern", | ||
string.format( "%s *** %s", scan, got ), | |||
options ) | options ) | ||
end -- feasible()local function fed( haystack, needle ) | end | ||
-- Find needle in haystack map | end | ||
return r | |||
end -- feasible() | |||
local function fed( haystack, needle ) | |||
-- Find needle in haystack map | |||
-- Precondition: | |||
-- haystack -- table; map of key values | -- haystack -- table; map of key values | ||
-- needle -- any; identifier | -- needle -- any; identifier | ||
-- Return true iff found | -- Postcondition: | ||
for k, v in pairs( haystack ) do | -- Return true iff found | ||
local k, v, r | |||
end -- fed()local function fetch( light, options ) | for k, v in pairs( haystack ) do | ||
-- Return regular table with all parameters | 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 | -- light -- true: template transclusion; false: #invoke | ||
-- options -- table; optional details | -- options -- table; optional details | ||
-- Postcondition: | -- options.low | ||
-- Uses: | -- Postcondition: | ||
-- frame:getParent() | -- Return table; whitespace-only values as false | ||
if options.low then | -- Uses: | ||
g = | -- TemplatePar.downcase() | ||
g = g:getParent() | -- TemplatePar.framing() | ||
if type( g ) == "table" then | -- frame:getParent() | ||
for k, v in pairs( g ) do | local g, k, v | ||
if v:match( "^%s*$" ) then | local r = { } | ||
end | if options.low then | ||
if type( k ) == "number" then | g = TemplatePar.downcase( options ) | ||
end | else | ||
r = g | g = TemplatePar.framing() | ||
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 ) | local function figure( append, options ) | ||
-- Extend options by rule from #invoke strings | -- Extend options by rule from #invoke strings | ||
-- Precondition: | |||
-- append -- string or nil; requested rule | -- append -- string or nil; requested rule | ||
-- options -- table; details | -- options -- table; details | ||
-- ++ .pattern | -- ++ .key | ||
-- Return sequence table | -- ++ .pattern | ||
-- Postcondition: | |||
-- Return sequence table | |||
local r = options | |||
if type( append ) == "string" then | if type( append ) == "string" then | ||
local story = mw.text.trim( append ) | local story = mw.text.trim( append ) | ||
| Line 203: | Line 806: | ||
if type( sub ) == "string" then | if type( sub ) == "string" then | ||
sub = sub:gsub( "%%!", "|" ) | sub = sub:gsub( "%%!", "|" ) | ||
:gsub( "%%%(%(", "{{" ) | |||
:gsub( "%%%)%)", "}}" ) | |||
options.pattern = sub | :gsub( "\\n", string.char( 10 ) ) | ||
options.key = story | options.pattern = sub | ||
end | options.key = nil | ||
-- Split requirement string separated by '=' | 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 | -- specified -- string or nil; requested parameter set | ||
-- Postcondition: | -- Postcondition: | ||
-- mw.text.split() | -- Return sequence table | ||
r = mw.text.split( specified, "%s*=%s*" ) | -- Uses: | ||
s = r[ i ] | -- mw.text.split() | ||
table.remove( r, i ) | local r | ||
else | if specified then | ||
local function finalize( submit, options ) | 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: | -- Precondition: | ||
-- 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.template | -- options.preview | ||
-- Return string or false | -- options.cat | ||
local r = false | -- options.template | ||
if type( options ) == "table" then | -- Postcondition: | ||
opt = { } | -- Return string or false | ||
-- Uses: | |||
-- TemplatePar.framing() | |||
-- 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 TemplatePar.framing():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 | if not opt.cat then | ||
r = | r = string.format( "%s %s", | ||
else | submit, factory( "noMSGnoCAT" ) ) | ||
end | |||
s = opt.cat | else | ||
if not r then | r = submit | ||
end | |||
if r and not lazy then | |||
local i | |||
if not show or show == "*" then | |||
return | local e = mw.html.create( "span" ) | ||
-- Find needle in haystack sequence | :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 | -- haystack -- table; sequence of key names, downcased if low | ||
-- needle -- any; key name | -- needle -- any; key name | ||
-- Return true iff found | -- Postcondition: | ||
if haystack[ i ] == needle then | -- Return true iff found | ||
end -- for i | local i | ||
local function fix( valid, duty, got, options ) | 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: | -- Precondition: | ||
-- valid -- table; unique sequence of known parameters | -- valid -- table; unique sequence of known parameters | ||
-- duty -- table; sequence of mandatory parameters | -- duty -- table; sequence of mandatory parameters | ||
-- got -- table; sequence of current parameters | -- got -- table; sequence of current parameters | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
-- Return string as configured; empty if valid | -- Postcondition: | ||
-- finder() | -- Return string as configured; empty if valid | ||
-- Uses: | |||
if not finder( valid, k ) then | -- finder() | ||
end -- for k, v | -- fault() | ||
r = failure( "unknown", | -- failure() | ||
else -- all names valid | -- fed() | ||
s = duty[ i ] | local r = false | ||
r = fault( r, s ) | local lack | ||
if r then | for k, v in pairs( got ) do | ||
else -- all mandatory present | if k == "" then | ||
s = duty[ i ] | lack = true | ||
r = fault( r, s ) | break -- for k, v | ||
if r then | elseif not finder( valid, k ) then | ||
end | r = fault( r, k ) | ||
end | |||
end -- for k, v | |||
if lack then | |||
r = failure( "unavailable", false, options ) | |||
elseif 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 ) | local function flat( collection, options ) | ||
-- Return all table elements with downcased string | -- Return all table elements with downcased string | ||
-- Precondition: | |||
-- collection -- table; k=v pairs | -- collection -- table; k=v pairs | ||
-- options -- table or nil; optional messaging details | -- options -- table or nil; optional messaging details | ||
-- Postcondition: | -- Postcondition: | ||
-- Return table, may be empty; or string with error message. | -- Return table, may be empty; or string with error message. | ||
-- mw.ustring.lower() | -- Uses: | ||
local k, v | -- mw.ustring.lower() | ||
for k, v in pairs( collection ) do | -- fault() | ||
k = mw.ustring.lower( k ) | -- failure() | ||
e = fault( e, k ) | local k, v | ||
end -- for k, v | local r = { } | ||
end | 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 | -- Merge two tables, create new sequence if both not empty | ||
-- Precondition: | -- Precondition: | ||
-- options -- table; details | |||
-- options.mandatory sequence to keep unchanged | -- options.mandatory sequence to keep unchanged | ||
-- options.optional sequence to be appended | -- options.optional sequence to be appended | ||
-- options.low downcased expected | -- options.low downcased expected | ||
-- Postcondition: | -- Postcondition: | ||
-- Uses: | -- Return merged table, or message string if error | ||
-- flat() | -- Uses: | ||
local extend = options.optional | -- finder() | ||
if #extend == 0 then | -- fault() | ||
r = extend | -- failure() | ||
r = base | -- flat() | ||
for i = 1, #extend do | local i, e, r, s | ||
if finder( base, s ) then | local base = options.mandatory | ||
end | local extend = options.optional | ||
r = failure( "dupOpt", e, options ) | if #base == 0 then | ||
r = { } | if #extend == 0 then | ||
table.insert( r, base[ i ] ) | 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 | for i = 1, #extend do | ||
table.insert( r, extend[ i ] ) | table.insert( r, extend[ i ] ) | ||
end | end -- for i | ||
if options.low and type( r ) == "table" then | end | ||
end | end | ||
-- Run parameter analysis on current environment | 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 | -- light -- true: template transclusion; false: #invoke | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
-- options.mandatory | -- options.mandatory | ||
-- Postcondition: | -- options.optional | ||
-- false if valid | -- frame -- object; #invoke environment, or false | ||
-- fix() | -- Postcondition: | ||
-- Return string with error message as configured; | |||
-- false if valid | |||
-- Uses: | |||
-- TemplatePar.framing() | |||
-- fold() | |||
-- fetch() | |||
-- fix() | |||
-- finalize() | |||
local duty, r | |||
if frame then | |||
TemplatePar.framing( frame ) | |||
end | |||
if type( options ) == "table" then | if type( options ) == "table" then | ||
if type( options.mandatory ) ~= "table" then | if type( options.mandatory ) ~= "table" then | ||
options.mandatory = { } | options.mandatory = { } | ||
end | |||
duty = options.mandatory | |||
if type( options.optional ) ~= "table" then | if type( options.optional ) ~= "table" then | ||
options.optional = { } | options.optional = { } | ||
options = { } | end | ||
if type( r ) == "table" then | r = fold( options ) | ||
else | |||
options = { } | |||
duty = { } | |||
r = { } | |||
end | |||
if type( r ) == "table" then | |||
local got = fetch( light, options ) | |||
if type( got ) == "table" then | if type( got ) == "table" then | ||
r = fix( r, duty, got, options ) | r = fix( r, duty, got, options ) | ||
end | else | ||
local function format( analyze, options ) | r = got | ||
-- Precondition: | 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 -- table or nil; optional details | ||
-- options.say | -- options.say | ||
-- options.max | -- options.min | ||
-- options.max | |||
-- 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 | ||
-- feasible() | -- Uses: | ||
local r | -- feasible() | ||
-- failure() | |||
local r = feasible( analyze, options, false ) | |||
local show | |||
if options.min and not r then | if options.min and not r then | ||
if type( options.min ) == "number" then | if type( options.min ) == "number" then | ||
| Line 331: | Line 1,217: | ||
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 | |||
if #analyze < options.min and not r then | if #analyze < options.min and not r then | ||
show = " <" .. options.min | show = " <" .. options.min | ||
show = | if options.say then | ||
r = failure( "tooShort", show, options ) | show = string.format( "%s "%s"", show, options.say ) | ||
else | end | ||
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 type( options.max ) == "number" then | ||
if #analyze > options.max then | if #analyze > options.max then | ||
show = " >" .. options.max | show = " >" .. options.max | ||
show = | if options.say then | ||
r = failure( "tooLong", show, options ) | show = string.format( "%s "%s"", show, options.say ) | ||
else | end | ||
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 ) | local function formatted( assignment, access, options ) | ||
-- Check validity of one particular parameter in a collection | -- Check validity of one particular parameter in a collection | ||
-- Precondition: | -- Precondition: | ||
-- assignment -- collection | |||
-- access -- id of parameter in collection | -- access -- id of parameter in collection | ||
-- options -- table or nil; optional details | -- options -- table or nil; optional details | ||
-- 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 | ||
-- format() | -- Uses: | ||
-- mw.text.trim() | |||
-- format() | |||
-- failure() | |||
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 = { } | |||
return | end | ||
-- Prepare #invoke evaluation of .assert() or .valid() | 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 | -- frame -- object; #invoke environment | ||
-- action -- "assert" or "valid" | -- action -- "assert" or "valid" | ||
-- Return string with error message or "" | -- Postcondition: | ||
-- | -- Return string with error message or "" | ||
-- TemplatePar. | -- Uses: | ||
-- form() | |||
-- failure() | |||
-- finalize() | |||
-- TemplatePar.valid() | |||
-- TemplatePar.assert() | |||
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", | ||
local s | 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, | low = frame.args.low, | ||
format = frame.args.format, | |||
template = frame.args.template | preview = frame.args.preview, | ||
template = frame.args.template | |||
} | |||
options = figure( frame.args[ 2 ], options ) | options = figure( frame.args[ 2 ], options ) | ||
if type( frame.args.min ) == "string" then | if type( frame.args.min ) == "string" then | ||
s = frame.args.min:match( "^%s*([0-9]+)%s*$" ) | s = frame.args.min:match( "^%s*([0-9]+)%s*$" ) | ||
options.min = tonumber( s ) | if s then | ||
options.min = tonumber( s ) | |||
else | |||
r = failure( "invalidPar", | r = failure( "invalidPar", | ||
"min=" .. frame.args.min, | "min=" .. frame.args.min, | ||
options ) | options ) | ||
end | |||
end | |||
if type( frame.args.max ) == "string" then | if type( frame.args.max ) == "string" then | ||
s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" ) | s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" ) | ||
if s then | if s then | ||
options.max = tonumber( s ) | |||
else | |||
r = failure( "invalidPar", | r = failure( "invalidPar", | ||
"max=" .. frame.args.max, | "max=" .. frame.args.max, | ||
options ) | options ) | ||
if r then | end | ||
s = frame.args[ 1 ] or "" | end | ||
if ( r ) then | if r then | ||
r = TemplatePar | r = finalize( r, options ) | ||
return r or ""end -- furnish() | 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 ) | TemplatePar.assert = function ( analyze, append, options ) | ||
-- Perform parameter analysis on a single string | -- Perform parameter analysis on a single string | ||
-- Precondition: | |||
-- analyze -- string to be analyzed | -- analyze -- string to be analyzed | ||
-- append -- string: append error message, prepending <br /> | -- append -- string: append error message, prepending <br /> | ||
-- false or nil: throw error with message | -- false or nil: throw error with message | ||
-- options -- table; optional details | -- options -- table; optional details | ||
-- Postcondition: | |||
-- Return string with error message as configured; | -- Return string with error message as configured; | ||
-- false if valid | -- false if valid | ||
local r = format( analyze, options ) | -- Uses: | ||
-- format() | |||
local r = format( analyze, options ) | |||
if ( r ) then | |||
if ( type( append ) == "string" ) then | if ( type( append ) == "string" ) then | ||
if ( append ~= "" ) then | if ( append ~= "" ) then | ||
end | r = string.format( "%s<br /> %s", append, r ) | ||
return | end | ||
else | |||
error( r, 0 ) | |||
end | |||
end | |||
return r | |||
end -- TemplatePar.assert() | |||
TemplatePar.check = function ( options ) | |||
-- Run parameter analysis on current template environment | -- Run parameter analysis on current template environment | ||
-- Precondition: | -- Precondition: | ||
-- options.mandatory | -- options -- table or nil; optional details | ||
-- Postcondition: | -- options.mandatory | ||
-- false if valid | -- options.optional | ||
return form( true, options )end -- TemplatePar.check() | -- Postcondition: | ||
TemplatePar.count = function () | -- Return string with error message as configured; | ||
-- Postcondition: | -- false if valid | ||
-- mw.getCurrentFrame() | -- Uses: | ||
local r = 0 | -- form() | ||
local o = t.args | return form( true, options, false ) | ||
end -- for k, v | 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 () | TemplatePar.countNotEmpty = function () | ||
-- Return number of template parameters with more than whitespace | -- Return number of template parameters with more than whitespace | ||
-- Postcondition: | -- Postcondition: | ||
-- mw.getCurrentFrame() | -- Return number, starting at 0 | ||
local r = 0 | -- Uses: | ||
local o = t.args | -- mw.getCurrentFrame() | ||
if not v:match( "^%s*$" ) then | -- frame:getParent() | ||
end -- for k, v | 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 ) | TemplatePar.downcase = function ( options ) | ||
-- Return all template parameters with downcased name | -- Return all template parameters with downcased name | ||
-- Precondition: | |||
-- options -- table or nil; optional messaging details | -- options -- table or nil; optional messaging details | ||
-- Postcondition: | -- Postcondition: | ||
-- Return table, may be empty; or string with error message. | -- Return table, may be empty; or string with error message. | ||
-- mw.getCurrentFrame() | -- Uses: | ||
local t = mw.getCurrentFrame():getParent() | -- mw.getCurrentFrame() | ||
end -- TemplatePar.downcase()TemplatePar.valid = function ( access, options ) | -- frame:getParent() | ||
-- Check validity of one particular template parameter | -- flat() | ||
local t = mw.getCurrentFrame():getParent() | |||
return flat( t.args, options ) | |||
end -- TemplatePar.downcase() | |||
TemplatePar.valid = function ( access, options ) | |||
-- Check validity of one particular template parameter | |||
-- Precondition: | |||
-- access -- id of parameter in template transclusion | -- access -- id of parameter in template transclusion | ||
-- options -- table or nil; optional details | -- string or number | ||
-- options -- table or nil; optional details | |||
-- 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 | ||
-- mw.text.trim() | -- Uses: | ||
-- | -- mw.text.trim() | ||
-- formatted() | -- TemplatePar.downcase() | ||
-- TemplatePar.framing() | |||
if #r == 0 then | -- frame:getParent() | ||
local params | -- formatted() | ||
options = { } | -- failure() | ||
params = TemplatePar.downcase( options ) | -- finalize() | ||
params = | local r = type( access ) | ||
r = formatted( params, access, options ) | if r == "string" then | ||
r = failure( "noname", false, options ) | r = mw.text.trim( access ) | ||
return finalize( r, options )end -- TemplatePar.valid() | if #r == 0 then | ||
r = false | |||
end | |||
elseif r == "number" then | |||
r = access | |||
else | |||
r = false | |||
end | |||
if r then | |||
local params | |||
if type( options ) ~= "table" then | |||
options = { } | |||
end | |||
if options.low then | |||
params = TemplatePar.downcase( options ) | |||
else | |||
params = TemplatePar.framing():getParent() | |||
end | |||
r = formatted( params, access, options ) | |||
else | |||
r = failure( "noname", false, options ) | |||
end | |||
return finalize( r, options ) | |||
end -- TemplatePar.valid() | |||
TemplatePar.verify = function ( options ) | TemplatePar.verify = function ( options ) | ||
-- Perform #invoke parameter analysis | -- Perform #invoke parameter analysis | ||
-- options -- table or nil; optional details | -- Precondition: | ||
-- options -- table or nil; optional details | |||
-- Postcondition: | |||
-- Return string with error message as configured; | -- Return string with error message as configured; | ||
-- false if valid | -- false if valid | ||
return form( false, options )end -- TemplatePar.verify() | -- Uses: | ||
-- Provide external | -- form() | ||
-- Perform parameter analysis on some single string | return form( false, options, false ) | ||
-- frame -- object; #invoke environment | end -- TemplatePar.verify() | ||
-- Return string with error message or "" | |||
-- furnish() | |||
function p.check( frame ) | |||
-- Precondition: | TemplatePar.framing = function( frame ) | ||
-- Postcondition: | -- Ensure availability of frame object | ||
-- Uses: | -- Precondition: | ||
-- frame -- object; #invoke environment, or false | |||
-- Postcondition: | |||
-- Return frame object | |||
-- Uses: | |||
-- >< Local.frame | |||
if not Local.frame then | |||
if type( frame ) == "table" and | |||
type( frame.args ) == "table" and | |||
type( frame.getParent ) == "function" and | |||
type( frame:getParent() ) == "table" and | |||
type( frame:getParent().getParent ) == "function" and | |||
type( frame:getParent():getParent() ) == "nil" then | |||
Local.frame = frame | |||
else | |||
Local.frame = mw.getCurrentFrame() | |||
end | |||
end | |||
return Local.frame | |||
end -- TemplatePar.framing() | |||
Failsafe.failsafe = function ( atleast ) | |||
-- Retrieve versioning and check for compliance | |||
-- Precondition: | |||
-- atleast -- string, with required version | |||
-- or wikidata|item|~|@ or false | |||
-- Postcondition: | |||
-- Returns string -- with queried version/item, also if problem | |||
-- false -- if appropriate | |||
-- 2020-08-17 | |||
local since = atleast | |||
local last = ( since == "~" ) | |||
local linked = ( since == "@" ) | |||
local link = ( since == "item" ) | |||
local r | |||
if last or link or linked or since == "wikidata" then | |||
local item = Failsafe.item | |||
since = false | |||
if type( item ) == "number" and item > 0 then | |||
local suited = string.format( "Q%d", item ) | |||
if link then | |||
r = suited | |||
else | |||
local entity = mw.wikibase.getEntity( suited ) | |||
if type( entity ) == "table" then | |||
local seek = Failsafe.serialProperty or "P348" | |||
local vsn = entity:formatPropertyValues( seek ) | |||
if type( vsn ) == "table" and | |||
type( vsn.value ) == "string" and | |||
vsn.value ~= "" then | |||
if last and vsn.value == Failsafe.serial then | |||
r = false | |||
elseif linked then | |||
if mw.title.getCurrentTitle().prefixedText | |||
== mw.wikibase.getSitelink( suited ) then | |||
r = false | |||
else | |||
r = suited | |||
end | |||
else | |||
r = vsn.value | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if type( r ) == "nil" then | |||
if not since or since <= Failsafe.serial then | |||
r = Failsafe.serial | |||
else | |||
r = false | |||
end | |||
end | |||
return r | |||
end -- Failsafe.failsafe() | |||
-- 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", | local options = { optional = { "all", | ||
"opt", | "opt", | ||
"cat", | "cat", | ||
"errNS", | |||
"low", | "low", | ||
" | "format", | ||
"preview", | |||
"template" }, | "template" }, | ||
template = "#invoke:TemplatePar|check|" | template = "#invoke:TemplatePar|check|" | ||
} | } | ||
local r = form( false, options, frame ) | |||
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, | |||
template = frame.args.template | preview = frame.args.preview, | ||
r = form( true, options ) | template = frame.args.template | ||
function p.count( frame ) | } | ||
-- Postcondition: | r = form( true, options, frame ) | ||
-- Uses: | end | ||
return tostring( TemplatePar.count() )end -- .count() | 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 ) | function p.countNotEmpty( frame ) | ||
-- Count number of template parameters which are not empty | -- Count number of template parameters which are not empty | ||
-- Postcondition: | -- Postcondition: | ||
-- Uses: | -- Return string with digits including "0" | ||
return tostring( TemplatePar.countNotEmpty() )end -- .countNotEmpty() | -- Uses: | ||
function p.match( frame ) | -- TemplatePar.countNotEmpty() | ||
-- Postcondition: | return tostring( TemplatePar.countNotEmpty() ) | ||
-- Uses: | end -- p.countNotEmpty() | ||
-- failure() | |||
-- figure() | |||
local r = false | |||
low | function p.match( frame ) | ||
-- Combined analysis of parameters and their values | |||
template | -- Precondition: | ||
local k, v, s | -- frame -- object; #invoke environment | ||
-- Postcondition: | |||
-- Return string with error message or "" | |||
-- Uses: | |||
-- TemplatePar.framing() | |||
-- 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.framing( frame ) | |||
for k, v in pairs( frame.args ) do | |||
if type( k ) == "number" then | if type( k ) == "number" then | ||
s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" ) | s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" ) | ||
s = mw.text.trim( s ) | if s then | ||
s = false | s = mw.text.trim( s ) | ||
if s then | if s == "" then | ||
s = mw.ustring.lower( s ) | s = false | ||
if params[ s ] then | end | ||
s[ #s + 1 ] = v | end | ||
params[ s ] = { v } | 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 ) | r = failure( "invalidPar", tostring( k ), options ) | ||
break -- for k, v | break -- for k, v | ||
if not r then | end | ||
s[ #s + 1 ] = k | end | ||
r = form( true, options ) | 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 errMiss, errValues, lack, rule | ||
local targs = frame:getParent().args | local targs = frame:getParent().args | ||
if options.low then | options.optional = nil | ||
else | if options.low then | ||
errMiss = false | targs = TemplatePar.downcase() | ||
for k, v in pairs( params ) do | else | ||
targs = frame:getParent().args | |||
if s == "" then | end | ||
else | errMiss = false | ||
else | errValues = false | ||
end | for k, v in pairs( params ) do | ||
options.say = k | |||
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 ) | options = figure( rule, options ) | ||
r = feasible( s, options, true ) | r = feasible( s, options, true ) | ||
if lack then | if r then | ||
if lack then | |||
if errMiss then | |||
s = "%s, "%s"" | |||
errMiss = string.format( s, errMiss, k ) | |||
else | else | ||
errMiss = " | errMiss = string.format( ""%s"", | ||
k ) | |||
end | |||
elseif not errMiss then | elseif not errMiss then | ||
errValues = fault( errValues, r ) | errValues = fault( errValues, r ) | ||
break -- for r, rule | end | ||
end -- for s, rule | break -- for r, rule | ||
r = ( errMiss or errValues ) | end | ||
r = failure( "undefined", errMiss, options ) | end -- for s, rule | ||
r = failure( "invalid", errValues, options ) | end -- for k, v | ||
r = finalize( r, options ) | r = ( errMiss or errValues ) | ||
end -- .match()function p.valid( frame ) | if r then | ||
-- Check validity of one particular template parameter | if errMiss then | ||
-- frame -- object; #invoke environment | r = failure( "undefined", errMiss, options ) | ||
-- Return string with error message or "" | else | ||
-- furnish() | r = failure( "invalid", errValues, options ) | ||
function p.TemplatePar() | end | ||
-- Postcondition: | r = finalize( r, options ) | ||
return | 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 ) | |||
-- Versioning interface | |||
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 Failsafe.failsafe( since ) or "" | |||
end -- p.failsafe | |||
function p.TemplatePar() | |||
-- Retrieve function access for modules | |||
-- Postcondition: | |||
-- Return table with functions | |||
return TemplatePar | |||
end -- p.TemplatePar() | |||
setmetatable( p, { __call = function ( func, ... ) | |||
setmetatable( p, nil ) | |||
return Failsafe | |||
end } ) | |||
return p | |||
Latest revision as of 16:40, 13 April 2023
This documentation is transcluded from Module:TemplatePar/doc.
local TemplatePar = { serial = "2023-03-20",
suite = "TemplatePar",
item = 15393417,
globals = { DateTime = 20652535,
FileMedia = 24765326,
Multilingual = 47541920,
TemplUtl = 52364930,
URLutil = 10859193 } }
--[=[
Template parameter utility
* assert
* check
* count
* countNotEmpty
* downcase()
* duplicates
* match
* valid
* verify()
* TemplatePar()
* failsafe()
]=]
local Local = { frame = false }
local Failsafe = TemplatePar
local GlobalMod = Local
-- Module globals
Local.messagePrefix = "lua-module-TemplatePar-"
Local.L10nDef = {}
Local.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",
unavailable = "Error in template * parameter name missing",
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.boolean = { ["1"] = true,
["true"] = true,
y = true,
yes = true,
on = true,
["0"] = true,
["false"] = true,
["-"] = true,
n = true,
no = true,
off = true }
Local.patternCJK = false
local foreignModule = function ( access, advanced, append, alt, alert )
-- Fetch global module
-- Precondition:
-- access -- string, with name of base module
-- advanced -- true, for require(); else mw.loadData()
-- append -- string, with subpage part, if any; or false
-- alt -- number, of wikidata item of root; or false
-- alert -- true, for throwing error on data problem
-- Postcondition:
-- Returns whatever, probably table
-- 2020-01-01
local storage = access
local finer = function ()
if append then
storage = string.format( "%s/%s",
storage,
append )
end
end
local fun, lucky, r, suited
if advanced then
fun = require
else
fun = mw.loadData
end
GlobalMod.globalModules = GlobalMod.globalModules or { }
suited = GlobalMod.globalModules[ access ]
if not suited then
finer()
lucky, r = pcall( fun, "Module:" .. storage )
end
if not lucky then
if not suited and
type( alt ) == "number" and
alt > 0 then
suited = string.format( "Q%d", alt )
suited = mw.wikibase.getSitelink( suited )
GlobalMod.globalModules[ access ] = suited or true
end
if type( suited ) == "string" then
storage = suited
finer()
lucky, r = pcall( fun, storage )
end
if not lucky and alert then
error( "Missing or invalid page: " .. storage )
end
end
return r
end -- foreignModule()
local function Foreign( access )
-- Access standardized library
-- Precondition:
-- access -- string, with name of base module
-- Postcondition:
-- Return library table, or not
-- Uses:
local r
if Local[ access ] then
r = Local[ access ]
else
local bib = foreignModule( access,
true,
false,
TemplatePar.globals[ access ],
false )
if type( bib ) == "table" and
type( bib[ access ] ) == "function" then
bib = bib[ access ]()
if type( bib ) == "table" then
r = bib
Local[ access ] = bib
end
end
end
return r
end -- Foreign()
local function containsCJK( analyse )
-- Is any CJK character present?
-- Precondition:
-- analyse -- string
-- Postcondition:
-- Return false iff no CJK present
-- Uses:
-- >< Local.patternCJK
-- mw.ustring.char()
-- mw.ustring.match()
local r = false
if not Local.patternCJK then
Local.patternCJK = mw.ustring.char( 91,
13312, 45, 40959,
131072, 45, 178207,
93 )
end
if mw.ustring.match( analyse, Local.patternCJK ) then
r = true
end
return r
end -- containsCJK()
local function facility( accept, attempt )
-- Check string as possible file name or other source page
-- Precondition:
-- accept -- string; requirement
-- file
-- file+
-- file:
-- file:+
-- image
-- image+
-- image:
-- image:+
-- attempt -- string; to be tested
-- Postcondition:
-- Return error keyword, or false
-- Uses:
-- Module:FileMedia
-- Foreign()
-- FileMedia.isFile()
-- FileMedia.isType()
local r
if attempt and attempt ~= "" then
local FileMedia = Foreign( "FileMedia" )
if FileMedia and type( FileMedia.isFile ) == "function"
and type( FileMedia.isType ) == "function" 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:
-- > Local.messagePrefix
-- > Local.L10nDef
-- mw.message.new()
-- mw.language.getContentLanguage()
-- Module:Multilingual
-- Foreign()
-- TemplatePar.framing()
-- Multilingual.tabData()
local m = mw.message.new( Local.messagePrefix .. say )
local r = false
if m:isBlank() then
local c = mw.language.getContentLanguage():getCode()
local l10n = Local.L10nDef[ c ]
if l10n then
r = l10n[ say ]
else
local MultiL = Foreign( "Multilingual" )
if MultiL and type( MultiL.tabData ) == "function" then
local lang
r, lang = MultiL.tabData( "I18n/Module:TemplatePar",
say,
false,
TemplatePar.framing() )
end
end
if not r then
r = Local.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 faculty( accept, attempt )
-- Check string as possible boolean
-- Precondition:
-- accept -- string; requirement
-- boolean
-- boolean+
-- attempt -- string; to be tested
-- Postcondition:
-- Return error keyword, or false
-- Uses:
-- Module:TemplUtl
-- Foreign()
-- TemplUtl.faculty()
local r
r = mw.text.trim( attempt ):lower()
if r == "" then
if accept == "boolean+" then
r = "empty"
else
r = false
end
elseif Local.boolean[ r ] or r:match( "^[01%-]+$" ) then
r = false
else
local TemplUtl = Foreign( "TemplUtl" )
if TemplUtl and type( TemplUtl.faculty ) == "function" then
r = TemplUtl.faculty( r, "-" )
if r == "-" then
r = "invalid"
else
r = false
end
else
r = "invalid"
end
end
return r
end -- faculty()
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:
-- Module:Multilingual
-- Foreign()
-- Multilingual.isLang()
local r
if attempt and attempt ~= "" then
local MultiL = Foreign( "Multilingual" )
if MultiL and type( MultiL.isLang ) == "function" 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 MultiL.isLang( group[ i ], lazy ) then
r = "invalid"
break -- for i
end
end -- for i
elseif MultiL.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:
-- Module:URLutil
-- Foreign()
-- URLutil.isWebURL()
local r
if attempt and attempt ~= "" then
local URLutil = Foreign( "URLutil" )
if URLutil and type( URLutil.isWebURL ) == "function" 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 fast( accept, attempt )
-- Check string as possible date or time
-- Precondition:
-- accept -- string; requirement
-- datetime
-- datetime+
-- datetime/y
-- datetime/y+
-- datetime/ym
-- datetime/ym+
-- datetime/ymd
-- datetime/ymd+
-- attempt -- string; to be tested
-- Postcondition:
-- Return error keyword, or false
-- Uses:
-- Module:DateTime
-- Foreign()
-- DateTime.DateTime()
local r
r = mw.text.trim( attempt )
if r == "" then
if accept:find( "+", 1, true ) then
r = "empty"
else
r = false
end
else
local DateTime = Foreign( "DateTime" )
if type( DateTime ) == "table" then
local d = DateTime( attempt )
if type( d ) == "table" then
if accept:find( "/", 1, true ) then
r = "invalid"
if accept:sub( 1, 10 ) == "datetime/y" then
if d.year then
r = false
if accept:sub( 1, 11 ) == "datetime/ym" then
if d.month then
if accept:sub( 1, 12 )
== "datetime/ymd" then
if not d.dom then
r = "invalid"
end
end
else
r = "invalid"
end
end
end
end
else
r = false
end
else
r = "invalid"
end
else
r = "invalid"
end
end
return r
end -- fast()
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:
-- > Local.patterns
-- failure()
-- mw.text.trim()
-- faculty()
-- fast()
-- facility()
-- familiar()
-- far()
-- fair()
-- containsCJK()
local r = false
local s = false
local show = nil
local scan = false
local stuff = mw.text.trim( analyze )
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 = Local.patterns[ s ]
end
if type( scan ) == "string" then
if s == "n" or s == "0,0" or s == "0.0" then
if not stuff:match( "[0-9]" ) and
not stuff: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( stuff )
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( "^boolean%+?$" ) then
r = faculty( s, stuff )
n = true
elseif s:match( "^datetime/?y?m?d?%+?$" ) then
r = fast( s, stuff )
n = true
elseif s:match( "^image%+?:?$" ) or
s:match( "^file%+?:?$" ) then
r = facility( s, stuff )
n = true
elseif s:match( "langs?W?%+?" ) then
r = familiar( s, stuff )
n = true
elseif s:match( "url%+?" ) then
r = far( s, stuff )
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, stuff, scan )
if legal then
if not got then
if s == "aa" then
got = containsCJK( stuff )
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()
-- TemplatePar.framing()
-- frame:getParent()
local g, k, v
local r = { }
if options.low then
g = TemplatePar.downcase( options )
else
g = TemplatePar.framing()
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:
-- TemplatePar.framing()
-- 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 TemplatePar.framing():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 r = false
local lack
for k, v in pairs( got ) do
if k == "" then
lack = true
break -- for k, v
elseif not finder( valid, k ) then
r = fault( r, k )
end
end -- for k, v
if lack then
r = failure( "unavailable", false, options )
elseif 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.framing()
-- fold()
-- fetch()
-- fix()
-- finalize()
local duty, r
if frame then
TemplatePar.framing( frame )
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.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:
-- mw.text.trim()
-- TemplatePar.downcase()
-- TemplatePar.framing()
-- frame:getParent()
-- formatted()
-- failure()
-- finalize()
local r = type( access )
if r == "string" then
r = mw.text.trim( access )
if #r == 0 then
r = false
end
elseif r == "number" then
r = access
else
r = false
end
if r then
local params
if type( options ) ~= "table" then
options = { }
end
if options.low then
params = TemplatePar.downcase( options )
else
params = TemplatePar.framing():getParent()
end
r = formatted( params, access, options )
else
r = failure( "noname", false, options )
end
return finalize( r, options )
end -- TemplatePar.valid()
TemplatePar.verify = function ( options )
-- Perform #invoke parameter analysis
-- Precondition:
-- options -- table or nil; optional details
-- Postcondition:
-- Return string with error message as configured;
-- false if valid
-- Uses:
-- form()
return form( false, options, false )
end -- TemplatePar.verify()
TemplatePar.framing = function( frame )
-- Ensure availability of frame object
-- Precondition:
-- frame -- object; #invoke environment, or false
-- Postcondition:
-- Return frame object
-- Uses:
-- >< Local.frame
if not Local.frame then
if type( frame ) == "table" and
type( frame.args ) == "table" and
type( frame.getParent ) == "function" and
type( frame:getParent() ) == "table" and
type( frame:getParent().getParent ) == "function" and
type( frame:getParent():getParent() ) == "nil" then
Local.frame = frame
else
Local.frame = mw.getCurrentFrame()
end
end
return Local.frame
end -- TemplatePar.framing()
Failsafe.failsafe = function ( atleast )
-- Retrieve versioning and check for compliance
-- Precondition:
-- atleast -- string, with required version
-- or wikidata|item|~|@ or false
-- Postcondition:
-- Returns string -- with queried version/item, also if problem
-- false -- if appropriate
-- 2020-08-17
local since = atleast
local last = ( since == "~" )
local linked = ( since == "@" )
local link = ( since == "item" )
local r
if last or link or linked or since == "wikidata" then
local item = Failsafe.item
since = false
if type( item ) == "number" and item > 0 then
local suited = string.format( "Q%d", item )
if link then
r = suited
else
local entity = mw.wikibase.getEntity( suited )
if type( entity ) == "table" then
local seek = Failsafe.serialProperty or "P348"
local vsn = entity:formatPropertyValues( seek )
if type( vsn ) == "table" and
type( vsn.value ) == "string" and
vsn.value ~= "" then
if last and vsn.value == Failsafe.serial then
r = false
elseif linked then
if mw.title.getCurrentTitle().prefixedText
== mw.wikibase.getSitelink( suited ) then
r = false
else
r = suited
end
else
r = vsn.value
end
end
end
end
end
end
if type( r ) == "nil" then
if not since or since <= Failsafe.serial then
r = Failsafe.serial
else
r = false
end
end
return r
end -- Failsafe.failsafe()
-- 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.framing()
-- 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.framing( 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
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
s = "%s, "%s""
errMiss = string.format( 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 )
-- Versioning interface
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 Failsafe.failsafe( since ) or ""
end -- p.failsafe
function p.TemplatePar()
-- Retrieve function access for modules
-- Postcondition:
-- Return table with functions
return TemplatePar
end -- p.TemplatePar()
setmetatable( p, { __call = function ( func, ... )
setmetatable( p, nil )
return Failsafe
end } )
return p