Module:Documentation/Template

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 = "", 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 = "\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 = "" .. 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 = "" 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 = elseif param.name and param.name ~= param.param then paramCell = 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  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 = " Map of the template parameters, which can be used . Numerical keys indicate positional parameters. String keys indicate named parameters. A special key named  indicates a variadic parameter (i.e. a template with a variable number of trailing arguments, such as Template:List).  This data can be used by Module:UtilsArg to parse template arguments. ", 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.  for single-line templates,   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  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  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 = "", },						desc = "One of the 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  key can be added to the Lua table which links to a page listing all the allowed values (see Module:Franchise 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  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  is set to. See Template:Sequence/Store for example.', },					{						name = "trim", type = "boolean", desc = "Indicator to utilsArg.parse that this template argument should be trimmed using utilsString.trim.", },					{						name = "nilIfEmpty", type = "boolean", desc = "Indicator to utilsArg.parse that this template argument should be made nil if it is an empty string, using utilsString.nilIfEmpty.", },					{						name = "split", type = "string", desc = "Indicator to utilsArg.parse. If set to, the template argument is treated as a list of commma-separated values, to be turned into an array using utilsString.split. If set to a string, that string will be used as the splitting pattern.", },				},			},		},	}, }

return p