Zelda Wiki

Want to contribute to this wiki?
Sign up for an account, and get started!

Come join the Zelda Wiki community Discord server!

READ MORE

Zelda Wiki
Advertisement

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 |",
		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)
			},
			{
				label = s("tabs.boilerplate"),
				content = h.boilerplate(templateName, params, format, templateSpec.boilerplate)
			},
		})
	else
		result = h.boilerplate(templateName, params, format, 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)
	local args = {}
	for i, param in ipairs(params) do
		if param.param == VARARG_KEY then
			h.insertVarArgsSyntax(args, i, param.placeholder)
		else
			args[i] = {
				param = param.param,
				value = param.placeholder or string.format("<%s>", param.name or param.param),
				inline = param.inline
			}
		end
	end
	local result = h.printTemplateInstance(templateName, args, format)
	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, 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)
	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 = {}
		for k, v in pairs(example.args or example) do
			local idx = paramOrder and utilsTable.keyOf(paramOrder, k) or (#args + 1)
			args[idx] = {
				param = k,
				value = v
			}
		end
		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 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 indent then
			result = result .. string.rep("", indent)
		end
		if arg.inline then
			result = result .. FORMATS.inline.argSeparator .. printedArg
		else
			result = result .. format.argSeparator .. 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,
	}
	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
	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 = "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.",
					},
					{
						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.",
					},
					{
						name = "split",
						type = "boolean",
						desc = "Indicator to [[Module:UtilsArg#parse|utilsArg.parse]] that the template argument is a list of commma-separated values, to be turned into an array.",
					},
				},
			},
		},
	},
}

return p
Advertisement