Module:Format TemplateData: Difference between revisions
Jump to navigation
Jump to search
Richardpruen (talk | contribs) m (1 revision imported) |
Richardpruen (talk | contribs) m (1 revision imported: Templates and CSS files) |
||
(One intermediate revision by one other user not shown) | |||
Line 1: | Line 1: | ||
local TemplateData = { serial = " | local TemplateData = { suite = "TemplateData", | ||
serial = "2022-03-10", | |||
item = 46997995 } | |||
--[=[ | --[==[ | ||
improve template:TemplateData | improve template:TemplateData | ||
]=] | ]==] | ||
local Failsafe = TemplateData | |||
local Config = { | local Config = { | ||
-- multiple | -- multiple option names mapped into unique internal fields | ||
basicCnf = { catProblem = "strange", | |||
classMultiColumns = "selMultClm", | |||
-- | classNoNumTOC = "suppressTOCnum", | ||
classTable = "classTable", | |||
cssParWrap = "cssTabWrap", | |||
cssParams = "cssTable", | |||
docpageCreate = "suffix", | |||
docpageDetect = "subpage", | |||
helpBoolean = "support4boolean", | |||
helpContent = "support4content", | |||
helpDate = "support4date", | |||
helpFile = "support4wiki-file-name", | |||
helpFormat = "supportFormat", | |||
helpLine = "support4line", | |||
helpNumber = "support4number", | |||
helpPage = "support4wiki-page-name", | |||
helpString = "support4string", | |||
helpTemplate = "support4wiki-template-name", | |||
helpURL = "support4url", | |||
helpUser = "support4wiki-user-name", | |||
msgDescMiss = "solo", | |||
tStylesTOCnum = "stylesTOCnum", | |||
tStylesMultiColumns = "stylesMultClm" }, | |||
classTable = { "wikitable" }, -- classes for params table | |||
debugmultilang = "C0C0C0", | |||
loudly = false, -- show exported element, etc. | loudly = false, -- show exported element, etc. | ||
solo = false, -- complaint on missing description | solo = false, -- complaint on missing description | ||
strange = false, -- title of maintenance category | strange = false, -- title of maintenance category | ||
cssTable = false, -- styles for params table | |||
cssTabWrap = false, -- styles for params table wrapper | |||
debug = false, | |||
subpage = false, -- pattern to identify subpage | subpage = false, -- pattern to identify subpage | ||
suffix = false, | suffix = false, -- subpage creation scheme | ||
suppressTOCnum = false -- class for TOC number suppression | suppressTOCnum = false, -- class for TOC number suppression | ||
jsonDebug = "json-code-lint" -- class for jsonDebug tool | |||
} | } | ||
local Data = { | local Data = { | ||
Line 30: | Line 50: | ||
got = false, -- table, initial templatedata object | got = false, -- table, initial templatedata object | ||
heirs = false, -- table, params that are inherited | heirs = false, -- table, params that are inherited | ||
jump = false, -- source position at end of "params" | |||
less = false, -- main description missing | less = false, -- main description missing | ||
lasting = false, -- old syntax encountered | lasting = false, -- old syntax encountered | ||
Line 38: | Line 59: | ||
params = false, -- table, exported parameters | params = false, -- table, exported parameters | ||
scream = false, -- error messages | scream = false, -- error messages | ||
slang = | sibling = false, -- TOC juxtaposed | ||
slang = nil, -- project/user language code | |||
slim = false, -- JSON reduced to plain | slim = false, -- JSON reduced to plain | ||
source = false, -- JSON input | source = false, -- JSON input | ||
Line 47: | Line 69: | ||
} | } | ||
local Permit = { | local Permit = { | ||
builder = { after = "block", | |||
suggested = " | align = "block", | ||
optional = " | block = "block", | ||
deprecated = " | compressed = "block", | ||
dense = "block", | |||
params = { aliases | grouped = "inline", | ||
autovalue | half = "inline", | ||
default | indent = "block", | ||
deprecated | inline = "inline", | ||
description = "string table I18N", | last = "block", | ||
example | lead = "block", | ||
label | newlines = "*", | ||
inherits | spaced = "inline" }, | ||
required | colors = { bg = "FFFFFF", | ||
suggested | fg = "000000", | ||
suggestedvalues = "table", | tableheadbg = "B3B7FF", | ||
type | required = "EAF3FF", | ||
suggested = "FFFFFF", | |||
optional = "EAECF0", | |||
deprecated = "FFCBCB" }, | |||
params = { aliases = "table", | |||
autovalue = "string", | |||
default = "string table I18N nowiki", | |||
deprecated = "boolean string I18N", | |||
description = "string table I18N", | |||
example = "string table I18N nowiki", | |||
label = "string table I18N", | |||
inherits = "string", | |||
required = "boolean", | |||
style = "string table", | |||
suggested = "boolean", | |||
suggestedvalues = "string table number boolean", | |||
type = "string" }, | |||
root = { description = "string table I18N", | root = { description = "string table I18N", | ||
format = "string", | format = "string", | ||
Line 89: | Line 127: | ||
} | } | ||
local function Fault( alert ) | local function Fault( alert ) | ||
Line 113: | Line 140: | ||
end -- Fault() | end -- Fault() | ||
---------------------------------- | |||
local function Fetch( ask, allow ) | |||
-- Fetch module | |||
-- Parameter: | |||
-- ask -- string, with name | |||
-- "/global" | |||
-- "JSONutil" | |||
-- "Multilingual" | |||
-- "Text" | |||
-- "WLink" | |||
-- allow -- true: no error if unavailable | |||
-- Returns table of module | |||
-- error: Module not available | |||
local sign = ask | |||
local r, stem | |||
if sign:sub( 1, 1 ) == "/" then | |||
sign = TemplateData.frame:getTitle() .. sign | |||
else | |||
stem = sign | |||
sign = "Module:" .. stem | |||
end | |||
if TemplateData.extern then | |||
r = TemplateData.extern[ sign ] | |||
else | |||
TemplateData.extern = { } | |||
end | |||
if not r then | |||
local lucky, g = pcall( require, sign ) | |||
if type( g ) == "table" then | |||
if stem and type( g[ stem ] ) == "function" then | |||
r = g[ stem ]() | |||
else | |||
r = g | |||
end | |||
TemplateData.extern[ sign ] = r | |||
elseif not allow then | |||
error( string.format( "Fetch(%s) %s", sign, g ), 0 ) | |||
end | |||
end | |||
return r | |||
end -- Fetch() | |||
local function Foreign() | |||
-- Guess human language | |||
-- Returns slang, or not | |||
if type( Data.slang ) == "nil" then | |||
local Multilingual = Fetch( "Multilingual", true ) | |||
if Multilingual and | |||
type( Multilingual.userLangCode ) == "function" then | |||
Data.slang = Multilingual.userLangCode() | |||
else | |||
Data.slang = mw.language.getContentLanguage():getCode() | |||
:lower() | |||
end | |||
end | |||
if Data.slang and | |||
mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then | |||
Data.slang = false | |||
end | |||
return Data.slang | |||
end -- Foreign() | |||
local function facet( ask, at ) | local function facet( ask, at ) | ||
Line 132: | Line 217: | ||
:gsub( "([%-.()+*?^$%[%]])", | :gsub( "([%-.()+*?^$%[%]])", | ||
"%%%1" ) ) | "%%%1" ) ) | ||
local i, k = Data.source:find( | local i, k, r, slice, source | ||
if not Data.jump then | |||
Data.jump = Data.source:find( "params", 2 ) | |||
if Data.jump then | |||
Data.jump = Data.jump + 7 | |||
else | |||
Data.jump = 1 | |||
end | |||
end | |||
i, k = Data.source:find( seek, at + Data.jump ) | |||
while i and not r do | while i and not r do | ||
source = Data.source:sub( k + 1 ) | source = Data.source:sub( k + 1 ) | ||
Line 144: | Line 237: | ||
r = k | r = k | ||
else | else | ||
i, k = Data.source:find( seek, k ) | i, k = Data.source:find( seek, k ) | ||
end | end | ||
end -- while i | end -- while i | ||
Line 152: | Line 245: | ||
local function | local function facilities( apply ) | ||
-- Retrieve details of suggestedvalues | |||
-- Parameter: | |||
-- apply -- table, with plain or enhanced values | |||
-- .suggestedvalues -- table|string|number, or more | |||
-- Returns | |||
-- 1 -- table, with suggestedvalues | |||
-- 2 -- table, with CSS map, or not | |||
-- 3 -- string, with class, or not | |||
-- 4 -- string, with templatestyles, or not | |||
local elements = apply.suggestedvalues | |||
local s = type( elements ) | |||
local r1, r2, r3, r4 | |||
if s == "table" then | |||
local values = elements.values | |||
if type( values ) == "table" then | |||
r1 = values | |||
if type( elements.scroll ) == "string" then | |||
r2 = r2 or { } | |||
r2.height = apply.scroll | |||
r2.overflow = "auto" | |||
end | |||
if type( elements.minwidth ) == "string" then | |||
local s = type( elements.maxcolumns ) | |||
r2 = r2 or { } | |||
r2["column-width"] = elements.minwidth | |||
if s == "string" or | |||
s == "number" then | |||
s = tostring( elements.maxcolumns ) | |||
r2["column-count"] = s | |||
end | |||
if type( Config.selMultClm ) == "string" then | |||
r3 = Config.selMultClm | |||
end | |||
if type( Config.stylesMultClm ) == "string" then | |||
local src = Config.stylesMultClm .. "/styles.css" | |||
r4 = TemplateData.frame | |||
:extensionTag( "templatestyles", | |||
nil, | |||
{ src = src } ) | |||
end | |||
end | |||
elseif elements and elements ~= "" then | |||
r1 = elements | |||
end | |||
elseif s == "string" then | |||
s = mw.text.trim( about ) | |||
if s ~= "" then | |||
r1 = { } | |||
table.insert( r1, | |||
{ code = s } ) | |||
end | |||
elseif s == "number" then | |||
r1 = { } | |||
table.insert( r1, | |||
{ code = tostring( elements ) } ) | |||
end | |||
return r1, r2, r3, r4 | |||
end -- facilities() | |||
local function factory( adapt ) | |||
-- Retrieve localized text from system message | -- Retrieve localized text from system message | ||
-- Parameter: | -- Parameter: | ||
-- adapt -- string, message ID after "templatedata-" | -- adapt -- string, message ID after "templatedata-" | ||
-- Returns string, with localized text | -- Returns string, with localized text | ||
local o = mw.message.new( "templatedata-" .. adapt ) | |||
end -- | if Foreign() then | ||
o:inLanguage( Data.slang ) | |||
end | |||
return o:plain() | |||
end -- factory() | |||
Line 204: | Line 363: | ||
local function | local function fair( adjust ) | ||
-- | -- Reduce text to one line of plain text, or noexport wikitext blocks | ||
-- adjust -- string | -- adjust -- string | ||
-- Returns string, with adjusted text | -- Returns string, with adjusted text | ||
local f = function ( a ) | |||
return a:gsub( "%s*\n%s*", " " ) | |||
:gsub( "%s%s+", " " ) | |||
end | |||
local tags = { { start = "<noexport>", | |||
stop = "</noexport>" }, | |||
{ start = "<exportonly>", | |||
stop = "</exportonly>", | |||
l = false } | |||
} | |||
local r = adjust | |||
local i, j, k, s, tag | |||
for m = 1, 2 do | |||
tag = tags[ m ] | |||
if r:find( tag.start, 1, true ) then | |||
s = r | |||
r = "" | |||
i = 1 | |||
tag.l = true | |||
j, k = s:find( tag.start, i, true ) | |||
while j do | |||
if j > 1 then | |||
r = r .. f( s:sub( i, j - 1 ) ) | |||
end | |||
i = k + 1 | |||
j, k = s:find( tag.stop, i, true ) | |||
if j then | |||
if m == 1 then | |||
r = r .. s:sub( i, j - 1 ) | |||
end | |||
i = k + 1 | |||
j, k = s:find( tag.start, i, true ) | |||
else | |||
Fault( "missing " .. tag.stop ) | |||
end | |||
end -- while j | |||
r = r .. s:sub( i ) | |||
elseif m == 1 then | |||
r = f( r ) | |||
end | |||
end -- for m | |||
if tags[ 2 ].l then | |||
r = r:gsub( "<exportonly>.*</exportonly>", "" ) | |||
end | |||
return r | |||
end -- fair() | |||
local function fancy( advance, alert ) | |||
-- Present JSON source | |||
-- Parameter: | |||
-- advance -- true, for nice | |||
-- alert -- true, for visible | |||
-- Returns string | |||
local r | local r | ||
if | if Data.source then | ||
local | local support = Config.jsonDebug | ||
local | local css | ||
if advance then | |||
css = { height = "6em", | |||
if | resize = "vertical" } | ||
r = { [ 1 ] = "syntaxhighlight", | |||
[ 2 ] = Data.source, | |||
lang = "json", | |||
style = table.concat( css, ";" ) } | |||
if alert then | |||
r.class( support ) | |||
end | end | ||
r = TemplateData.frame:callParserFunction( "#tag", r ) | |||
else | |||
css = { [ "font-size" ] = "77%", | |||
[ "line-height" ] = "1.35" } | |||
if alert then | |||
css.resize = "vertical" | |||
else | else | ||
css.display = "none" | |||
end | end | ||
end | r = mw.html.create( "pre" ) | ||
r = | :addClass( support ) | ||
:css( css ) | |||
:wikitext( mw.text.encode( Data.source ) ) | |||
r = tostring( r ) | |||
end | |||
r = "\n".. r | |||
else | else | ||
r = | r = "" | ||
end | end | ||
return r | return r | ||
end -- | end -- fancy() | ||
local function faraway( alternatives ) | local function faraway( alternatives ) | ||
-- Retrieve | -- Retrieve best language version from multilingual text | ||
-- Parameter: | -- Parameter: | ||
-- alternatives -- table, to be evaluated | -- alternatives -- table, to be evaluated | ||
Line 248: | Line 470: | ||
local variants = { } | local variants = { } | ||
local r1, r2 | local r1, r2 | ||
for k, v in pairs( alternatives ) do | for k, v in pairs( alternatives ) do | ||
if type( v ) == "string" then | if type( v ) == "string" then | ||
v = mw.text.trim( v ) | v = mw.text.trim( v ) | ||
if v ~= "" then | if v ~= "" and type( k ) == "string" then | ||
k = k:lower() | |||
variants[ k ] = v | variants[ k ] = v | ||
n = n + 1 | n = n + 1 | ||
Line 261: | Line 481: | ||
end -- for k, v | end -- for k, v | ||
if n > 0 then | if n > 0 then | ||
local Multilingual = Fetch( "Multilingual", true ) | |||
if | if Multilingual and | ||
type( Multilingual.i18n ) == "function" then | |||
local show, slang = Multilingual.i18n( variants ) | |||
if show then | |||
r1 = show | |||
variants[ slang ] = nil | |||
r2 = variants | |||
end | end | ||
end | end | ||
if not r1 then | if not r1 then | ||
Foreign() | |||
for k, v in pairs( variants ) do | for k, v in pairs( variants ) do | ||
if v | if n == 1 then | ||
r1 = v | |||
elseif Data.slang == k then | |||
variants[ k ] = nil | variants[ k ] = nil | ||
r1 = v | r1 = v | ||
r2 = variants | r2 = variants | ||
end | end | ||
end -- for k, v | end -- for k, v | ||
end | end | ||
if r2 then | if r2 and Multilingual then | ||
for k, v in pairs( r2 ) do | for k, v in pairs( r2 ) do | ||
if v | if v and not Multilingual.isLang( k, true ) then | ||
Fault( string.format( "%s <code>lang=%s</code>", | |||
"Invalid", | |||
k ) ) | |||
end | end | ||
end -- for k, v | end -- for k, v | ||
Line 320: | Line 515: | ||
return r1, r2 | return r1, r2 | ||
end -- faraway() | end -- faraway() | ||
local function fashioned( about, asked, assign ) | |||
-- Create description head | |||
-- Parameter: | |||
-- about -- table, supposed to contain description | |||
-- asked -- true, if mandatory description | |||
-- assign -- <block>, if to be equipped | |||
-- Returns <block>, with head, or nil | |||
local para = assign or mw.html.create( "div" ) | |||
local plus, r | |||
if about and about.description then | |||
if type( about.description ) == "string" then | |||
para:wikitext( about.description ) | |||
else | |||
para:wikitext( about.description[ 1 ] ) | |||
plus = mw.html.create( "ul" ) | |||
plus:css( "text-align", "left" ) | |||
for k, v in pairs( about.description[ 2 ] ) do | |||
plus:node( mw.html.create( "li" ) | |||
:node( mw.html.create( "code" ) | |||
:wikitext( k ) ) | |||
:node( mw.html.create( "br" ) ) | |||
:wikitext( fair( v ) ) ) | |||
end -- for k, v | |||
if Config.loudly then | |||
plus = mw.html.create( "div" ) | |||
:css( "background-color", | |||
"#" .. Config.debugmultilang ) | |||
:node( plus ) | |||
else | |||
plus:addClass( "templatedata-maintain" ) | |||
:css( "display", "none" ) | |||
end | |||
end | |||
elseif Config.solo and asked then | |||
para:addClass( "error" ) | |||
:wikitext( Config.solo ) | |||
Data.less = true | |||
else | |||
para = false | |||
end | |||
if para then | |||
if plus then | |||
r = mw.html.create( "div" ) | |||
:node( para ) | |||
:node( plus ) | |||
else | |||
r = para | |||
end | |||
end | |||
return r | |||
end -- fashioned() | |||
local function fatten( access ) | |||
-- Create table row for sub-headline | |||
-- Parameter: | |||
-- access -- string, with name | |||
-- Returns <tr> | |||
local param = Data.tree.params[ access ] | |||
local sub, sort = access:match( "(=+)%s*(%S.*)$" ) | |||
local headline = mw.html.create( string.format( "h%d", #sub ) ) | |||
local r = mw.html.create( "tr" ) | |||
local td = mw.html.create( "td" ) | |||
:attr( "colspan", "5" ) | |||
:attr( "data-sort-value", "!" .. sort ) | |||
local s | |||
if param.style then | |||
s = type( param.style ) | |||
if s == "table" then | |||
td:css( param.style ) | |||
elseif s == "string" then | |||
td:cssText( param.style ) | |||
end | |||
end | |||
s = fashioned( param, false, headline ) | |||
if s then | |||
headline = s | |||
else | |||
headline:wikitext( sort ) | |||
end | |||
td:node( headline ) | |||
r:node( td ) | |||
return r | |||
end -- fatten() | |||
Line 333: | Line 616: | ||
end -- for k, v | end -- for k, v | ||
for i = 1, n do | for i = 1, n do | ||
for k, v in pairs( Data.heirs ) do | if Data.heirs then | ||
for k, v in pairs( Data.heirs ) do | |||
if v and not Data.heirs[ v ] then | |||
n = n - 1 | |||
t[ k ].inherits = nil | |||
Data.heirs[ k ] = nil | |||
p2 = { } | |||
t2 = { } | |||
if p[ v ] then | |||
for k2, v2 in pairs( p[ v ] ) do | |||
p2[ k2 ] = v2 | p2[ k2 ] = v2 | ||
end -- for k2, v2 | |||
if p[ k ] then | |||
for k2, v2 in pairs( p[ k ] ) do | |||
if type( v2 ) ~= "nil" then | |||
p2[ k2 ] = v2 | |||
end | |||
end -- for k2, v2 | |||
end | end | ||
p[ k ] = p2 | |||
for k2, v2 in pairs( t[ v ] ) do | |||
t2[ k2 ] = v2 | |||
end -- for k2, v2 | |||
for k2, v2 in pairs( t[ k ] ) do | |||
if type( v2 ) ~= "nil" then | |||
t2[ k2 ] = v2 | |||
end | |||
end -- for k2, v2 | |||
t[ k ] = t2 | |||
else | |||
Fault( "No params[] inherits " .. v ) | |||
end | |||
end | end | ||
end -- for k, v | |||
end | |||
end | |||
end -- i = 1, n | end -- i = 1, n | ||
if n > 0 then | if n > 0 then | ||
Line 380: | Line 669: | ||
local function | local function favorize() | ||
-- | -- Local customization issues | ||
- | local boole = { ["font-size"] = "125%" } | ||
local l, cx = pcall( mw.loadData, | |||
- | TemplateData.frame:getTitle() .. "/config" ) | ||
local scripting, style | |||
TemplateData.ltr = not mw.language.getContentLanguage():isRTL() | |||
if TemplateData.ltr then | |||
scripting = "left" | |||
if | else | ||
scripting = "right" | |||
end | |||
boole[ "margin-" .. scripting ] = "3em" | |||
Permit.boole = { [false] = { css = boole, | |||
lead = true, | |||
show = "☐" }, | |||
[true] = { css = boole, | |||
lead = true, | |||
show = "☑" } } | |||
Permit.css = { } | |||
for k, v in pairs( Permit.colors ) do | |||
if k == "tableheadbg" then | |||
k = "tablehead" | |||
end | |||
if k == "fg" then | |||
style = "color" | |||
else | else | ||
style = "background-color" | |||
end | |||
if | Permit.css[ k ] = { } | ||
Permit.css[ k ][ style ] = "#" .. v | |||
end -- for k, v | |||
if type( cx ) == "table" then | |||
local c, s | |||
if type( cx.permit ) == "table" then | |||
if type( cx.permit.boole ) == "table" then | |||
if type( cx.permit.boole[ true ] ) == "table" then | |||
Permit.boole[ false ] = cx.permit.boole[ false ] | |||
end | |||
if type( cx.permit.boole[ true ] ) == "table" then | |||
Permit.boole[ true ] = cx.permit.boole[ true ] | |||
end | |||
end | |||
if type( cx.permit.css ) == "table" then | |||
for k, v in pairs( cx.permit.css ) do | |||
if type( v ) == "table" then | |||
Permit.css[ k ] = v | |||
end | |||
end -- for k, v | |||
end | end | ||
end | end | ||
for k, v in pairs( Config.basicCnf ) do | |||
s = type( cx[ k ] ) | |||
if s == "string" or s == "table" then | |||
Config[ v ] = cx[ k ] | |||
end | |||
end -- for k, v | |||
end | end | ||
if | if type( Config.subpage ) ~= "string" or | ||
type( Config.suffix ) ~= "string" then | |||
local got = mw.message.new( "templatedata-doc-subpage" ) | |||
local suffix | |||
if got:isDisabled() then | |||
suffix = "doc" | |||
else | else | ||
suffix = got:plain() | |||
end | |||
if type( Config.subpage ) ~= "string" then | |||
Config.subpage = string.format( "/%s$", suffix ) | |||
end | |||
if type( Config.suffix ) ~= "string" then | |||
Config.suffix = string.format( "%%s/%s", suffix ) | |||
end | end | ||
end | end | ||
return | end -- favorize() | ||
local function feasible( all, at, about ) | |||
-- Deal with suggestedvalues within parameter | |||
-- Parameter: | |||
-- all -- parameter details | |||
-- .default | |||
-- .type | |||
-- at -- string, with parameter name | |||
-- about -- suggestedvalues -- table, | |||
-- value and possibly description | |||
-- table may have elements: | |||
-- .code -- mandatory | |||
-- .label -- table|string | |||
-- .support -- table|string | |||
-- .icon -- string | |||
-- .class -- table|string | |||
-- .css -- table | |||
-- .style -- string | |||
-- .less -- true: suppress code | |||
-- Returns | |||
-- 1: mw.html object <ul> | |||
-- 2: sequence table with values, or nil | |||
local h = { } | |||
local e, r1, r2, s, v | |||
if #about > 0 then | |||
for i = 1, #about do | |||
e = about[ i ] | |||
s = type( e ) | |||
if s == "table" then | |||
if type( e.code ) == "string" then | |||
s = mw.text.trim( e.code ) | |||
if s == "" then | |||
e = nil | |||
else | |||
e.code = s | |||
end | |||
else | |||
e = nil | |||
s = string.format( "params.%s.%s[%d] %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
"MISSING 'code:'" ) | |||
end | |||
elseif s == "string" then | |||
s = mw.text.trim( e ) | |||
if s == "" then | |||
e = nil | |||
s = string.format( "params.%s.%s[%d] EMPTY", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
else | |||
e = { code = s } | |||
end | |||
elseif s == "number" then | |||
e = { code = tostring( e ) } | |||
else | |||
s = string.format( "params.%s.%s[%d] INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
e = false | |||
end | |||
if e then | |||
v = v or { } | |||
table.insert( v, e ) | |||
if h[ e.code ] then | |||
s = string.format( "params.%s.%s REPEATED %s", | |||
at, | |||
"suggestedvalues", | |||
e.code ) | |||
Fault( s ) | |||
else | |||
h[ e.code ] = true | |||
end | |||
end | |||
end -- for i | |||
else | |||
Fault( string.format( "params.%s.suggestedvalues %s", | |||
at, "NOT AN ARRAY" ) ) | |||
end | |||
if v then | |||
local code, d, k, less, story, swift, t, u | |||
r1 = mw.html.create( "ul" ) | |||
r2 = { } | |||
for i = 1, #v do | |||
u = mw.html.create( "li" ) | |||
e = v[ i ] | |||
table.insert( r2, e.code ) | |||
story = false | |||
less = ( e.less == true ) | |||
if not less then | |||
swift = e.code | |||
if e.support then | |||
local scream, support | |||
s = type( e.support ) | |||
if s == "string" then | |||
support = e.support | |||
elseif s == "table" then | |||
support = faraway( e.support ) | |||
else | |||
scream = "INVALID" | |||
end | |||
if support then | |||
s = mw.text.trim( support ) | |||
if s == "" then | |||
scream = "EMPTY" | |||
elseif s:find( "[%[%]|%<%>]" ) then | |||
scream = "BAD PAGE" | |||
else | |||
support = s | |||
end | |||
end | |||
if scream then | |||
s = string.format( "params.%s.%s[%d].support %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
scream ) | |||
Fault( s ) | |||
else | |||
swift = string.format( "[[:%s|%s]]", | |||
support, swift ) | |||
end | |||
end | |||
if all.type:sub( 1, 5 ) == "wiki-" and | |||
swift == e.code then | |||
local rooms = { file = 6, | |||
temp = 10, | |||
user = 2 } | |||
local ns = rooms[ all.type:sub( 6, 9 ) ] or 0 | |||
t = mw.title.makeTitle( ns, swift ) | |||
if t and t.exists then | |||
swift = string.format( "[[:%s|%s]]", | |||
t.prefixedText, swift ) | |||
end | |||
end | |||
if e.code == all.default then | |||
k = 800 | |||
else | |||
k = 300 | |||
end | |||
code = mw.html.create( "code" ) | |||
:css( "font-weight", tostring( k ) ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( swift ) | |||
u:node( code ) | |||
end | |||
if e.class then | |||
s = type( e.class ) | |||
if s == "string" then | |||
u:addClass( e.class ) | |||
elseif s == "table" then | |||
for k, s in pairs( e.class ) do | |||
u:addClass( s ) | |||
end -- for k, s | |||
else | |||
s = string.format( "params.%s.%s[%d].class INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
if e.css then | |||
if type( e.css ) == "table" then | |||
u:css( e.css ) | |||
else | |||
s = string.format( "params.%s.%s[%d].css INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
if e.style then | |||
if type( e.style ) == "string" then | |||
u:cssText( e.style ) | |||
else | |||
s = string.format( "params.%s.%s[%d].style INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
if all.type == "wiki-file-name" and not e.icon then | |||
e.icon = e.code | |||
end | |||
if e.label then | |||
s = type( e.label ) | |||
if s == "string" then | |||
s = mw.text.trim( e.label ) | |||
if s == "" then | |||
s = string.format( "params.%s.%s[%d].label %s", | |||
at, | |||
"suggestedvalues", | |||
i, | |||
"EMPTY" ) | |||
Fault( s ) | |||
else | |||
story = s | |||
end | |||
elseif s == "table" then | |||
story = faraway( e.label ) | |||
else | |||
s = string.format( "params.%s.%s[%d].label INVALID", | |||
at, "suggestedvalues", i ) | |||
Fault( s ) | |||
end | |||
end | |||
s = false | |||
if type( e.icon ) == "string" then | |||
t = mw.title.makeTitle( 6, e.icon ) | |||
if t and t.file.exists then | |||
local g = mw.html.create( "span" ) | |||
s = string.format( "[[%s|16px]]", t.prefixedText ) | |||
g:attr( "role", "presentation" ) | |||
:wikitext( s ) | |||
s = tostring( g ) | |||
end | |||
end | |||
if not s and not less and e.label then | |||
s = mw.ustring.char( 0x2013 ) | |||
end | |||
if s then | |||
d = mw.html.create( "span" ) | |||
:wikitext( s ) | |||
if TemplateData.ltr then | |||
if not less then | |||
d:css( "margin-left", "0.5em" ) | |||
end | |||
if story then | |||
d:css( "margin-right", "0.5em" ) | |||
end | |||
else | |||
if not less then | |||
d:css( "margin-right", "0.5em" ) | |||
end | |||
if story then | |||
d:css( "margin-left", "0.5em" ) | |||
end | |||
end | |||
u:node( d ) | |||
end | |||
if story then | |||
u:wikitext( story ) | |||
end | |||
r1:newline() | |||
:node( u ) | |||
end -- for i | |||
end | |||
if not r1 and v ~= false then | |||
Fault( string.format( "params.%s.suggestedvalues INVALID", at ) ) | |||
r1 = mw.html.create( "code" ) | |||
:addClass( "error" ) | |||
:wikitext( "INVALID" ) | |||
end | |||
return r1, r2 | |||
end -- feasible() | end -- feasible() | ||
Line 445: | Line 1,020: | ||
local pointers = { } | local pointers = { } | ||
local points = { } | local points = { } | ||
local given = { } | |||
for k, v in pairs( Data.tree.params ) do | for k, v in pairs( Data.tree.params ) do | ||
i = facet( k, 1 ) | i = facet( k, 1 ) | ||
if type( v ) == "table" then | |||
if type( v.label ) == "string" then | |||
s = mw.text.trim( v.label ) | |||
if s == "" then | |||
s = k | |||
end | |||
else | |||
s = k | |||
end | |||
if given[ s ] then | |||
if given[ s ] == 1 then | |||
local scream = "Parameter label '%s' detected multiple times" | |||
Fault( string.format( scream, s ) ) | |||
given[ s ] = 2 | |||
end | |||
else | |||
given[ s ] = 1 | |||
end | |||
end | |||
if i then | if i then | ||
table.insert( points, i ) | table.insert( points, i ) | ||
Line 488: | Line 1,083: | ||
local code = mw.html.create( "code" ) | local code = mw.html.create( "code" ) | ||
local desc = mw.html.create( "td" ) | local desc = mw.html.create( "td" ) | ||
local eager = mw.html.create( "td" ) | |||
local legal = true | local legal = true | ||
local param = Data.tree.params[ access ] | local param = Data.tree.params[ access ] | ||
local ranking = { "required", "suggested", "optional", "deprecated" } | local ranking = { "required", "suggested", "optional", "deprecated" } | ||
local r = mw.html.create( "tr" ) | local r = mw.html.create( "tr" ) | ||
local styles = "mw-templatedata-doc-param-" | |||
local sort, typed | local sort, typed | ||
Line 520: | Line 1,117: | ||
end | end | ||
code = mw.html.create( "td" ) | code = mw.html.create( "td" ) | ||
:addClass( styles .. "name" ) | |||
:node( code ) | :node( code ) | ||
if access:match( "^%d+$" ) then | if access:match( "^%d+$" ) then | ||
Line 526: | Line 1,124: | ||
end | end | ||
if type( param.aliases ) == "table" then | if type( param.aliases ) == "table" then | ||
local lapsus | local lapsus, syn | ||
for k, v in pairs( param.aliases ) do | for k, v in pairs( param.aliases ) do | ||
code:tag( "br" ) | code:tag( "br" ) | ||
Line 536: | Line 1,134: | ||
:css( "font-style", "italic" ) | :css( "font-style", "italic" ) | ||
:wikitext( "string" ) ) | :wikitext( "string" ) ) | ||
:wikitext( s ) | |||
else | |||
syn = mw.html.create( "span" ) | |||
:addClass( styles .. "alias" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( s ) | |||
code:node( syn ) | |||
end | end | ||
else | else | ||
lapsus = true | lapsus = true | ||
Line 547: | Line 1,151: | ||
if lapsus then | if lapsus then | ||
s = string.format( "params.<code>%s</code>.aliases", access ) | s = string.format( "params.<code>%s</code>.aliases", access ) | ||
Fault( | Fault( factory( "invalid-value" ):gsub( "$1", s ) ) | ||
legal = false | legal = false | ||
end | end | ||
Line 553: | Line 1,157: | ||
-- description etc. | -- description etc. | ||
s = | s = fashioned( param ) | ||
if s then | if s then | ||
desc:node( s ) | desc:node( s ) | ||
end | end | ||
if param.suggestedvalues or param.default or param.example or param.autovalue then | if param.style then | ||
local details = { "suggestedvalues", "default", "example", "autovalue" } | s = type( param.style ) | ||
if s == "table" then | |||
desc:css( param.style ) | |||
elseif s == "string" then | |||
desc:cssText( param.style ) | |||
end | |||
end | |||
if param.suggestedvalues or | |||
param.default or | |||
param.example or | |||
param.autovalue then | |||
local details = { "suggestedvalues", | |||
"default", | |||
"example", | |||
"autovalue" } | |||
local dl = mw.html.create( "dl" ) | local dl = mw.html.create( "dl" ) | ||
local dd, section, show | local dd, section, show | ||
for i = 1, #details do | for i = 1, #details do | ||
s = details[ i ] | s = details[ i ] | ||
show = param[ s ] | show = param[ s ] | ||
if show then | if show then | ||
dd = mw.html.create( "dd" ) | dd = mw.html.create( "dd" ) | ||
section = factory( "doc-param-" .. s ) | |||
if param.type == "boolean" and | |||
( show == "0" or show == "1" ) then | |||
if param.type == "boolean" | local boole = Permit.boole[ ( show == "1" ) ] | ||
if boole.lead == true then | |||
dd:node( mw.html.create( "code" ) | |||
:wikitext( show ) ) | |||
:wikitext( " " ) | |||
end | |||
if type( boole.show ) == "string" then | |||
local v = mw.html.create( "span" ) | |||
:attr( "aria-hidden", "true" ) | |||
:wikitext( boole.show ) | |||
if boole.css then | |||
v:css( boole.css ) | |||
end | end | ||
dd:node( v ) | |||
dd:wikitext( | end | ||
if type( boole.suffix ) == "string" then | |||
dd:wikitext(" | dd:wikitext( boole.suffix ) | ||
end | |||
if boole.lead == false then | |||
dd:wikitext( " " ) | |||
:node( mw.html.create( "code" ) | |||
:wikitext( show ) ) | |||
end | end | ||
elseif | elseif s == "suggestedvalues" then | ||
local v, css, class, ts = facilities( param ) | |||
if v then | |||
local ul | |||
if | ul, v = feasible( param, access, v ) | ||
dd: | if v then | ||
dd:newline() | |||
:node( ul ) | |||
if css then | |||
dd:css( css ) | |||
if class then | |||
dd:addClass( class ) | |||
end | |||
if ts then | |||
dd:newline() | |||
dd:node( ts ) | |||
end | |||
end | |||
Data.params[ access ].suggestedvalues = v | |||
end | end | ||
end | end | ||
else | else | ||
dd:wikitext( show ) | dd:wikitext( show ) | ||
end | end | ||
dl:node( dt ) | dl:node( mw.html.create( "dt" ) | ||
:wikitext( section ) ) | |||
:node( dd ) | :node( dd ) | ||
end | end | ||
Line 614: | Line 1,243: | ||
-- type | -- type | ||
if type( param.type ) == "string" then | |||
param.type = mw.text.trim( param.type ) | |||
if param.type == "" then | |||
param.type = false | |||
end | |||
end | |||
if param.type then | if param.type then | ||
s = Permit.types[ param.type ] | s = Permit.types[ param.type ] | ||
typed = mw.html.create( "td" ) | typed = mw.html.create( "td" ) | ||
:addClass( styles .. "type" ) | |||
if s then | if s then | ||
if | if s == "string" then | ||
Data.params[ access ].type = s | Data.params[ access ].type = s | ||
typed:wikitext( | typed:wikitext( factory( "doc-param-type-" .. s ) ) | ||
:tag( "br" ) | :tag( "br" ) | ||
typed:node( mw.html.create( "span" ) | typed:node( mw.html.create( "span" ) | ||
Line 627: | Line 1,263: | ||
Data.lasting = true | Data.lasting = true | ||
else | else | ||
s = | local support = Config[ "support4" .. param.type ] | ||
s = factory( "doc-param-type-" .. param.type ) | |||
if support then | |||
s = string.format( "[[%s|%s]]", support, s ) | |||
end | |||
typed:wikitext( s ) | typed:wikitext( s ) | ||
end | end | ||
Line 635: | Line 1,275: | ||
:wikitext( "INVALID" ) | :wikitext( "INVALID" ) | ||
s = string.format( "params.<code>%s</code>.type", access ) | s = string.format( "params.<code>%s</code>.type", access ) | ||
Fault( | Fault( factory( "invalid-value" ):gsub( "$1", s ) ) | ||
legal = false | legal = false | ||
end | end | ||
else | else | ||
typed = mw.html.create( "td" ) | typed = mw.html.create( "td" ) | ||
:wikitext( | :wikitext( factory( "doc-param-type-unknown" ) ) | ||
Data.params[ access ].type = "unknown" | |||
if param.default then | |||
Data.params[ access ].default = nil | |||
Fault( "Default value requires <code>type</code>" ) | |||
legal = false | |||
end | |||
end | end | ||
typed:addClass( "navigation-not-searchable" ) | |||
-- status | -- status | ||
if param.required then | if param.required then | ||
mode = 1 | mode = 1 | ||
if param.autovalue then | |||
Fault( string.format( "autovalued <code>%s</code> required", | |||
access ) ) | |||
legal = false | |||
end | |||
if param.default then | |||
Fault( string.format( "Defaulted <code>%s</code> required", | |||
access ) ) | |||
legal = false | |||
end | |||
if param.deprecated then | if param.deprecated then | ||
Fault( string.format( "Required deprecated <code>%s</code>", | Fault( string.format( "Required deprecated <code>%s</code>", | ||
Line 659: | Line 1,315: | ||
end | end | ||
status = ranking[ mode ] | status = ranking[ mode ] | ||
ranking = | ranking = factory( "doc-param-status-" .. status ) | ||
if mode == 1 or mode == 4 then | if mode == 1 or mode == 4 then | ||
ranking = mw.html.create( "span" ) | ranking = mw.html.create( "span" ) | ||
Line 667: | Line 1,323: | ||
ranking:tag( "br" ) | ranking:tag( "br" ) | ||
ranking:wikitext( param.deprecated ) | ranking:wikitext( param.deprecated ) | ||
end | |||
if param.suggested and mode == 4 then | |||
s = string.format( "Suggesting deprecated <code>%s</code>", | |||
access ) | |||
Fault( s ) | |||
legal = false | |||
end | end | ||
end | end | ||
eager:attr( "data-sort-value", tostring( mode ) ) | |||
:node( ranking ) | |||
:addClass( string.format( "%sstatus-%s %s", | |||
styles, status, | |||
"navigation-not-searchable" ) ) | |||
-- <tr> | -- <tr> | ||
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) | r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) | ||
: | :css( Permit.css[ status ] ) | ||
:addClass( styles .. status ) | |||
:node( begin ) | :node( begin ) | ||
:node( code ) | :node( code ) | ||
:node( desc ) | :node( desc ) | ||
:node( typed ) | :node( typed ) | ||
:node( | :node( eager ) | ||
:newline() | :newline() | ||
if not legal then | if not legal then | ||
Line 694: | Line 1,360: | ||
local r | local r | ||
if Data.tree and Data.tree.params then | if Data.tree and Data.tree.params then | ||
local tbl = mw.html.create( "table" ) | |||
local tbl | local tr = mw.html.create( "tr" ) | ||
local tr | |||
feat() | feat() | ||
if Data.order and #Data.order > 1 then | if Data.order and #Data.order > 1 then | ||
tbl:addClass( "sortable" ) | tbl:addClass( "sortable" ) | ||
end | end | ||
if type( Config.classTable ) == "table" then | |||
for k, v in pairs( Config.classTable ) do | |||
-- | tbl:addClass( v ) | ||
if Config. | end -- for k, v | ||
tbl: | end | ||
if type( Config.cssTable ) == "table" then | |||
tbl:css( Config.cssTable ) | |||
end | end | ||
tr:node( mw.html.create( "th" ) | tr:addClass( "navigation-not-searchable" ) | ||
:node( mw.html.create( "th" ) | |||
:attr( "colspan", "2" ) | :attr( "colspan", "2" ) | ||
: | :css( Permit.css.tablehead ) | ||
:wikitext( | :wikitext( factory( "doc-param-name" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :css( Permit.css.tablehead ) | ||
:wikitext( | :wikitext( factory( "doc-param-desc" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :css( Permit.css.tablehead ) | ||
:wikitext( | :wikitext( factory( "doc-param-type" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :css( Permit.css.tablehead ) | ||
:wikitext( | :wikitext( factory( "doc-param-status" ) ) ) | ||
tbl:newline() | tbl:newline() | ||
-- :node( mw.html.create( "thead" ) | -- :node( mw.html.create( "thead" ) | ||
:node( tr ) | :node( tr ) | ||
-- | -- ) | ||
:newline() | :newline() | ||
if Data.order then | if Data.order then | ||
local leave, s | |||
for i = 1, #Data.order do | for i = 1, #Data.order do | ||
tbl:node( | s = Data.order[ i ] | ||
if s:sub( 1, 1 ) == "=" then | |||
leave = true | |||
tbl:node( fatten( s ) ) | |||
Data.order[ i ] = false | |||
elseif s:match( "[=|]" ) then | |||
Fault( string.format( "Bad param <code>%s</code>", | |||
s ) ) | |||
else | |||
tbl:node( feature( s ) ) | |||
end | |||
end -- for i = 1, #Data.order | end -- for i = 1, #Data.order | ||
if leave then | |||
for i = #Data.order, 1, -1 do | |||
if not Data.order[ i ] then | |||
table.remove( Data.order, i ) | |||
end | |||
end -- for i = #Data.order, 1, -1 | |||
end | |||
Data.tag.paramOrder = Data.order | |||
end | end | ||
if Config. | if Config.cssTabWrap or Data.scroll then | ||
r = mw.html.create( "div" ) | r = mw.html.create( "div" ) | ||
if type( Config.cssTabWrap ) == "table" then | |||
r:css( Config.cssTabWrap ) | |||
elseif type( Config.cssTabWrap ) == "string" then | |||
-- deprecated | |||
r:cssText( Config.cssTabWrap ) | |||
end | |||
if Data.scroll then | |||
r:css( "height", Data.scroll ) | |||
:css( "overflow", "auto" ) | |||
end | |||
r:node( tbl ) | |||
else | else | ||
r = tbl | r = tbl | ||
Line 744: | Line 1,439: | ||
local function finalize() | local function fellow( any, assigned, at ) | ||
-- Check sets[] parameter and issue error message, if necessary | |||
-- Parameter: | |||
-- any -- should be number | |||
-- assigned -- parameter name | |||
-- at -- number, of set | |||
local s | |||
if type( any ) ~= "number" then | |||
s = "<code>sets[%d].params[%s]</code>??" | |||
Fault( string.format( s, | |||
at, | |||
mw.text.nowiki( tostring( any ) ) ) ) | |||
elseif type( assigned ) == "string" then | |||
if not Data.got.params[ assigned ] then | |||
s = "<code>sets[%d].params %s</code> is undefined" | |||
Fault( string.format( s, at, assigned ) ) | |||
end | |||
else | |||
s = "<code>sets[%d].params[%d] = %s</code>??" | |||
Fault( string.format( s, k, type( assigned ) ) ) | |||
end | |||
end -- fellow() | |||
local function fellows() | |||
-- Check sets[] and issue error message, if necessary | |||
local s | |||
if type( Data.got.sets ) == "table" then | |||
if type( Data.got.params ) == "table" then | |||
for k, v in pairs( Data.got.sets ) do | |||
if type( k ) == "number" then | |||
if type( v ) == "table" then | |||
for ek, ev in pairs( v ) do | |||
if ek == "label" then | |||
s = type( ev ) | |||
if s ~= "string" and | |||
s ~= "table" then | |||
s = "<code>sets[%d].label</code>??" | |||
Fault( string.format( s, k ) ) | |||
end | |||
elseif ek == "params" and | |||
type( ev ) == "table" then | |||
for pk, pv in pairs( ev ) do | |||
fellow( pk, pv, k ) | |||
end -- for pk, pv | |||
else | |||
ek = mw.text.nowiki( tostring( ek ) ) | |||
s = "<code>sets[%d][%s]</code>??" | |||
Fault( string.format( s, k, ek ) ) | |||
end | |||
end -- for ek, ev | |||
else | |||
k = mw.text.nowiki( tostring( k ) ) | |||
v = mw.text.nowiki( tostring( v ) ) | |||
s = string.format( "<code>sets[%s][%s]</code>??", | |||
k, v ) | |||
Fault( s ) | |||
end | |||
else | |||
k = mw.text.nowiki( tostring( k ) ) | |||
s = string.format( "<code>sets[%s]</code> ?????", k ) | |||
Fault( s ) | |||
end | |||
end -- for k, v | |||
else | |||
s = "<code>params</code> required for <code>sets</code>" | |||
Fault( s ) | |||
end | |||
else | |||
s = "<code>sets</code> needs to be of <code>object</code> type" | |||
Fault( s ) | |||
end | |||
end -- fellows() | |||
local function finalize( advance ) | |||
-- Wrap presentation into frame | -- Wrap presentation into frame | ||
-- Parameter: | |||
-- advance -- true, for nice | |||
-- Returns string | -- Returns string | ||
local r | local r, lapsus | ||
if Data.div then | if Data.div then | ||
r = tostring( Data.div ) | r = tostring( Data.div ) | ||
Line 753: | Line 1,527: | ||
r = Data.strip | r = Data.strip | ||
else | else | ||
r = "" | lapsus = true | ||
r = "" | |||
end | |||
r = r .. failures() | |||
if Data.source then | |||
local live = ( advance or lapsus ) | |||
if not live then | |||
live = TemplateData.frame:preprocess( "{{REVISIONID}}" ) | |||
live = ( live == "" ) | |||
end | |||
if live then | |||
r = r .. fancy( advance, lapsus ) | |||
end | |||
end | end | ||
return r | return r | ||
end -- finalize() | end -- finalize() | ||
Line 778: | Line 1,564: | ||
local function flat( adjust ) | local function flat( adjust ) | ||
-- Remove formatting from text string | -- Remove formatting from text string for VE | ||
-- Parameter: | -- Parameter: | ||
-- arglist -- string, to be stripped, or nil | -- arglist -- string, to be stripped, or nil | ||
Line 786: | Line 1,572: | ||
r = adjust:gsub( "\n", " " ) | r = adjust:gsub( "\n", " " ) | ||
if r:find( "<noexport>", 1, true ) then | if r:find( "<noexport>", 1, true ) then | ||
r = r:gsub( "<noexport> | r = r:gsub( "<noexport>.*</noexport>", "" ) | ||
end | |||
if r:find( "<exportonly>", 1, true ) then | |||
r = r:gsub( "</?exportonly>", "" ) | |||
end | |||
if r:find( "''", 1, true ) then | |||
r = r:gsub( "'''", "" ):gsub( "''", "" ) | |||
end | |||
if r:find( "<", 1, true ) then | |||
local Text = Fetch( "Text" ) | |||
r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) ) | |||
end | |||
if r:find( "[", 1, true ) then | |||
local WLink = Fetch( "WLink" ) | |||
if WLink.isBracketedURL( r ) then | |||
r = r:gsub( "%[([hf]tt?ps?://%S+) [^%]]+%]", "%1" ) | |||
end | |||
r = WLink.getPlain( r ) | |||
end | end | ||
if r:find( "&", 1, true ) then | if r:find( "&", 1, true ) then | ||
r = mw.text.decode( r ) | r = mw.text.decode( r ) | ||
if r:find( "­", 1, true ) then | |||
r = r:gsub( "­", "" ) | |||
end | |||
end | end | ||
end | end | ||
Line 865: | Line 1,670: | ||
if scope then | if scope then | ||
s = type( v ) | s = type( v ) | ||
if s == "string" then | if s == "string" and k ~= "format" then | ||
v = mw.text.trim( v ) | v = mw.text.trim( v ) | ||
end | end | ||
Line 871: | Line 1,676: | ||
if scope:find( "I18N", 1, true ) then | if scope:find( "I18N", 1, true ) then | ||
if s == "string" then | if s == "string" then | ||
elem = | elem = fair( v ) | ||
elseif s == "table" then | |||
local translated | local translated | ||
v, translated = faraway( v ) | v, translated = faraway( v ) | ||
Line 878: | Line 1,683: | ||
if translated and | if translated and | ||
k == "description" then | k == "description" then | ||
elem = { [ 1 ] = | elem = { [ 1 ] = fair( v ), | ||
[ 2 ] = translated } | [ 2 ] = translated } | ||
else | else | ||
elem = | elem = fair( v ) | ||
end | end | ||
else | else | ||
Line 887: | Line 1,692: | ||
end | end | ||
end | end | ||
if v then | if type( v ) == "string" then | ||
if scope:find( "nowiki", 1, true ) then | if k == "deprecated" then | ||
if v == "1" then | |||
v = true | |||
elseif v == "0" then | |||
v = false | |||
end | |||
elem = v | |||
elseif scope:find( "nowiki", 1, true ) then | |||
elem = mw.text.nowiki( v ) | elem = mw.text.nowiki( v ) | ||
elem = elem:gsub( " \n", "<br>" ) | |||
v = v:gsub( string.char( 13 ), "" ) | |||
else | else | ||
v = flat( v ) | v = flat( v ) | ||
end | |||
elseif s == "boolean" then | |||
if scope:find( "boolean", 1, true ) then | |||
elem = v | |||
else | |||
s = "Type <code>boolean</code> bad for " | |||
.. f( k, slot ) | |||
Fault( s ) | |||
end | end | ||
end | end | ||
Line 899: | Line 1,721: | ||
elem = nil | elem = nil | ||
elseif k == "format" and not access then | elseif k == "format" and not access then | ||
elem = mw.text.decode( v ) | |||
v = nil | |||
elseif k == "inherits" then | elseif k == "inherits" then | ||
elem = v | elem = v | ||
Line 908: | Line 1,730: | ||
Data.heirs[ slot ] = v | Data.heirs[ slot ] = v | ||
v = nil | v = nil | ||
elseif k == "style" then | |||
elem = v | |||
v = nil | |||
elseif s == "string" then | elseif s == "string" then | ||
v = mw.text.nowiki( v ) | v = mw.text.nowiki( v ) | ||
Line 917: | Line 1,742: | ||
if type( elem ) ~= "nil" then | if type( elem ) ~= "nil" then | ||
if not target then | if not target then | ||
if access then | |||
if not Data.tree.params then | |||
Data.tree.params = { } | |||
end | |||
Data.tree.params[ slot ] = { } | |||
target = Data.tree.params[ slot ] | |||
else | |||
Data.tree = { } | |||
target = Data.tree | |||
end | |||
end | end | ||
target[ k ] = elem | target[ k ] = elem | ||
Line 934: | Line 1,759: | ||
if not tag then | if not tag then | ||
if access then | if access then | ||
if not Data.params then | if type( v ) == "string" and | ||
Data.params = { } | v.sub( 1, 1 ) == "=" then | ||
v = nil | |||
else | |||
if not Data.params then | |||
Data.params = { } | |||
end | |||
Data.params[ slot ] = { } | |||
tag = Data.params[ slot ] | |||
end | end | ||
else | else | ||
Data.tag = { } | Data.tag = { } | ||
Line 944: | Line 1,774: | ||
end | end | ||
end | end | ||
tag[ k ] = v | if type( v ) ~= "nil" and | ||
k ~= "suggestedvalues" then | |||
tag[ k ] = v | |||
end | |||
end | end | ||
else | else | ||
Line 955: | Line 1,788: | ||
end | end | ||
end -- for k, v | end -- for k, v | ||
if not access and Data.got.sets then | |||
fellows() | |||
end | |||
else | else | ||
Fault( f() .. " needs to be of <code>object</code> type" ) | Fault( f() .. " needs to be of <code>object</code> type" ) | ||
Line 963: | Line 1,799: | ||
local function format() | local function format() | ||
-- Build formatted element | |||
-- Returns <inline> | |||
local source = Data.tree.format:lower() | |||
local r, s | |||
if source == "inline" or source == "block" then | |||
r = mw.html.create( "i" ) | |||
:wikitext( source ) | |||
else | |||
local code | |||
if source:find( "|", 1, true ) then | |||
local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$" | |||
if source:match( scan ) then | |||
code = source:gsub( "\n", "N" ) | |||
else | |||
s = mw.text.nowiki( source ):gsub( "\n", "\n" ) | |||
s = tostring( mw.html.create( "code" ) | |||
:wikitext( s ) ) | |||
Fault( "Invalid format " .. s ) | |||
source = false | |||
end | |||
else | |||
local words = mw.text.split( source, "%s+" ) | |||
local show, start, support, unknown | |||
for i = 1, #words do | |||
s = words[ i ] | |||
if i == 1 then | |||
start = s | |||
end | |||
support = Permit.builder[ s ] | |||
if support == start or | |||
support == "*" then | |||
Permit.builder[ s ] = true | |||
elseif s:match( "^[1-9]%d?" ) and | |||
Permit.builder.align then | |||
Permit.builder.align = tonumber( s ) | |||
else | |||
if unknown then | |||
unknown = string.format( "%s %s", unknown, s ) | |||
else | |||
unknown = s | |||
end | |||
end | |||
end -- i = 1, #words | |||
if unknown then | |||
s = tostring( mw.html.create( "code" ) | |||
:css( "white-space", "nowrap" ) | |||
:wikitext( s ) ) | |||
Fault( "Unknown/misplaced format keyword " .. s ) | |||
source = false | |||
start = false | |||
end | |||
if start == "inline" then | |||
if Permit.builder.half == true then | |||
show = "inline half" | |||
code = "{{_ |_=_}}" | |||
elseif Permit.builder.grouped == true then | |||
show = "inline grouped" | |||
code = "{{_ | _=_}}" | |||
elseif Permit.builder.spaced == true then | |||
show = "inline spaced" | |||
code = "{{_ | _ = _ }}" | |||
end | |||
if Permit.builder.newlines == true then | |||
show = show or "inline" | |||
code = code or "{{_|_=_}}" | |||
show = show .. " newlines" | |||
code = string.format( "N%sN", code ) | |||
end | |||
elseif start == "block" then | |||
local space = "" -- amid "|" and name | |||
local spaced = " " -- preceding "=" | |||
local spacer = " " -- following "=" | |||
local suffix = "N" -- closing "}}" on new line | |||
show = "block" | |||
if Permit.builder.indent == true then | |||
start = " " | |||
show = "block indent" | |||
else | |||
start = "" | |||
end | |||
if Permit.builder.compressed == true then | |||
spaced = "" | |||
spacer = "" | |||
show = show .. " compressed" | |||
if Permit.builder.last == true then | |||
show = show .. " last" | |||
else | |||
suffix = "" | |||
end | |||
else | |||
if Permit.builder.lead == true then | |||
show = show .. " lead" | |||
space = " " | |||
end | |||
if type( Permit.builder.align ) ~= "string" then | |||
local n | |||
s = " align" | |||
if Permit.builder.align == true then | |||
n = 0 | |||
if type( Data.got ) == "table" and | |||
type( Data.got.params ) == "table" then | |||
for k, v in pairs( Data.got.params ) do | |||
if type( v ) == "table" and | |||
not v.deprecated and | |||
type( k ) == "string" then | |||
k = mw.ustring.len( k ) | |||
if k > n then | |||
n = k | |||
end | |||
end | |||
end -- for k, v | |||
end | |||
else | |||
n = Permit.builder.align | |||
if type( n ) == "number" and n > 1 then | |||
s = string.format( "%s %d", s, n ) | |||
else | |||
n = 0 -- How comes? | |||
end | |||
end | |||
if n > 1 then | |||
spaced = string.rep( "_", n - 1 ) .. " " | |||
end | |||
show = show .. s | |||
elseif Permit.builder.after == true then | |||
spaced = "" | |||
show = show .. " after" | |||
elseif Permit.builder.dense == true then | |||
spaced = "" | |||
spacer = "" | |||
show = show .. " dense" | |||
end | |||
if Permit.builder.last == true then | |||
suffix = spacer | |||
show = show .. " last" | |||
end | |||
end | |||
code = string.format( "N{{_N%s|%s_%s=%s_%s}}N", | |||
start, | |||
space, | |||
spaced, | |||
spacer, | |||
suffix ) | |||
if show == "block" then | |||
show = "block newlines" | |||
end | |||
end | |||
if show then | |||
r = mw.html.create( "span" ) | |||
:wikitext( show ) | |||
end | |||
end | |||
if code then | |||
source = code:gsub( "N", "\n" ) | |||
code = mw.text.nowiki( code ):gsub( "N", "\n" ) | |||
code = mw.html.create( "code" ) | |||
:css( "margin-left", "1em" ) | |||
:css( "margin-right", "1em" ) | |||
:wikitext( code ) | |||
if r then | |||
r = mw.html.create( "span" ) | |||
:node( r ) | |||
:node( code ) | |||
else | |||
r = code | |||
end | |||
end | |||
end | |||
if source and Data.tag then | |||
Data.tag.format = source | |||
end | |||
return r | |||
end -- format() | |||
local function formatter() | |||
-- Build presented documentation | -- Build presented documentation | ||
-- Returns <div> | -- Returns <div> | ||
local r = mw.html.create( "div" ) | local r = mw.html.create( "div" ) | ||
local | local x = fashioned( Data.tree, true, r ) | ||
if | local s | ||
r | if x then | ||
r = x | |||
end | end | ||
if Data.leading then | if Data.leading then | ||
local toc = mw.html.create( "div" ) | local toc = mw.html.create( "div" ) | ||
local shift | |||
if Config.suppressTOCnum then | if Config.suppressTOCnum then | ||
toc:addClass( Config.suppressTOCnum ) | toc:addClass( Config.suppressTOCnum ) | ||
if type( Config.stylesTOCnum ) == "string" then | |||
local src = Config.stylesTOCnum .. "/styles.css" | |||
s = TemplateData.frame:extensionTag( "templatestyles", | |||
nil, | |||
{ src = src } ) | |||
r:newline() | |||
:node( s ) | |||
end | |||
end | end | ||
toc:css( "margin-top", "0.5em" ) | toc:addClass( "navigation-not-searchable" ) | ||
:css( "margin-top", "0.5em" ) | |||
:wikitext( "__TOC__" ) | :wikitext( "__TOC__" ) | ||
if Data.sibling then | |||
local block = mw.html.create( "div" ) | |||
if TemplateData.ltr then | |||
shift = "right" | |||
else | |||
shift = "left" | |||
end | |||
block:css( "float", shift ) | |||
:wikitext( Data.sibling ) | |||
r:newline() | |||
:node( block ) | |||
:newline() | |||
end | |||
r:newline() | r:newline() | ||
:node( toc ) | :node( toc ) | ||
:newline() | :newline() | ||
if shift then | |||
r:node( mw.html.create( "div" ) | |||
:css( "clear", shift ) ) | |||
:newline() | |||
end | |||
end | end | ||
s = features() | s = features() | ||
if s then | if s then | ||
if Data.leading then | if Data.leading then | ||
r:node( mw.html.create( " | r:node( mw.html.create( "h" .. Config.nested ) | ||
:wikitext( | :wikitext( factory( "doc-params" ) ) ) | ||
:newline() | :newline() | ||
end | end | ||
r:node( s ) | r:node( s ) | ||
end | |||
if Data.shared then | |||
local global = mw.html.create( "div" ) | |||
:attr( "id", "templatedata-global" ) | |||
local shift | |||
if TemplateData.ltr then | |||
shift = "right" | |||
else | |||
shift = "left" | |||
end | |||
global:css( "float", shift ) | |||
:wikitext( string.format( "[[%s|%s]]", | |||
Data.shared, "Global" ) ) | |||
r:newline() | |||
:node( global ) | |||
end | end | ||
if Data.tree and Data.tree.format then | if Data.tree and Data.tree.format then | ||
local e | local e = format() | ||
if e then | |||
if | local show = "Format" | ||
if Config.supportFormat then | |||
show = string.format( "[[%s|%s]]", | |||
Config.supportFormat, show ) | |||
end | |||
r:node( mw.html.create( "p" ) | |||
:addClass( "navigation-not-searchable" ) | |||
:wikitext( show .. ": " ) | |||
:node( e ) ) | |||
end | end | ||
end | end | ||
return r | return r | ||
end -- | end -- formatter() | ||
Line 1,010: | Line 2,068: | ||
local function free() | local function free() | ||
-- Remove JSON comment lines | -- Remove JSON comment lines | ||
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])", | if Data.source:find( "//", 1, true ) then | ||
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])", | |||
"%1%3" ) | |||
end | |||
end -- free() | end -- free() | ||
Line 1,017: | Line 2,077: | ||
local function full() | local function full() | ||
-- Build | -- Build survey table from JSON data, append invisible <templatedata> | ||
Data.div = mw.html.create( "div" ) | Data.div = mw.html.create( "div" ) | ||
:addClass( "mw-templatedata-doc-wrap" ) | :addClass( "mw-templatedata-doc-wrap" ) | ||
if Permit.css.bg then | |||
Data.div:css( Permit.css.bg ) | |||
end | |||
if Permit.css.fg then | |||
Data.div:css( Permit.css.fg ) | |||
end | |||
focus() | focus() | ||
if Data.tag then | if Data.tag then | ||
Line 1,032: | Line 2,097: | ||
end | end | ||
end | end | ||
Data.div:node( | Data.div:node( formatter() ) | ||
if not Data.lazy then | if not Data.lazy then | ||
Data.slim = flush() | Data.slim = flush() | ||
Line 1,043: | Line 2,108: | ||
div:wikitext( Data.strip ) | div:wikitext( Data.strip ) | ||
if Config.loudly then | if Config.loudly then | ||
Data.div:node( mw.html.create( "hr" ) | |||
Data.div:node( mw.html.create( "hr" | :css( { height = "7ex" } ) ) | ||
else | else | ||
div:css( "display", "none" ) | |||
end | end | ||
Data.div:node( div ) | |||
end | end | ||
end | |||
if Data.lasting then | |||
Fault( "deprecated type syntax" ) | |||
end | |||
if Data.less then | |||
Fault( Config.solo ) | |||
end | end | ||
end -- full() | end -- full() | ||
Line 1,064: | Line 2,127: | ||
local function furnish( adapt, arglist ) | local function furnish( adapt, arglist ) | ||
-- | -- Analyze transclusion | ||
-- Parameter: | -- Parameter: | ||
-- adapt -- table, #invoke parameters | -- adapt -- table, #invoke parameters | ||
-- arglist -- table, template parameters | -- arglist -- table, template parameters | ||
-- Returns string | -- Returns string | ||
local source | local source | ||
for k, v in pairs( Config ) do | favorize() | ||
-- deprecated: | |||
for k, v in pairs( Config.basicCnf ) do | |||
if adapt[ k ] and adapt[ k ] ~= "" then | if adapt[ k ] and adapt[ k ] ~= "" then | ||
Config[ v ] = adapt[ k ] | Config[ v ] = adapt[ k ] | ||
end | end | ||
end -- for k, v | end -- for k, v | ||
if arglist.heading and arglist.heading:match( "^[3-6]$" ) then | |||
Config.nested = arglist.heading | |||
else | |||
Config.nested = "2" | |||
end | |||
Config.loudly = faculty( arglist.debug or adapt.debug ) | Config.loudly = faculty( arglist.debug or adapt.debug ) | ||
Data.lazy = faculty( arglist.lazy ) and not Config.loudly | Data.lazy = faculty( arglist.lazy ) and not Config.loudly | ||
Data.leading = faculty( arglist.TOC ) | Data.leading = faculty( arglist.TOC ) | ||
if Data.leading and arglist.TOCsibling then | |||
Data.sibling = mw.text.trim( arglist.TOCsibling ) | |||
end | |||
if arglist.lang then | |||
Data.slang = arglist.lang:lower() | |||
elseif adapt.lang then | |||
Data.slang = adapt.lang:lower() | |||
end | |||
if arglist.JSON then | if arglist.JSON then | ||
source = arglist.JSON | source = arglist.JSON | ||
elseif arglist.Global then | |||
source = TemplateData.getGlobalJSON( arglist.Global, | |||
arglist.Local ) | |||
elseif arglist[ 1 ] then | elseif arglist[ 1 ] then | ||
local s = mw.text.trim( arglist[ 1 ] ) | local s = mw.text.trim( arglist[ 1 ] ) | ||
Line 1,097: | Line 2,169: | ||
source = s | source = s | ||
elseif mw.ustring.sub( s, 1, 8 ) == | elseif mw.ustring.sub( s, 1, 8 ) == | ||
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then | mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then | ||
Data.strip = s | Data.strip = s | ||
end | end | ||
end | |||
if type( arglist.vertical ) == "string" and | |||
arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then | |||
Data.scroll = arglist.vertical | |||
end | end | ||
if not source then | if not source then | ||
Line 1,105: | Line 2,181: | ||
source = find() | source = find() | ||
if not source and | if not source and | ||
not Data.title.text:match( Config.subpage ) then | not Data.title.text:match( Config.subpage ) then | ||
local s = string.format( Config.suffix, | local s = string.format( Config.suffix, | ||
Line 1,114: | Line 2,189: | ||
end | end | ||
end | end | ||
end | end | ||
if not Data.lazy | if not Data.lazy then | ||
if not Data.title then | if not Data.title then | ||
Data.title = mw.title.getCurrentTitle() | Data.title = mw.title.getCurrentTitle() | ||
Line 1,127: | Line 2,196: | ||
Data.lazy = Data.title.text:match( Config.subpage ) | Data.lazy = Data.title.text:match( Config.subpage ) | ||
end | end | ||
TemplateData.getPlainJSON( source ) | if type( source ) == "string" then | ||
return finalize() | TemplateData.getPlainJSON( source ) | ||
end | |||
return finalize( faculty( arglist.source ) ) | |||
end -- furnish() | end -- furnish() | ||
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 | local r | ||
if not | if last or link or linked or since == "wikidata" then | ||
r = TemplateData. | local item = Failsafe.item | ||
since = false | |||
r = | 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() | |||
TemplateData.getGlobalJSON = function ( access, adapt ) | |||
-- Retrieve TemplateData from a global repository (JSON) | |||
-- Parameter: | |||
-- access -- string, with page specifier (on WikiMedia Commons) | |||
-- adapt -- JSON string or table with local overrides | |||
-- Returns true, if succeeded | |||
local plugin = Fetch( "/global" ) | |||
local r | |||
if type( plugin ) == "table" and | |||
type( plugin.fetch ) == "function" then | |||
local s, got = plugin.fetch( access, adapt ) | |||
if got then | |||
Data.got = got | |||
Data.order = got.paramOrder | |||
Data.shared = s | |||
r = true | |||
full() | |||
else | |||
Fault( s ) | |||
end | |||
end | end | ||
return r | return r | ||
end -- TemplateData. | end -- TemplateData.getGlobalJSON() | ||
Line 1,153: | Line 2,294: | ||
-- Returns string, or not | -- Returns string, or not | ||
if type( adapt ) == "string" then | if type( adapt ) == "string" then | ||
local JSONutil = Fetch( "JSONutil", true ) | |||
Data.source = adapt | Data.source = adapt | ||
free() | free() | ||
Data.got = mw.text.jsonDecode | if JSONutil then | ||
if Data.got then | local Multilingual = Fetch( "Multilingual", true ) | ||
local f | |||
if Multilingual then | |||
f = Multilingual.i18n | |||
end | |||
Data.got = JSONutil.fetch( Data.source, true, f ) | |||
else | |||
local lucky | |||
lucky, Data.got = pcall( mw.text.jsonDecode, Data.source ) | |||
end | |||
if type( Data.got ) == "table" then | |||
full() | full() | ||
if Data. | elseif not Data.strip then | ||
local scream = type( Data.got ) | |||
if scream == "string" then | |||
scream = Data.got | |||
else | |||
scream = "Data.got: " .. scream | |||
end | end | ||
Fault( "fatal JSON error: " .. scream ) | |||
Fault( "fatal JSON error" ) | |||
end | end | ||
end | end | ||
Line 1,184: | Line 2,336: | ||
p.f = function ( frame ) | p.f = function ( frame ) | ||
-- | -- Template call | ||
local lucky, r | |||
local lucky, | |||
TemplateData.frame = frame | TemplateData.frame = frame | ||
lucky, | lucky, r = pcall( furnish, frame.args, frame:getParent().args ) | ||
if not lucky then | if not lucky then | ||
Fault( "INTERNAL: " .. | Fault( "INTERNAL: " .. r ) | ||
r = failures() | |||
end | end | ||
return | return r | ||
end -- p.f | end -- p.f | ||
p.failsafe = function ( frame ) | p.failsafe = function ( frame ) | ||
Line 1,211: | Line 2,362: | ||
end | end | ||
end | end | ||
return | return Failsafe.failsafe( since ) or "" | ||
end -- p.failsafe | end -- p.failsafe | ||
p.TemplateData = function () | p.TemplateData = function () |
Latest revision as of 18:54, 4 December 2022
Documentation for this module may be created at Module:Format TemplateData/doc
local TemplateData = { suite = "TemplateData", serial = "2022-03-10", item = 46997995 } --[==[ improve template:TemplateData ]==] local Failsafe = TemplateData local Config = { -- multiple option names mapped into unique internal fields basicCnf = { catProblem = "strange", classMultiColumns = "selMultClm", classNoNumTOC = "suppressTOCnum", classTable = "classTable", cssParWrap = "cssTabWrap", cssParams = "cssTable", docpageCreate = "suffix", docpageDetect = "subpage", helpBoolean = "support4boolean", helpContent = "support4content", helpDate = "support4date", helpFile = "support4wiki-file-name", helpFormat = "supportFormat", helpLine = "support4line", helpNumber = "support4number", helpPage = "support4wiki-page-name", helpString = "support4string", helpTemplate = "support4wiki-template-name", helpURL = "support4url", helpUser = "support4wiki-user-name", msgDescMiss = "solo", tStylesTOCnum = "stylesTOCnum", tStylesMultiColumns = "stylesMultClm" }, classTable = { "wikitable" }, -- classes for params table debugmultilang = "C0C0C0", loudly = false, -- show exported element, etc. solo = false, -- complaint on missing description strange = false, -- title of maintenance category cssTable = false, -- styles for params table cssTabWrap = false, -- styles for params table wrapper debug = false, subpage = false, -- pattern to identify subpage suffix = false, -- subpage creation scheme suppressTOCnum = false, -- class for TOC number suppression jsonDebug = "json-code-lint" -- class for jsonDebug tool } local Data = { div = false, -- <div class="mw-templatedata-doc-wrap"> got = false, -- table, initial templatedata object heirs = false, -- table, params that are inherited jump = false, -- source position at end of "params" less = false, -- main description missing lasting = false, -- old syntax encountered lazy = false, -- doc mode; do not generate effective <templatedata> leading = false, -- show TOC -- low = false, -- 1= mode order = false, -- parameter sequence params = false, -- table, exported parameters scream = false, -- error messages sibling = false, -- TOC juxtaposed slang = nil, -- project/user language code slim = false, -- JSON reduced to plain source = false, -- JSON input strip = false, -- <templatedata> evaluation tag = false, -- table, exported root element title = false, -- page tree = false -- table, rewritten templatedata object } local Permit = { builder = { after = "block", align = "block", block = "block", compressed = "block", dense = "block", grouped = "inline", half = "inline", indent = "block", inline = "inline", last = "block", lead = "block", newlines = "*", spaced = "inline" }, colors = { bg = "FFFFFF", fg = "000000", tableheadbg = "B3B7FF", required = "EAF3FF", suggested = "FFFFFF", optional = "EAECF0", deprecated = "FFCBCB" }, params = { aliases = "table", autovalue = "string", default = "string table I18N nowiki", deprecated = "boolean string I18N", description = "string table I18N", example = "string table I18N nowiki", label = "string table I18N", inherits = "string", required = "boolean", style = "string table", suggested = "boolean", suggestedvalues = "string table number boolean", type = "string" }, root = { description = "string table I18N", format = "string", maps = "table", params = "table", paramOrder = "table", sets = "table" }, search = "[{,]%%s*(['\"])%s%%1%%s*:%%s*%%{", types = { boolean = true, content = true, date = true, line = true, number = true, string = true, unknown = true, url = true, ["wiki-file-name"] = true, ["wiki-page-name"] = true, ["wiki-template-name"] = true, ["wiki-user-name"] = true, ["unbalanced-wikitext"] = true, ["string/line"] = "line", ["string/wiki-page-name"] = "wiki-page-name", ["string/wiki-user-name"] = "wiki-user-name" } } local function Fault( alert ) -- Memorize error message -- Parameter: -- alert -- string, error message if Data.scream then Data.scream = string.format( "%s *** %s", Data.scream, alert ) else Data.scream = alert end end -- Fault() local function Fetch( ask, allow ) -- Fetch module -- Parameter: -- ask -- string, with name -- "/global" -- "JSONutil" -- "Multilingual" -- "Text" -- "WLink" -- allow -- true: no error if unavailable -- Returns table of module -- error: Module not available local sign = ask local r, stem if sign:sub( 1, 1 ) == "/" then sign = TemplateData.frame:getTitle() .. sign else stem = sign sign = "Module:" .. stem end if TemplateData.extern then r = TemplateData.extern[ sign ] else TemplateData.extern = { } end if not r then local lucky, g = pcall( require, sign ) if type( g ) == "table" then if stem and type( g[ stem ] ) == "function" then r = g[ stem ]() else r = g end TemplateData.extern[ sign ] = r elseif not allow then error( string.format( "Fetch(%s) %s", sign, g ), 0 ) end end return r end -- Fetch() local function Foreign() -- Guess human language -- Returns slang, or not if type( Data.slang ) == "nil" then local Multilingual = Fetch( "Multilingual", true ) if Multilingual and type( Multilingual.userLangCode ) == "function" then Data.slang = Multilingual.userLangCode() else Data.slang = mw.language.getContentLanguage():getCode() :lower() end end if Data.slang and mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then Data.slang = false end return Data.slang end -- Foreign() local function facet( ask, at ) -- Find physical position of parameter definition in JSON -- Parameter: -- ask -- string, parameter name -- at -- number, physical position within definition -- Returns number, or nil local seek = string.format( Permit.search, ask:gsub( "%%", "%%%%" ) :gsub( "([%-.()+*?^$%[%]])", "%%%1" ) ) local i, k, r, slice, source if not Data.jump then Data.jump = Data.source:find( "params", 2 ) if Data.jump then Data.jump = Data.jump + 7 else Data.jump = 1 end end i, k = Data.source:find( seek, at + Data.jump ) while i and not r do source = Data.source:sub( k + 1 ) slice = source:match( "^%s*\"([^\"]+)\"s*:" ) if not slice then slice = source:match( "^%s*'([^']+)'%s*:" ) end if ( slice and Permit.params[ slice ] ) or source:match( "^%s*%}" ) then r = k else i, k = Data.source:find( seek, k ) end end -- while i return r end -- facet() local function facilities( apply ) -- Retrieve details of suggestedvalues -- Parameter: -- apply -- table, with plain or enhanced values -- .suggestedvalues -- table|string|number, or more -- Returns -- 1 -- table, with suggestedvalues -- 2 -- table, with CSS map, or not -- 3 -- string, with class, or not -- 4 -- string, with templatestyles, or not local elements = apply.suggestedvalues local s = type( elements ) local r1, r2, r3, r4 if s == "table" then local values = elements.values if type( values ) == "table" then r1 = values if type( elements.scroll ) == "string" then r2 = r2 or { } r2.height = apply.scroll r2.overflow = "auto" end if type( elements.minwidth ) == "string" then local s = type( elements.maxcolumns ) r2 = r2 or { } r2["column-width"] = elements.minwidth if s == "string" or s == "number" then s = tostring( elements.maxcolumns ) r2["column-count"] = s end if type( Config.selMultClm ) == "string" then r3 = Config.selMultClm end if type( Config.stylesMultClm ) == "string" then local src = Config.stylesMultClm .. "/styles.css" r4 = TemplateData.frame :extensionTag( "templatestyles", nil, { src = src } ) end end elseif elements and elements ~= "" then r1 = elements end elseif s == "string" then s = mw.text.trim( about ) if s ~= "" then r1 = { } table.insert( r1, { code = s } ) end elseif s == "number" then r1 = { } table.insert( r1, { code = tostring( elements ) } ) end return r1, r2, r3, r4 end -- facilities() local function factory( adapt ) -- Retrieve localized text from system message -- Parameter: -- adapt -- string, message ID after "templatedata-" -- Returns string, with localized text local o = mw.message.new( "templatedata-" .. adapt ) if Foreign() then o:inLanguage( Data.slang ) end return o:plain() end -- factory() local function faculty( adjust ) -- Test template arg for boolean -- adjust -- string or nil -- Returns boolean local s = type( adjust ) local r if s == "string" then r = mw.text.trim( adjust ) r = ( r ~= "" and r ~= "0" ) elseif s == "boolean" then r = adjust else r = false end return r end -- faculty() local function failures() -- Retrieve error collection and category -- Returns string local r if Data.scream then local e = mw.html.create( "span" ) :addClass( "error" ) :wikitext( Data.scream ) r = tostring( e ) mw.addWarning( "'''TemplateData'''<br />" .. Data.scream ) if Config.strange then r = string.format( "%s[[category:%s]]", r, Config.strange ) end else r = "" end return r end -- failures() local function fair( adjust ) -- Reduce text to one line of plain text, or noexport wikitext blocks -- adjust -- string -- Returns string, with adjusted text local f = function ( a ) return a:gsub( "%s*\n%s*", " " ) :gsub( "%s%s+", " " ) end local tags = { { start = "<noexport>", stop = "</noexport>" }, { start = "<exportonly>", stop = "</exportonly>", l = false } } local r = adjust local i, j, k, s, tag for m = 1, 2 do tag = tags[ m ] if r:find( tag.start, 1, true ) then s = r r = "" i = 1 tag.l = true j, k = s:find( tag.start, i, true ) while j do if j > 1 then r = r .. f( s:sub( i, j - 1 ) ) end i = k + 1 j, k = s:find( tag.stop, i, true ) if j then if m == 1 then r = r .. s:sub( i, j - 1 ) end i = k + 1 j, k = s:find( tag.start, i, true ) else Fault( "missing " .. tag.stop ) end end -- while j r = r .. s:sub( i ) elseif m == 1 then r = f( r ) end end -- for m if tags[ 2 ].l then r = r:gsub( "<exportonly>.*</exportonly>", "" ) end return r end -- fair() local function fancy( advance, alert ) -- Present JSON source -- Parameter: -- advance -- true, for nice -- alert -- true, for visible -- Returns string local r if Data.source then local support = Config.jsonDebug local css if advance then css = { height = "6em", resize = "vertical" } r = { [ 1 ] = "syntaxhighlight", [ 2 ] = Data.source, lang = "json", style = table.concat( css, ";" ) } if alert then r.class( support ) end r = TemplateData.frame:callParserFunction( "#tag", r ) else css = { [ "font-size" ] = "77%", [ "line-height" ] = "1.35" } if alert then css.resize = "vertical" else css.display = "none" end r = mw.html.create( "pre" ) :addClass( support ) :css( css ) :wikitext( mw.text.encode( Data.source ) ) r = tostring( r ) end r = "\n".. r else r = "" end return r end -- fancy() local function faraway( alternatives ) -- Retrieve best language version from multilingual text -- Parameter: -- alternatives -- table, to be evaluated -- Returns -- 1 -- string, with best match -- 2 -- table of other versions, if any local n = 0 local variants = { } local r1, r2 for k, v in pairs( alternatives ) do if type( v ) == "string" then v = mw.text.trim( v ) if v ~= "" and type( k ) == "string" then k = k:lower() variants[ k ] = v n = n + 1 end end end -- for k, v if n > 0 then local Multilingual = Fetch( "Multilingual", true ) if Multilingual and type( Multilingual.i18n ) == "function" then local show, slang = Multilingual.i18n( variants ) if show then r1 = show variants[ slang ] = nil r2 = variants end end if not r1 then Foreign() for k, v in pairs( variants ) do if n == 1 then r1 = v elseif Data.slang == k then variants[ k ] = nil r1 = v r2 = variants end end -- for k, v end if r2 and Multilingual then for k, v in pairs( r2 ) do if v and not Multilingual.isLang( k, true ) then Fault( string.format( "%s <code>lang=%s</code>", "Invalid", k ) ) end end -- for k, v end end return r1, r2 end -- faraway() local function fashioned( about, asked, assign ) -- Create description head -- Parameter: -- about -- table, supposed to contain description -- asked -- true, if mandatory description -- assign -- <block>, if to be equipped -- Returns <block>, with head, or nil local para = assign or mw.html.create( "div" ) local plus, r if about and about.description then if type( about.description ) == "string" then para:wikitext( about.description ) else para:wikitext( about.description[ 1 ] ) plus = mw.html.create( "ul" ) plus:css( "text-align", "left" ) for k, v in pairs( about.description[ 2 ] ) do plus:node( mw.html.create( "li" ) :node( mw.html.create( "code" ) :wikitext( k ) ) :node( mw.html.create( "br" ) ) :wikitext( fair( v ) ) ) end -- for k, v if Config.loudly then plus = mw.html.create( "div" ) :css( "background-color", "#" .. Config.debugmultilang ) :node( plus ) else plus:addClass( "templatedata-maintain" ) :css( "display", "none" ) end end elseif Config.solo and asked then para:addClass( "error" ) :wikitext( Config.solo ) Data.less = true else para = false end if para then if plus then r = mw.html.create( "div" ) :node( para ) :node( plus ) else r = para end end return r end -- fashioned() local function fatten( access ) -- Create table row for sub-headline -- Parameter: -- access -- string, with name -- Returns <tr> local param = Data.tree.params[ access ] local sub, sort = access:match( "(=+)%s*(%S.*)$" ) local headline = mw.html.create( string.format( "h%d", #sub ) ) local r = mw.html.create( "tr" ) local td = mw.html.create( "td" ) :attr( "colspan", "5" ) :attr( "data-sort-value", "!" .. sort ) local s if param.style then s = type( param.style ) if s == "table" then td:css( param.style ) elseif s == "string" then td:cssText( param.style ) end end s = fashioned( param, false, headline ) if s then headline = s else headline:wikitext( sort ) end td:node( headline ) r:node( td ) return r end -- fatten() local function fathers() -- Merge params with inherited values local n = 0 local p = Data.params local t = Data.tree.params local p2, t2 for k, v in pairs( Data.heirs ) do n = n + 1 end -- for k, v for i = 1, n do if Data.heirs then for k, v in pairs( Data.heirs ) do if v and not Data.heirs[ v ] then n = n - 1 t[ k ].inherits = nil Data.heirs[ k ] = nil p2 = { } t2 = { } if p[ v ] then for k2, v2 in pairs( p[ v ] ) do p2[ k2 ] = v2 end -- for k2, v2 if p[ k ] then for k2, v2 in pairs( p[ k ] ) do if type( v2 ) ~= "nil" then p2[ k2 ] = v2 end end -- for k2, v2 end p[ k ] = p2 for k2, v2 in pairs( t[ v ] ) do t2[ k2 ] = v2 end -- for k2, v2 for k2, v2 in pairs( t[ k ] ) do if type( v2 ) ~= "nil" then t2[ k2 ] = v2 end end -- for k2, v2 t[ k ] = t2 else Fault( "No params[] inherits " .. v ) end end end -- for k, v end end -- i = 1, n if n > 0 then local s for k, v in pairs( Data.heirs ) do if v then if s then s = string.format( "%s | %s", s, k ) else s = "Circular inherits: " .. k end end end -- for k, v Fault( s ) end end -- fathers() local function favorize() -- Local customization issues local boole = { ["font-size"] = "125%" } local l, cx = pcall( mw.loadData, TemplateData.frame:getTitle() .. "/config" ) local scripting, style TemplateData.ltr = not mw.language.getContentLanguage():isRTL() if TemplateData.ltr then scripting = "left" else scripting = "right" end boole[ "margin-" .. scripting ] = "3em" Permit.boole = { [false] = { css = boole, lead = true, show = "☐" }, [true] = { css = boole, lead = true, show = "☑" } } Permit.css = { } for k, v in pairs( Permit.colors ) do if k == "tableheadbg" then k = "tablehead" end if k == "fg" then style = "color" else style = "background-color" end Permit.css[ k ] = { } Permit.css[ k ][ style ] = "#" .. v end -- for k, v if type( cx ) == "table" then local c, s if type( cx.permit ) == "table" then if type( cx.permit.boole ) == "table" then if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ false ] = cx.permit.boole[ false ] end if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ true ] = cx.permit.boole[ true ] end end if type( cx.permit.css ) == "table" then for k, v in pairs( cx.permit.css ) do if type( v ) == "table" then Permit.css[ k ] = v end end -- for k, v end end for k, v in pairs( Config.basicCnf ) do s = type( cx[ k ] ) if s == "string" or s == "table" then Config[ v ] = cx[ k ] end end -- for k, v end if type( Config.subpage ) ~= "string" or type( Config.suffix ) ~= "string" then local got = mw.message.new( "templatedata-doc-subpage" ) local suffix if got:isDisabled() then suffix = "doc" else suffix = got:plain() end if type( Config.subpage ) ~= "string" then Config.subpage = string.format( "/%s$", suffix ) end if type( Config.suffix ) ~= "string" then Config.suffix = string.format( "%%s/%s", suffix ) end end end -- favorize() local function feasible( all, at, about ) -- Deal with suggestedvalues within parameter -- Parameter: -- all -- parameter details -- .default -- .type -- at -- string, with parameter name -- about -- suggestedvalues -- table, -- value and possibly description -- table may have elements: -- .code -- mandatory -- .label -- table|string -- .support -- table|string -- .icon -- string -- .class -- table|string -- .css -- table -- .style -- string -- .less -- true: suppress code -- Returns -- 1: mw.html object <ul> -- 2: sequence table with values, or nil local h = { } local e, r1, r2, s, v if #about > 0 then for i = 1, #about do e = about[ i ] s = type( e ) if s == "table" then if type( e.code ) == "string" then s = mw.text.trim( e.code ) if s == "" then e = nil else e.code = s end else e = nil s = string.format( "params.%s.%s[%d] %s", at, "suggestedvalues", i, "MISSING 'code:'" ) end elseif s == "string" then s = mw.text.trim( e ) if s == "" then e = nil s = string.format( "params.%s.%s[%d] EMPTY", at, "suggestedvalues", i ) Fault( s ) else e = { code = s } end elseif s == "number" then e = { code = tostring( e ) } else s = string.format( "params.%s.%s[%d] INVALID", at, "suggestedvalues", i ) Fault( s ) e = false end if e then v = v or { } table.insert( v, e ) if h[ e.code ] then s = string.format( "params.%s.%s REPEATED %s", at, "suggestedvalues", e.code ) Fault( s ) else h[ e.code ] = true end end end -- for i else Fault( string.format( "params.%s.suggestedvalues %s", at, "NOT AN ARRAY" ) ) end if v then local code, d, k, less, story, swift, t, u r1 = mw.html.create( "ul" ) r2 = { } for i = 1, #v do u = mw.html.create( "li" ) e = v[ i ] table.insert( r2, e.code ) story = false less = ( e.less == true ) if not less then swift = e.code if e.support then local scream, support s = type( e.support ) if s == "string" then support = e.support elseif s == "table" then support = faraway( e.support ) else scream = "INVALID" end if support then s = mw.text.trim( support ) if s == "" then scream = "EMPTY" elseif s:find( "[%[%]|%<%>]" ) then scream = "BAD PAGE" else support = s end end if scream then s = string.format( "params.%s.%s[%d].support %s", at, "suggestedvalues", i, scream ) Fault( s ) else swift = string.format( "[[:%s|%s]]", support, swift ) end end if all.type:sub( 1, 5 ) == "wiki-" and swift == e.code then local rooms = { file = 6, temp = 10, user = 2 } local ns = rooms[ all.type:sub( 6, 9 ) ] or 0 t = mw.title.makeTitle( ns, swift ) if t and t.exists then swift = string.format( "[[:%s|%s]]", t.prefixedText, swift ) end end if e.code == all.default then k = 800 else k = 300 end code = mw.html.create( "code" ) :css( "font-weight", tostring( k ) ) :css( "white-space", "nowrap" ) :wikitext( swift ) u:node( code ) end if e.class then s = type( e.class ) if s == "string" then u:addClass( e.class ) elseif s == "table" then for k, s in pairs( e.class ) do u:addClass( s ) end -- for k, s else s = string.format( "params.%s.%s[%d].class INVALID", at, "suggestedvalues", i ) Fault( s ) end end if e.css then if type( e.css ) == "table" then u:css( e.css ) else s = string.format( "params.%s.%s[%d].css INVALID", at, "suggestedvalues", i ) Fault( s ) end end if e.style then if type( e.style ) == "string" then u:cssText( e.style ) else s = string.format( "params.%s.%s[%d].style INVALID", at, "suggestedvalues", i ) Fault( s ) end end if all.type == "wiki-file-name" and not e.icon then e.icon = e.code end if e.label then s = type( e.label ) if s == "string" then s = mw.text.trim( e.label ) if s == "" then s = string.format( "params.%s.%s[%d].label %s", at, "suggestedvalues", i, "EMPTY" ) Fault( s ) else story = s end elseif s == "table" then story = faraway( e.label ) else s = string.format( "params.%s.%s[%d].label INVALID", at, "suggestedvalues", i ) Fault( s ) end end s = false if type( e.icon ) == "string" then t = mw.title.makeTitle( 6, e.icon ) if t and t.file.exists then local g = mw.html.create( "span" ) s = string.format( "[[%s|16px]]", t.prefixedText ) g:attr( "role", "presentation" ) :wikitext( s ) s = tostring( g ) end end if not s and not less and e.label then s = mw.ustring.char( 0x2013 ) end if s then d = mw.html.create( "span" ) :wikitext( s ) if TemplateData.ltr then if not less then d:css( "margin-left", "0.5em" ) end if story then d:css( "margin-right", "0.5em" ) end else if not less then d:css( "margin-right", "0.5em" ) end if story then d:css( "margin-left", "0.5em" ) end end u:node( d ) end if story then u:wikitext( story ) end r1:newline() :node( u ) end -- for i end if not r1 and v ~= false then Fault( string.format( "params.%s.suggestedvalues INVALID", at ) ) r1 = mw.html.create( "code" ) :addClass( "error" ) :wikitext( "INVALID" ) end return r1, r2 end -- feasible() local function feat() -- Check and store parameter sequence if Data.source then local i = 0 local s for k, v in pairs( Data.tree.params ) do if i == 0 then Data.order = { } i = 1 s = k else i = 2 break -- for k, v end end -- for k, v if i > 1 then local pointers = { } local points = { } local given = { } for k, v in pairs( Data.tree.params ) do i = facet( k, 1 ) if type( v ) == "table" then if type( v.label ) == "string" then s = mw.text.trim( v.label ) if s == "" then s = k end else s = k end if given[ s ] then if given[ s ] == 1 then local scream = "Parameter label '%s' detected multiple times" Fault( string.format( scream, s ) ) given[ s ] = 2 end else given[ s ] = 1 end end if i then table.insert( points, i ) pointers[ i ] = k i = facet( k, i ) if i then s = "Parameter '%s' detected twice" Fault( string.format( s, k ) ) end else s = "Parameter '%s' not detected" Fault( string.format( s, k ) ) end end -- for k, v table.sort( points ) for i = 1, #points do table.insert( Data.order, pointers[ points[ i ] ] ) end -- i = 1, #points elseif s then table.insert( Data.order, s ) end end end -- feat() local function feature( access ) -- Create table row for parameter, check and display violations -- Parameter: -- access -- string, with name -- Returns <tr> local mode, s, status local fine = function ( a ) s = mw.text.trim( a ) return a == s and a ~= "" and not a:find( "%|=\n" ) and not a:find( "%s%s" ) end local begin = mw.html.create( "td" ) local code = mw.html.create( "code" ) local desc = mw.html.create( "td" ) local eager = mw.html.create( "td" ) local legal = true local param = Data.tree.params[ access ] local ranking = { "required", "suggested", "optional", "deprecated" } local r = mw.html.create( "tr" ) local styles = "mw-templatedata-doc-param-" local sort, typed for k, v in pairs( param ) do if v == "" then param[ k ] = false end end -- for k, v -- label sort = param.label or access if sort:match( "^%d+$" ) then begin:attr( "data-sort-value", string.format( "%05d", tonumber( sort ) ) ) end begin:css( "font-weight", "bold" ) :wikitext( sort ) -- name and aliases code:css( "font-size", "92%" ) :css( "white-space", "nowrap" ) :wikitext( access ) if not fine( access ) then code:addClass( "error" ) Fault( string.format( "Bad ID params.<code>%s</code>", access ) ) legal = false begin:attr( "data-sort-value", " " .. sort ) end code = mw.html.create( "td" ) :addClass( styles .. "name" ) :node( code ) if access:match( "^%d+$" ) then code:attr( "data-sort-value", string.format( "%05d", tonumber( access ) ) ) end if type( param.aliases ) == "table" then local lapsus, syn for k, v in pairs( param.aliases ) do code:tag( "br" ) if type( v ) == "string" then if not fine( v ) then lapsus = true code:node( mw.html.create( "span" ) :addClass( "error" ) :css( "font-style", "italic" ) :wikitext( "string" ) ) :wikitext( s ) else syn = mw.html.create( "span" ) :addClass( styles .. "alias" ) :css( "white-space", "nowrap" ) :wikitext( s ) code:node( syn ) end else lapsus = true code:node( mw.html.create( "code" ) :addClass( "error" ) :wikitext( type( v ) ) ) end end -- for k, v if lapsus then s = string.format( "params.<code>%s</code>.aliases", access ) Fault( factory( "invalid-value" ):gsub( "$1", s ) ) legal = false end end -- description etc. s = fashioned( param ) if s then desc:node( s ) end if param.style then s = type( param.style ) if s == "table" then desc:css( param.style ) elseif s == "string" then desc:cssText( param.style ) end end if param.suggestedvalues or param.default or param.example or param.autovalue then local details = { "suggestedvalues", "default", "example", "autovalue" } local dl = mw.html.create( "dl" ) local dd, section, show for i = 1, #details do s = details[ i ] show = param[ s ] if show then dd = mw.html.create( "dd" ) section = factory( "doc-param-" .. s ) if param.type == "boolean" and ( show == "0" or show == "1" ) then local boole = Permit.boole[ ( show == "1" ) ] if boole.lead == true then dd:node( mw.html.create( "code" ) :wikitext( show ) ) :wikitext( " " ) end if type( boole.show ) == "string" then local v = mw.html.create( "span" ) :attr( "aria-hidden", "true" ) :wikitext( boole.show ) if boole.css then v:css( boole.css ) end dd:node( v ) end if type( boole.suffix ) == "string" then dd:wikitext( boole.suffix ) end if boole.lead == false then dd:wikitext( " " ) :node( mw.html.create( "code" ) :wikitext( show ) ) end elseif s == "suggestedvalues" then local v, css, class, ts = facilities( param ) if v then local ul ul, v = feasible( param, access, v ) if v then dd:newline() :node( ul ) if css then dd:css( css ) if class then dd:addClass( class ) end if ts then dd:newline() dd:node( ts ) end end Data.params[ access ].suggestedvalues = v end end else dd:wikitext( show ) end dl:node( mw.html.create( "dt" ) :wikitext( section ) ) :node( dd ) end end -- i = 1, #details desc:node( dl ) end -- type if type( param.type ) == "string" then param.type = mw.text.trim( param.type ) if param.type == "" then param.type = false end end if param.type then s = Permit.types[ param.type ] typed = mw.html.create( "td" ) :addClass( styles .. "type" ) if s then if s == "string" then Data.params[ access ].type = s typed:wikitext( factory( "doc-param-type-" .. s ) ) :tag( "br" ) typed:node( mw.html.create( "span" ) :addClass( "error" ) :wikitext( param.type ) ) Data.lasting = true else local support = Config[ "support4" .. param.type ] s = factory( "doc-param-type-" .. param.type ) if support then s = string.format( "[[%s|%s]]", support, s ) end typed:wikitext( s ) end else Data.params[ access ].type = "unknown" typed:addClass( "error" ) :wikitext( "INVALID" ) s = string.format( "params.<code>%s</code>.type", access ) Fault( factory( "invalid-value" ):gsub( "$1", s ) ) legal = false end else typed = mw.html.create( "td" ) :wikitext( factory( "doc-param-type-unknown" ) ) Data.params[ access ].type = "unknown" if param.default then Data.params[ access ].default = nil Fault( "Default value requires <code>type</code>" ) legal = false end end typed:addClass( "navigation-not-searchable" ) -- status if param.required then mode = 1 if param.autovalue then Fault( string.format( "autovalued <code>%s</code> required", access ) ) legal = false end if param.default then Fault( string.format( "Defaulted <code>%s</code> required", access ) ) legal = false end if param.deprecated then Fault( string.format( "Required deprecated <code>%s</code>", access ) ) legal = false end elseif param.deprecated then mode = 4 elseif param.suggested then mode = 2 else mode = 3 end status = ranking[ mode ] ranking = factory( "doc-param-status-" .. status ) if mode == 1 or mode == 4 then ranking = mw.html.create( "span" ) :css( "font-weight", "bold" ) :wikitext( ranking ) if type( param.deprecated ) == "string" then ranking:tag( "br" ) ranking:wikitext( param.deprecated ) end if param.suggested and mode == 4 then s = string.format( "Suggesting deprecated <code>%s</code>", access ) Fault( s ) legal = false end end eager:attr( "data-sort-value", tostring( mode ) ) :node( ranking ) :addClass( string.format( "%sstatus-%s %s", styles, status, "navigation-not-searchable" ) ) -- <tr> r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) :css( Permit.css[ status ] ) :addClass( styles .. status ) :node( begin ) :node( code ) :node( desc ) :node( typed ) :node( eager ) :newline() if not legal then r:css( "border", "#FF0000 3px solid" ) end return r end -- feature() local function features() -- Create <table> for parameters -- Returns <table>, or nil local r if Data.tree and Data.tree.params then local tbl = mw.html.create( "table" ) local tr = mw.html.create( "tr" ) feat() if Data.order and #Data.order > 1 then tbl:addClass( "sortable" ) end if type( Config.classTable ) == "table" then for k, v in pairs( Config.classTable ) do tbl:addClass( v ) end -- for k, v end if type( Config.cssTable ) == "table" then tbl:css( Config.cssTable ) end tr:addClass( "navigation-not-searchable" ) :node( mw.html.create( "th" ) :attr( "colspan", "2" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-name" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-desc" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-type" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-status" ) ) ) tbl:newline() -- :node( mw.html.create( "thead" ) :node( tr ) -- ) :newline() if Data.order then local leave, s for i = 1, #Data.order do s = Data.order[ i ] if s:sub( 1, 1 ) == "=" then leave = true tbl:node( fatten( s ) ) Data.order[ i ] = false elseif s:match( "[=|]" ) then Fault( string.format( "Bad param <code>%s</code>", s ) ) else tbl:node( feature( s ) ) end end -- for i = 1, #Data.order if leave then for i = #Data.order, 1, -1 do if not Data.order[ i ] then table.remove( Data.order, i ) end end -- for i = #Data.order, 1, -1 end Data.tag.paramOrder = Data.order end if Config.cssTabWrap or Data.scroll then r = mw.html.create( "div" ) if type( Config.cssTabWrap ) == "table" then r:css( Config.cssTabWrap ) elseif type( Config.cssTabWrap ) == "string" then -- deprecated r:cssText( Config.cssTabWrap ) end if Data.scroll then r:css( "height", Data.scroll ) :css( "overflow", "auto" ) end r:node( tbl ) else r = tbl end end return r end -- features() local function fellow( any, assigned, at ) -- Check sets[] parameter and issue error message, if necessary -- Parameter: -- any -- should be number -- assigned -- parameter name -- at -- number, of set local s if type( any ) ~= "number" then s = "<code>sets[%d].params[%s]</code>??" Fault( string.format( s, at, mw.text.nowiki( tostring( any ) ) ) ) elseif type( assigned ) == "string" then if not Data.got.params[ assigned ] then s = "<code>sets[%d].params %s</code> is undefined" Fault( string.format( s, at, assigned ) ) end else s = "<code>sets[%d].params[%d] = %s</code>??" Fault( string.format( s, k, type( assigned ) ) ) end end -- fellow() local function fellows() -- Check sets[] and issue error message, if necessary local s if type( Data.got.sets ) == "table" then if type( Data.got.params ) == "table" then for k, v in pairs( Data.got.sets ) do if type( k ) == "number" then if type( v ) == "table" then for ek, ev in pairs( v ) do if ek == "label" then s = type( ev ) if s ~= "string" and s ~= "table" then s = "<code>sets[%d].label</code>??" Fault( string.format( s, k ) ) end elseif ek == "params" and type( ev ) == "table" then for pk, pv in pairs( ev ) do fellow( pk, pv, k ) end -- for pk, pv else ek = mw.text.nowiki( tostring( ek ) ) s = "<code>sets[%d][%s]</code>??" Fault( string.format( s, k, ek ) ) end end -- for ek, ev else k = mw.text.nowiki( tostring( k ) ) v = mw.text.nowiki( tostring( v ) ) s = string.format( "<code>sets[%s][%s]</code>??", k, v ) Fault( s ) end else k = mw.text.nowiki( tostring( k ) ) s = string.format( "<code>sets[%s]</code> ?????", k ) Fault( s ) end end -- for k, v else s = "<code>params</code> required for <code>sets</code>" Fault( s ) end else s = "<code>sets</code> needs to be of <code>object</code> type" Fault( s ) end end -- fellows() local function finalize( advance ) -- Wrap presentation into frame -- Parameter: -- advance -- true, for nice -- Returns string local r, lapsus if Data.div then r = tostring( Data.div ) elseif Data.strip then r = Data.strip else lapsus = true r = "" end r = r .. failures() if Data.source then local live = ( advance or lapsus ) if not live then live = TemplateData.frame:preprocess( "{{REVISIONID}}" ) live = ( live == "" ) end if live then r = r .. fancy( advance, lapsus ) end end return r end -- finalize() local function find() -- Find JSON data within page source (title) -- Returns string, or nil local s = Data.title:getContent() local i, j = s:find( "<templatedata>", 1, true ) local r if i then local k = s:find( "</templatedata>", j, true ) if k then r = mw.text.trim( s:sub( j + 1, k - 1 ) ) end end return r end -- find() local function flat( adjust ) -- Remove formatting from text string for VE -- Parameter: -- arglist -- string, to be stripped, or nil -- Returns string, or nil local r if adjust then r = adjust:gsub( "\n", " " ) if r:find( "<noexport>", 1, true ) then r = r:gsub( "<noexport>.*</noexport>", "" ) end if r:find( "<exportonly>", 1, true ) then r = r:gsub( "</?exportonly>", "" ) end if r:find( "''", 1, true ) then r = r:gsub( "'''", "" ):gsub( "''", "" ) end if r:find( "<", 1, true ) then local Text = Fetch( "Text" ) r = Text.getPlain( r:gsub( "<br */?>", "\r\n" ) ) end if r:find( "[", 1, true ) then local WLink = Fetch( "WLink" ) if WLink.isBracketedURL( r ) then r = r:gsub( "%[([hf]tt?ps?://%S+) [^%]]+%]", "%1" ) end r = WLink.getPlain( r ) end if r:find( "&", 1, true ) then r = mw.text.decode( r ) if r:find( "­", 1, true ) then r = r:gsub( "­", "" ) end end end return r end -- flat() local function flush() -- JSON encode narrowed input; obey unnamed (numerical) parameters -- Returns <templatedata> JSON string local r if Data.tag then r = mw.text.jsonEncode( Data.tag ):gsub( "%}$", "," ) else r = "{" end r = r .. "\n\"params\":{" if Data.order then local sep = "" local s for i = 1, #Data.order do s = Data.order[ i ] r = string.format( "%s%s\n%s:%s", r, sep, mw.text.jsonEncode( s ), mw.text.jsonEncode( Data.params[ s ] ) ) sep = ",\n" end -- for i = 1, #Data.order end r = r .. "\n}\n}" return r end -- flush() local function focus( access ) -- Check components; focus multilingual description, build trees -- Parameter: -- access -- string, name of parameter, nil for root local f = function ( a, at ) local r if at then r = string.format( "<code>params.%s</code>", at ) else r = "''root''" end if a then r = string.format( "%s<code>.%s</code>", r, a ) end return r end local parent if access then parent = Data.got.params[ access ] else parent = Data.got end if type( parent ) == "table" then local elem, got, permit, s, scope, slot, tag, target if access then permit = Permit.params if type( access ) == "number" then slot = tostring( access ) else slot = access end else permit = Permit.root end for k, v in pairs( parent ) do scope = permit[ k ] if scope then s = type( v ) if s == "string" and k ~= "format" then v = mw.text.trim( v ) end if scope:find( s, 1, true ) then if scope:find( "I18N", 1, true ) then if s == "string" then elem = fair( v ) elseif s == "table" then local translated v, translated = faraway( v ) if v then if translated and k == "description" then elem = { [ 1 ] = fair( v ), [ 2 ] = translated } else elem = fair( v ) end else elem = false end end if type( v ) == "string" then if k == "deprecated" then if v == "1" then v = true elseif v == "0" then v = false end elem = v elseif scope:find( "nowiki", 1, true ) then elem = mw.text.nowiki( v ) elem = elem:gsub( " \n", "<br>" ) v = v:gsub( string.char( 13 ), "" ) else v = flat( v ) end elseif s == "boolean" then if scope:find( "boolean", 1, true ) then elem = v else s = "Type <code>boolean</code> bad for " .. f( k, slot ) Fault( s ) end end else if k == "params" and not access then v = nil elem = nil elseif k == "format" and not access then elem = mw.text.decode( v ) v = nil elseif k == "inherits" then elem = v if not Data.heirs then Data.heirs = { } end Data.heirs[ slot ] = v v = nil elseif k == "style" then elem = v v = nil elseif s == "string" then v = mw.text.nowiki( v ) elem = v else elem = v end end if type( elem ) ~= "nil" then if not target then if access then if not Data.tree.params then Data.tree.params = { } end Data.tree.params[ slot ] = { } target = Data.tree.params[ slot ] else Data.tree = { } target = Data.tree end end target[ k ] = elem elem = false end if type( v ) ~= "nil" then if not tag then if access then if type( v ) == "string" and v.sub( 1, 1 ) == "=" then v = nil else if not Data.params then Data.params = { } end Data.params[ slot ] = { } tag = Data.params[ slot ] end else Data.tag = { } tag = Data.tag end end if type( v ) ~= "nil" and k ~= "suggestedvalues" then tag[ k ] = v end end else s = string.format( "Type <code>%s</code> bad for %s", scope, f( k, slot ) ) Fault( s ) end else Fault( "Unknown component " .. f( k, slot ) ) end end -- for k, v if not access and Data.got.sets then fellows() end else Fault( f() .. " needs to be of <code>object</code> type" ) end end -- focus() local function format() -- Build formatted element -- Returns <inline> local source = Data.tree.format:lower() local r, s if source == "inline" or source == "block" then r = mw.html.create( "i" ) :wikitext( source ) else local code if source:find( "|", 1, true ) then local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$" if source:match( scan ) then code = source:gsub( "\n", "N" ) else s = mw.text.nowiki( source ):gsub( "\n", "\n" ) s = tostring( mw.html.create( "code" ) :wikitext( s ) ) Fault( "Invalid format " .. s ) source = false end else local words = mw.text.split( source, "%s+" ) local show, start, support, unknown for i = 1, #words do s = words[ i ] if i == 1 then start = s end support = Permit.builder[ s ] if support == start or support == "*" then Permit.builder[ s ] = true elseif s:match( "^[1-9]%d?" ) and Permit.builder.align then Permit.builder.align = tonumber( s ) else if unknown then unknown = string.format( "%s %s", unknown, s ) else unknown = s end end end -- i = 1, #words if unknown then s = tostring( mw.html.create( "code" ) :css( "white-space", "nowrap" ) :wikitext( s ) ) Fault( "Unknown/misplaced format keyword " .. s ) source = false start = false end if start == "inline" then if Permit.builder.half == true then show = "inline half" code = "{{_ |_=_}}" elseif Permit.builder.grouped == true then show = "inline grouped" code = "{{_ | _=_}}" elseif Permit.builder.spaced == true then show = "inline spaced" code = "{{_ | _ = _ }}" end if Permit.builder.newlines == true then show = show or "inline" code = code or "{{_|_=_}}" show = show .. " newlines" code = string.format( "N%sN", code ) end elseif start == "block" then local space = "" -- amid "|" and name local spaced = " " -- preceding "=" local spacer = " " -- following "=" local suffix = "N" -- closing "}}" on new line show = "block" if Permit.builder.indent == true then start = " " show = "block indent" else start = "" end if Permit.builder.compressed == true then spaced = "" spacer = "" show = show .. " compressed" if Permit.builder.last == true then show = show .. " last" else suffix = "" end else if Permit.builder.lead == true then show = show .. " lead" space = " " end if type( Permit.builder.align ) ~= "string" then local n s = " align" if Permit.builder.align == true then n = 0 if type( Data.got ) == "table" and type( Data.got.params ) == "table" then for k, v in pairs( Data.got.params ) do if type( v ) == "table" and not v.deprecated and type( k ) == "string" then k = mw.ustring.len( k ) if k > n then n = k end end end -- for k, v end else n = Permit.builder.align if type( n ) == "number" and n > 1 then s = string.format( "%s %d", s, n ) else n = 0 -- How comes? end end if n > 1 then spaced = string.rep( "_", n - 1 ) .. " " end show = show .. s elseif Permit.builder.after == true then spaced = "" show = show .. " after" elseif Permit.builder.dense == true then spaced = "" spacer = "" show = show .. " dense" end if Permit.builder.last == true then suffix = spacer show = show .. " last" end end code = string.format( "N{{_N%s|%s_%s=%s_%s}}N", start, space, spaced, spacer, suffix ) if show == "block" then show = "block newlines" end end if show then r = mw.html.create( "span" ) :wikitext( show ) end end if code then source = code:gsub( "N", "\n" ) code = mw.text.nowiki( code ):gsub( "N", "\n" ) code = mw.html.create( "code" ) :css( "margin-left", "1em" ) :css( "margin-right", "1em" ) :wikitext( code ) if r then r = mw.html.create( "span" ) :node( r ) :node( code ) else r = code end end end if source and Data.tag then Data.tag.format = source end return r end -- format() local function formatter() -- Build presented documentation -- Returns <div> local r = mw.html.create( "div" ) local x = fashioned( Data.tree, true, r ) local s if x then r = x end if Data.leading then local toc = mw.html.create( "div" ) local shift if Config.suppressTOCnum then toc:addClass( Config.suppressTOCnum ) if type( Config.stylesTOCnum ) == "string" then local src = Config.stylesTOCnum .. "/styles.css" s = TemplateData.frame:extensionTag( "templatestyles", nil, { src = src } ) r:newline() :node( s ) end end toc:addClass( "navigation-not-searchable" ) :css( "margin-top", "0.5em" ) :wikitext( "__TOC__" ) if Data.sibling then local block = mw.html.create( "div" ) if TemplateData.ltr then shift = "right" else shift = "left" end block:css( "float", shift ) :wikitext( Data.sibling ) r:newline() :node( block ) :newline() end r:newline() :node( toc ) :newline() if shift then r:node( mw.html.create( "div" ) :css( "clear", shift ) ) :newline() end end s = features() if s then if Data.leading then r:node( mw.html.create( "h" .. Config.nested ) :wikitext( factory( "doc-params" ) ) ) :newline() end r:node( s ) end if Data.shared then local global = mw.html.create( "div" ) :attr( "id", "templatedata-global" ) local shift if TemplateData.ltr then shift = "right" else shift = "left" end global:css( "float", shift ) :wikitext( string.format( "[[%s|%s]]", Data.shared, "Global" ) ) r:newline() :node( global ) end if Data.tree and Data.tree.format then local e = format() if e then local show = "Format" if Config.supportFormat then show = string.format( "[[%s|%s]]", Config.supportFormat, show ) end r:node( mw.html.create( "p" ) :addClass( "navigation-not-searchable" ) :wikitext( show .. ": " ) :node( e ) ) end end return r end -- formatter() local function free() -- Remove JSON comment lines if Data.source:find( "//", 1, true ) then Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])", "%1%3" ) end end -- free() local function full() -- Build survey table from JSON data, append invisible <templatedata> Data.div = mw.html.create( "div" ) :addClass( "mw-templatedata-doc-wrap" ) if Permit.css.bg then Data.div:css( Permit.css.bg ) end if Permit.css.fg then Data.div:css( Permit.css.fg ) end focus() if Data.tag then if type( Data.got.params ) == "table" then for k, v in pairs( Data.got.params ) do focus( k ) end -- for k, v if Data.heirs then fathers() end end end Data.div:node( formatter() ) if not Data.lazy then Data.slim = flush() if TemplateData.frame then local div = mw.html.create( "div" ) local tdata = { [ 1 ] = "templatedata", [ 2 ] = Data.slim } Data.strip = TemplateData.frame:callParserFunction( "#tag", tdata ) div:wikitext( Data.strip ) if Config.loudly then Data.div:node( mw.html.create( "hr" ) :css( { height = "7ex" } ) ) else div:css( "display", "none" ) end Data.div:node( div ) end end if Data.lasting then Fault( "deprecated type syntax" ) end if Data.less then Fault( Config.solo ) end end -- full() local function furnish( adapt, arglist ) -- Analyze transclusion -- Parameter: -- adapt -- table, #invoke parameters -- arglist -- table, template parameters -- Returns string local source favorize() -- deprecated: for k, v in pairs( Config.basicCnf ) do if adapt[ k ] and adapt[ k ] ~= "" then Config[ v ] = adapt[ k ] end end -- for k, v if arglist.heading and arglist.heading:match( "^[3-6]$" ) then Config.nested = arglist.heading else Config.nested = "2" end Config.loudly = faculty( arglist.debug or adapt.debug ) Data.lazy = faculty( arglist.lazy ) and not Config.loudly Data.leading = faculty( arglist.TOC ) if Data.leading and arglist.TOCsibling then Data.sibling = mw.text.trim( arglist.TOCsibling ) end if arglist.lang then Data.slang = arglist.lang:lower() elseif adapt.lang then Data.slang = adapt.lang:lower() end if arglist.JSON then source = arglist.JSON elseif arglist.Global then source = TemplateData.getGlobalJSON( arglist.Global, arglist.Local ) elseif arglist[ 1 ] then local s = mw.text.trim( arglist[ 1 ] ) local start = s:sub( 1, 1 ) if start == "<" then Data.strip = s elseif start == "{" then source = s elseif mw.ustring.sub( s, 1, 8 ) == mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then Data.strip = s end end if type( arglist.vertical ) == "string" and arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then Data.scroll = arglist.vertical end if not source then Data.title = mw.title.getCurrentTitle() source = find() if not source and not Data.title.text:match( Config.subpage ) then local s = string.format( Config.suffix, Data.title.prefixedText ) Data.title = mw.title.new( s ) if Data.title.exists then source = find() end end end if not Data.lazy then if not Data.title then Data.title = mw.title.getCurrentTitle() end Data.lazy = Data.title.text:match( Config.subpage ) end if type( source ) == "string" then TemplateData.getPlainJSON( source ) end return finalize( faculty( arglist.source ) ) end -- furnish() 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() TemplateData.getGlobalJSON = function ( access, adapt ) -- Retrieve TemplateData from a global repository (JSON) -- Parameter: -- access -- string, with page specifier (on WikiMedia Commons) -- adapt -- JSON string or table with local overrides -- Returns true, if succeeded local plugin = Fetch( "/global" ) local r if type( plugin ) == "table" and type( plugin.fetch ) == "function" then local s, got = plugin.fetch( access, adapt ) if got then Data.got = got Data.order = got.paramOrder Data.shared = s r = true full() else Fault( s ) end end return r end -- TemplateData.getGlobalJSON() TemplateData.getPlainJSON = function ( adapt ) -- Reduce enhanced JSON data to plain text localized JSON -- Parameter: -- adapt -- string, with enhanced JSON -- Returns string, or not if type( adapt ) == "string" then local JSONutil = Fetch( "JSONutil", true ) Data.source = adapt free() if JSONutil then local Multilingual = Fetch( "Multilingual", true ) local f if Multilingual then f = Multilingual.i18n end Data.got = JSONutil.fetch( Data.source, true, f ) else local lucky lucky, Data.got = pcall( mw.text.jsonDecode, Data.source ) end if type( Data.got ) == "table" then full() elseif not Data.strip then local scream = type( Data.got ) if scream == "string" then scream = Data.got else scream = "Data.got: " .. scream end Fault( "fatal JSON error: " .. scream ) end end return Data.slim end -- TemplateData.getPlainJSON() TemplateData.test = function ( adapt, arglist ) TemplateData.frame = mw.getCurrentFrame() return furnish( adapt, arglist ) end -- TemplateData.test() -- Export local p = { } p.f = function ( frame ) -- Template call local lucky, r TemplateData.frame = frame lucky, r = pcall( furnish, frame.args, frame:getParent().args ) if not lucky then Fault( "INTERNAL: " .. r ) r = failures() end return r end -- p.f 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 p.TemplateData = function () -- Module interface return TemplateData end return p