local p = {}
local h = {}
local i18n = require("Module:I18n")
local s = i18n.getString
local utilsArg = require("Module:UtilsArg")
local utilsLayout = require("Module:UtilsLayout")
local utilsMarkup = require("Module:UtilsMarkup")
local utilsTable = require("Module:UtilsTable")
local FORMATS = {
inline = {
templateData = "{{_|_= _}}",
argSeparator = "|",
afterLastArg = "",
},
block = {
templateData = "{{_\n|_= _\n}}",
argSeparator = "\n%s|",
afterLastArg = "\n",
}
}
local VARARG_KEY = "..."
function p.Template(frame)
local title = mw.title.getCurrentTitle()
local templateName = title.text
local moduleName = frame.args.module or title.baseText
return p.printTemplateDoc(templateName, moduleName)
end
function p.Examples(frame)
local args = frame:getParent().args
local examples = h.examplesFromFrame(args)
return p.printExamples(examples)
end
function p.printTemplateDoc(templateName, moduleName)
moduleName = moduleName or templateName
local templateSpec = require("Module:" .. moduleName).Templates[templateName]
utilsArg.schemaValidate(p.Schemas.TemplateSpec, "TemplateSpec", templateSpec, templateName)
local result = "__TOC__\n"
if templateSpec.purpose then
result = result .. utilsMarkup.heading(2, s("heading.purpose"))
result = result .. mw.getCurrentFrame():preprocess(templateSpec.purpose) .. "\n"
end
result = result .. utilsMarkup.heading(2, s("heading.usage"))
if templateSpec.usesData then
local dataSource = utilsMarkup.link(templateSpec.usesData)
result = result .. utilsMarkup.italic(s("usesData", {dataSource = dataSource})) .. "\n"
end
if templateSpec.storesData then
local dataSource = utilsMarkup.link(templateSpec.storesData)
result = result .. utilsMarkup.italic(s("storesData", {dataSource = dataSource})) .. "\n"
end
result = result .. p.printUsage(templateName, templateSpec)
if templateSpec.examples then
local examples = h.examplesFromSpec(templateName, templateSpec)
result = result .. utilsMarkup.heading(3, s("heading.examples"))
result = result .. p.printExamples(examples)
end
result = result .. h.templateData(templateSpec)
result = result .. h.categories(templateSpec)
return result
end
function p.printUsage(templateName, templateSpec)
local params = {}
local paramOrder = templateSpec.paramOrder
local format = templateSpec.format
local positionalParamCount = 0
local hasVarArg = false
for k, v in pairs(templateSpec.params) do
if type(k) == "number" then
positionalParamCount = positionalParamCount + 1
end
if k == VARARG_KEY then
hasVarArg = true
end
local i = paramOrder and utilsTable.keyOf(paramOrder, k) or (#params + 1)
params[i] = utilsTable.merge({}, v, {
param = k
})
end
local format = FORMATS[templateSpec.format]
local result = ""
if positionalParamCount > 0 or hasVarArg then
result = result .. utilsLayout.tabs({
{
label = s("tabs.syntax"),
content = h.syntax(templateName, params, format, templateSpec.indent)
},
{
label = s("tabs.boilerplate"),
content = h.boilerplate(templateName, params, format, templateSpec.indent, templateSpec.boilerplate)
},
})
else
result = h.boilerplate(templateName, params, format, templateSpec.indent, templateSpec.boilerplate)
end
result = result .. h.params(params, positionalParamCount)
return result
end
function p.printExamples(examples)
for i, example in ipairs(examples) do
local input = mw.text.trim(example.input)
if string.find(input, "\n") or examples.vertical then
input = utilsMarkup.pre(input, {
wrapLines = false
})
else
input = utilsMarkup.code(mw.text.nowiki(input))
end
if example.output then
local output, categories = utilsMarkup.stripCategories(example.output)
local output = utilsMarkup.killBacklinks(output)
local categoryList = utilsMarkup.bulletList(categories)
examples[i] = {
input = input,
output = output,
categoryList = categoryList,
desc = example.desc
}
else
examples[i] = {
input = input,
desc = example.desc
}
end
end
if not examples.vertical then
local rows = {}
for _, example in ipairs(examples) do
if example.desc then
table.insert(rows, {
{
header = true,
colspan = -1,
styles = {
["text-align"] = "left"
},
content = example.desc,
}
})
end
table.insert(rows, {example.input, example.output, example.categoryList})
end
return utilsLayout.table({
hideEmptyColumns = true,
headers = { s("header.input"), s("header.output"), s("header.categoriesAdded"), header = true },
rows = rows
})
end
local result = ""
for _, example in ipairs(examples) do
local headerStyles = {
["width"] = "5rem" -- for alignment. See Template:Letter/Documentation for an example of why this is needed
}
result = result .. utilsLayout.table({
hideEmptyRows = true,
rows = {
{
{ header = true, content= s("header.input"), styles = headerStyles},
example.input,
},
{
{ header = true, content = s("header.output"), styles = headerStyles},
example.output
},
{
{ header = true, content = s("header.categoriesAdded"), styles = headerStyles },
example.categoryList
},
}
}) .. "\n"
end
return result
end
function h.syntax(templateName, params, format, indent)
local args = {}
for i, param in ipairs(params) do
if param.param == VARARG_KEY then
h.insertVarArgsSyntax(args, i, param.placeholder)
elseif type(param.param) == "number" then
args[i] = {
param = param.param,
value = param.placeholder or string.format("<%s>", param.name or param.param),
inline = param.inline,
}
else
args[i] = {
param = param.param,
inline = param.inline,
value = ""
}
end
end
local result = h.printTemplateInstance(templateName, args, format, indent)
return utilsMarkup.pre(result)
end
function h.insertVarArgsSyntax(args, i, placeholder)
for j = 1, 3 do
table.insert(args, i+j-1, {
param = j,
value = placeholder .. j
})
end
table.insert(args, i+3, {
param = i+3,
value = "..."
})
table.insert(args, i+4, {
param = i+4,
value = placeholder .. "N"
})
end
function h.boilerplate(templateName, params, format, indent, options)
local options = options or {}
local args = {}
local i = 1
for _, param in ipairs(params) do
if param.canOmit then
-- do nothing
elseif param.param == VARARG_KEY then
h.insertVarArgBoilerplate(args, i)
else
i = i + 1
table.insert(args, {
param = param.param,
value = "",
inline = param.inline,
})
end
end
local result = h.printTemplateInstance(templateName, args, format, indent)
if options.list then
local listArgs = {
{
param = 1,
value = result,
},
{
param = 2,
value = result,
},
{
param = 3,
value = result,
},
}
result = h.printTemplateInstance("List", listArgs, FORMATS.block, 1)
end
if options.commentBefore then
result = "<!--\n-->" .. result
end
return utilsMarkup.pre(result)
end
function h.insertVarArgBoilerplate(args, i)
for j = 1, 3 do
table.insert(args, i+j-1, {
param = j,
value = ""
})
end
end
function h.examplesFromFrame(examples)
local result = {
vertical = examples.vertical
}
for i, example in ipairs(examples) do
local input = mw.text.unstripNoWiki(example)
local output = mw.getCurrentFrame():preprocess(mw.text.decode(input))
result[i] = {
input = input,
output = output
}
end
return result
end
function h.examplesFromSpec(templateName, templateSpec)
local paramOrder = templateSpec.paramOrder
local examples = templateSpec.examples
local result = {
vertical = examples.vertical
}
for i, example in ipairs(examples) do
local args = {}
local unsortedArgs = {}
for k, v in pairs(example.args or example) do
local idx = paramOrder and utilsTable.keyOf(paramOrder, k)
if idx then
args[idx] = {
param = k,
value = v
}
else
table.insert(unsortedArgs, {
param = k,
value = v
})
end
end
args = utilsTable.concat(args, unsortedArgs)
result[i] = {}
result[i].input = h.printTemplateInstance(templateName, args, FORMATS[templateSpec.format])
if not templateSpec.storesData then
result[i].output = mw.getCurrentFrame():expandTemplate({
title = templateName,
args = example.args or example
})
end
result[i].desc = example.desc
end
return result
end
function h.printTemplateInstance(name, args, format, indent)
local indent = string.rep(" ", indent or 0)
local result = "{{" .. name
for _, arg in ipairs(args) do
local printedArg
if type(arg.param) == "number" then
printedArg = arg.value
else
printedArg = string.format("%s= %s", arg.param, arg.value)
end
if arg.inline then
result = result .. FORMATS.inline.argSeparator .. printedArg
else
result = result .. string.format(format.argSeparator, indent) .. printedArg
end
end
result = result .. format.afterLastArg .. "}}"
return result
end
function h.params(params, positionalParamCount)
local rows = {}
for i, param in ipairs(params) do
rows[i] = h.paramRow(param, positionalParamCount)
end
return utilsLayout.table({
hideEmptyColumns = true,
headers = {s("header.parameter"), s("header.status"), s("header.description"), s("header.enum")},
rows = rows,
})
end
function h.paramRow(param, i)
if param.param == VARARG_KEY then
local varArgs = utilsTable.map({"1", ".", ".", "N"}, utilsMarkup.code)
varArgs = utilsMarkup.list(varArgs)
paramCell = {{varArgs, utilsMarkup.code(param.name)}}
elseif param.name and param.name ~= param.param then
paramCell = {{utilsMarkup.code(param.param), utilsMarkup.code(param.name)}}
else
paramCell = utilsMarkup.code(param.param)
end
local statusCell = param.required and s("status.required") or s("status.optional")
local descriptionCell = param.desc
local enumCell = param.enum and h.printEnum(param) or ""
return {paramCell, statusCell, descriptionCell, enumCell}
end
function h.printEnum(param)
local enum = param.enum
local dependsOn = param.enumDependsOn
local enumReference = type(enum) == "table" and enum.reference or nil
if dependsOn then
return s("enum.depends", { arg = utilsMarkup.code(dependsOn) })
elseif enumReference then
return s("enum.ref", { dataSource = enumReference })
else
local values = utilsTable.map(enum, utilsMarkup.code)
return utilsMarkup.bulletList(values)
end
end
function h.templateData(templateSpec)
local data = {
description = templateSpec.purpose,
params = {},
paramOrder = templateSpec.paramOrder,
}
-- The following workaround is ugly, but necessary to ensure that data.params is encoded as a JSON object rather than an array.
for k, v in pairs(templateSpec.params) do
data.params[k] = {
label = v.name or k,
required = v.required,
description = v.desc,
aliases = type(k) == "number" and {v.name} or nil,
type = v.type,
}
end
if utilsTable.isArray(templateSpec.params) then
data.params["_"] = {
label = "_",
description = "Dummy parameter. Do not use.",
}
end
local templateData = mw.getCurrentFrame():extensionTag("templatedata", mw.text.jsonEncode(data))
local html = mw.html.create("div")
:addClass("mw-collapsible mw-collapsed")
:attr("data-expandtext", s("templateData.show"))
:attr("data-collapsetext", s("templateData.hide"))
:css("float", "left")
:wikitext(templateData)
return tostring(html)
end
function h.categories(templateSpec)
local categories = {s("categories.all")}
if templateSpec.usesData then
table.insert(categories, s("categories.usesData"))
end
if templateSpec.storesData then
table.insert(categories, s("categories.storesData"))
end
return utilsMarkup.categories(categories)
end
i18n.loadStrings({
en = {
categories = {
all = "Category:Templates",
usesData = "Category:Cargo Query Templates",
storesData = "Category:Cargo Storage Templates",
},
usesData = "This template relies on the centralized data stored at ${dataSource}.",
storesData = "This template is used to store centralized data at ${dataSource}.",
enum = {
ref = "See ${dataSource}",
depends = "Depends on ${arg}",
},
header = {
description = "Description",
parameter = "Parameter",
status = "Status",
enum = "Accepted values",
categoriesAdded = "Categories added",
input = "Input",
output = "Output",
},
heading = {
examples = "Examples",
purpose = "Purpose",
usage = "Usage",
},
status = {
optional = "optional",
required = "required",
},
tabs = {
boilerplate = "Boilerplate",
syntax = "Syntax",
},
templateData = {
show = "show TemplateData ▼",
hide = "hide TemplateData ▲",
},
}
})
p.Schemas = {
TemplateSpec = {
type = "record",
properties = {
{
name = "purpose",
type = "string",
required = true,
desc = "Purpose of the template.",
},
{
name = "storesData",
type = "string",
desc = "For <code>/Store</code> templates, use this field to indicate which Data page this template is used on. If present, the template is added to [[:Category:Cargo Storage Templates]].",
},
{
name = "usesData",
type = "string",
desc = "For templates that use Cargo data, use this field to indicate which page the data is stored on. If present, the template is added to [[:Category:Cargo Query Templates]].",
},
{
name = "params",
type = "map",
required = true,
desc = "<p>Map of the template parameters, which can be used . Numerical keys indicate positional parameters. String keys indicate named parameters. A special key named <code>" .. VARARG_KEY .. "</code> indicates a variadic parameter (i.e. a template with a variable number of trailing arguments, such as [[Template:List]]).</p><p>This data can be used by [[Module:UtilsArg]] to parse template arguments.</p>",
keys = {
oneOf = {
{ type = "string" },
{ type = "number" },
},
},
values = { _ref = "#/definitions/param" },
},
{
name = "paramOrder",
type = "array",
items = {
oneOf = {
{ type = "number" },
{ type = "string" },
},
},
desc = "Array of parameter keys indicating what order they should be presented in.",
},
{
name = "format",
type = "string",
required = true,
enum = {"inline", "block"},
desc = "Indicates how the template should be laid out in source. <code>inline</code> for single-line templates, <code>block</code> for multiline templates."
},
{
name = "indent",
type = "number",
default = 0,
desc = "Number of spaces to indent block-format parameters.",
},
{
name = "boilerplate",
type = "record",
desc = "Directives for how generate boilerplate for the template.",
properties = {
{
name = "commentBefore",
type = "boolean",
desc = "If true, a wikitext comment is placed before the template boilerplate. See [[Template:Franchise/Store Game]] for example.",
},
{
name = "list",
type = "boolean",
desc = "If true, boilerplate includes [[Template:List]]. See [[Template:Game Rating]] for example.",
},
},
},
{
name = "examples",
desc = "Array of argument tables representing different invocations of the template + an optional <code>vertical</code> key. It is possible to add descriptions to specific examples as well. See [[Template:List]] for examples.",
allOf = {
{
type = "record",
properties = {
{
name = "vertical",
type = "boolean",
default = "false",
desc = "If false, examples are laid out in a single table (e.g. [[Template:List]]). If true, examples are listed one after the other (e.g. [[Template:Letter]])."
},
},
},
{
type = "array",
items = {
type = "map",
keys = {
oneOf = {
{ type = "number" },
{ type = "string" },
},
},
values = { type = "any" },
},
},
},
},
},
definitions = {
param = {
type = "record",
properties = {
{
name = "name",
type = "string",
desc = "Use this to assign names to positional parameters.",
},
{
name = "placeholder",
type = "string",
desc = "Placeholder to use for argument value when demonstrating template usage. Defaults to <code><name></code> for positional parameters; empty string for named parameters.",
},
{
name = "type",
type = "string",
enum = {
"unknown",
"number",
"string",
"line",
"boolean",
"date",
"url",
"wiki-page-name",
"wiki-file-name",
"wiki-template-name",
"wiki-user-name",
"content",
"unbalanced-wikitext",
reference = "{{Mediawiki|Extension:TemplateData}}",
},
desc = "One of the {{Mediawiki|Extension:TemplateData}} types."
},
{
name = "required",
type = "boolean",
desc = "Indicates a required parameter.",
},
{
name = "deprecated",
type = "boolean",
desc = "Indicates a parameter that should no longer be used.",
},
{
name = "enum",
type = "any",
desc = "An array of allowed values. Optionally, a <code>reference</code> key can be added to the Lua table which links to a page listing all the allowed values (see [[Module:Franchise#enum]] for example).",
},
{
name = "enumDependsOn",
type = "string",
desc = "If the allowed values for this parameter depends on the value of another parameter, specify that parameter name here. Then, instead of <code>enum</code> being an array, it can be a function that returns an array. The value of the dependency argument is passed to the function. See [[Module:Letter]] for an example.",
},
{
name = "desc",
type = "string",
required = true,
desc = "Wikitext description of the parameter.",
},
{
name = "canOmit",
type = "boolean",
desc = "Omit parameter from generated boilerplate. Use for parameters that cover edge cases and should thefore not be used in normal circumstances. See [[Template:Game Rating]] for example.",
},
{
name = "inline",
type = "boolean",
desc = 'If true, then the parameter will be printed on the same line as the previous parameter, even if <code>format</code> is set to <code>"block"</code>. See [[Template:Sequence/Store]] for example.',
},
{
name = "trim",
type = "boolean",
desc = "Indicator to [[Module:UtilsArg#parse|utilsArg.parse]] that this template argument should be trimmed using [[Module:UtilsString#trim|utilsString.trim]].",
},
{
name = "nilIfEmpty",
type = "boolean",
desc = "Indicator to [[Module:UtilsArg#parse|utilsArg.parse]] that this template argument should be made nil if it is an empty string, using [[Module:UtilsString#nilIfEmpty|utilsString.nilIfEmpty]].",
},
{
name = "split",
type = "string",
desc = "Indicator to [[Module:UtilsArg#parse|utilsArg.parse]]. If set to <code>true</code>, the template argument is treated as a list of commma-separated values, to be turned into an array using [[Module:UtilsString#split|utilsString.split]]. If set to a string, that string will be used as the splitting pattern.",
},
},
},
},
},
}
return p
Advertisement
Advertisement