Módulu:TemplatePar
Documentation for template programmers
Esta páxina ye una redireición interwiki.
The source code of this script comes from upstream, in this case de.wikipedia, and will regularly be updated from there. |
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