Module:Format TemplateData: Difference between revisions
Appearance
tweak CSS var |
m 1 revision imported |
||
(No difference)
| |||
Latest revision as of 19:07, 13 March 2026
Documentation for this module may be created at Module:Format TemplateData/doc
local TemplateData = {
item = 46997995,
serial = "2025-02-07",
suite = "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",
helpAliases = "supportAliases",
helpBoolean = "support4boolean",
helpContent = "support4content",
helpDate = "support4date",
helpDefault = "support4default",
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",
tStylesMultiColumns = "stylesMultClm",
tStylesTOCnum = "stylesTOCnum"
},
classTable = { "wikitable" }, -- Classes for params table
cssTable = false, -- Styles for params table
cssTabWrap = false, -- Styles for params table wrapper
debug = false,
debugmultilang = "#c0c0c0",
jsonDebug = "json-code-lint", -- Class for jsonDebug tool
loudly = false, -- Show exported element, etc.
solo = false, -- Complaint on missing description
strange = false, -- Title of maintenance category
subpage = false, -- Pattern to identify subpage
suffix = false, -- Subpage creation scheme
suppressTOCnum = false -- Class for TOC number suppression
}
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 = "var(--background-color-base, #fff)",
deprecated = "#ffcbcb",
fg = "var(--color-base, #000)",
optional = "#eaecf0",
required = "#eaf3ff",
suggested = "#fff",
tableheadbg = "var(--background-color-progressive-subtle, #b3b7ff)"
},
params = {
aliases = "table",
autovalue = "string",
default = "string table I18N nowiki",
deprecated = "boolean string I18N",
description = "string table I18N",
example = "string table I18N nowiki",
inherits = "string",
label = "string table I18N",
required = "boolean",
style = "string table",
suggested = "boolean",
suggestedvalues = "string table number boolean",
type = "string"
},
root = {
description = "string table I18N",
format = "string",
maps = "table",
paramOrder = "table",
params = "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,
["string/line"] = "line",
["string/wiki-page-name"] = "wiki-page-name",
["string/wiki-user-name"] = "wiki-user-name",
["unbalanced-wikitext"] = true,
["wiki-file-name"] = true,
["wiki-page-name"] = true,
["wiki-template-name"] = true,
["wiki-user-name"] = true
}
}
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 argument 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"] = "0.77em",
["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)
:css("color", "inherit")
: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 subheadline.
-- 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 parameters 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"] = "1.25em" }
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", "700"):wikitext(sort)
-- name and aliases
code:css("font-size", "0.92em"):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
if Config.supportAliases then
s = string.format("[[%s|%s]]", Config.supportAliases, mw.text.nowiki(s))
end
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 s == "default" and Config.support4default then
section = string.format("[[%s|%s]]", Config.support4default, mw.text.nowiki(section))
end
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", "700"):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", "3px solid #f00")
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 VisualEditor.
-- 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 = r:gsub("<br */?>", "\r\n"):gsub("<sup>2</sup>", "²"):gsub("<sup>3</sup>", "³")
r = Text.getPlain(r)
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, 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(" " .. string.char(10), "<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 come?
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
-- 2024-03-01
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
elseif link then
r = false
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
setmetatable(p, {
__call = function(func, ...)
setmetatable(p, nil)
return Failsafe
end,
})
return p