Module:Test

-- Module for displaying a table with TemplateData information of -- a template that is much richer than the table that is displayed -- by the tags by default.

-- Originally based on https://en.wikipedia.org/wiki/Module:Format_TemplateData, -- heavily refactored for readability and maintainability, -- and adjusted to the needs of the Terraria Wiki.

local TemplateData = { serial = "2017-11-06", suite = "TemplateData" } local plaintext = require("Module:Plain text") local trim = mw.text.trim

- -- Global tables

---The keys in this table are the arguments from the module invocation that ---are recognized for modifying the internal ``Config`` table, the values ---are the respective keys of the ``Config`` table local ConfigAliases = { -- e.g. using "|cat=foo" in the #invoke will set Config.maintenanceCateName to "foo" cat = "maintenanceCateName", cate = "maintenanceCateName",

tocclass = "classForToc", classNoNumTOC = "classForToc",

tablecss = "stylesForParamstable", tablestyle = "stylesForParamstable", cssParams = "stylesForParamstable",

wrappercss = "stylesForParamstableWrapper", wrapperstyle = "stylesForParamstableWrapper", cssParWrap = "stylesForParamstableWrapper",

docpageCreate = "patternForCreatingSubpage",

docpageDetect = "patternForMatchingSubpage",

missingDescText = "textIfDescriptionIsMissing", msgDescMiss = "textIfDescriptionIsMissing",

detailtrue = "paramDetailValueTrue",

detailfalse = "paramDetailValueFalse" }

---Global table with internal configuration options, ---filled from module and template arguments local Config = { ---Boolean, whether to display the raw `` `` table even if	---``Data.showRawTempldataTable`` is true alwaysDisplayRawTempldataTable = false,

---Boolean, whether not to display the template description above the parameter table hideDescription = true,

---String, error message to display when the template description is not set textIfDescriptionIsMissing = false,

---String, name of the maintenance category for erroneous module calls maintenanceCateName = false,

---String, CSS styling for the parameter table stylesForParamstable = false,

---String, CSS styling for a `` `` around the parameter table stylesForParamstableWrapper = false,

---String, pattern that identifies a page to be a documentation subpage patternForMatchingSubpage = false,

---String, pattern for ``string.format`` from which the documentation subpage ---title is created; e.g. "%s/doc" to create "Foo/doc" from "Foo" patternForCreatingSubpage = false,

---String, CSS class for the table of contents, ---intended to be used for suppressing the numbering classForToc = false,

---String, HTML to display when the default/example/auto value of	---a parameter is "True" (1) paramDetailValueTrue = false,

---String, HTML to display when the default/example/auto value of	---a parameter is "False" (0) paramDetailValueFalse = false }

---Global table with various pieces of information ---set and accessed throughout the process local Data = { ---``mw.html`` table, wrapper element around the entire output: `` `` outputWrapper = false,

---Table, initial TemplateData JSON object, obtained from ---decoding the TemplateData JSON code in ``TemplateData.getPlainJSON`` templdataJsonTurnedLua = false,

---Table, stores all parameter inheritances in the format ``{["child"]="parent"}``, ---filled in ``processTempldataJson`` and used in ``applyInheritance`` heirs = false,

---Boolean, whether to hide the entire output hideEntireOutput = false,

---Boolean, whether the main template description is missing, ---is set in ``makeTemplateOrParamDescription`` templateDescriptionIsMissing = false,

---Boolean, whether old syntax was encountered in at least one parameter's type field ---(old syntax = one of the three deprecated type values "string/line",	---"string/wiki-page-name", or "string/wiki-user-name"), ---is set in ``makeParamRow`` when making the cell for the parameter type oldSyntaxInAnyParameterType = false,

---Boolean, whether to display a table of contents, used in ``makeEntireHtmlOutput`` showToc = false,

---Table, sorted array of parameter names, set in ``getParameterOrder`` ---either from the "paramOrder" array from JSON or from the params' ---order in the TemplateData JSON code order = false,

---Table, the Lua representation of the "params" object from JSON, ---contains all information about all parameters params = false,

---String, concatenation of all error messages that are created in various functions stringWithAllErrors = false,

---String, language code of the target output language slang = false,

---String, TemplateData JSON code generated from the processed input ---(e.g. stripped of markup), is used to make the invisible `` `` tag, ---is set in ``processJsonAndMakeOutputFromIt`` templdataJsonPlain = false,

---Boolean, whether to fill ``Data.templdataJsonPlain`` in ---``processJsonAndMakeOutputFromIt``, i.e. make effective `` `` tags dontMakeRawTempldataTable = false,

---Boolean, whether to display the raw TemplateData table (used in	---``processJsonAndMakeOutputFromIt``) showRawTempldataTable = false,

---String, unprocessed TemplateData JSON code (comments *are* removed, though) ---that was found on the page templdataJson = false,

---String, contains `` `` wikitext; either the one found ---on the page (set in ``main``) or the one created from the input ---(set in ``processJsonAndMakeOutputFromIt``) templdataWikitext = false,

---Table, the Lua representation of the entire processed ---TemplateData JSON object, filled in ``processTempldataJson`` ---from ``Data.templdataJsonTurnedLua`` tree = false,

---Table, similar to ``Data.tree`` (but does not contain any	---parameter information), used for creating a functional ---`` `` tag treeForExport = false,

---Table, the ``mw.title`` object of the current page ---(or the subpage defined in Config, if that one has to be searched for TemplateData code) pageTitleObject = false }

---Pattern for finding a parameter object in the TemplateData JSON code, ---the "%s" in the middle that is not "%%s" will be replaced by ---the parameter name, in ``findPositionOfParameterObjectInJson`` local JsonParameterPattern = "[{,]%%s*(['\"])%s%%1%%s*:%%s*%%{"

---Table with all valid parameter types, as per https://www.mediawiki.org/wiki/Extension:TemplateData#parameter_types local ValidParameterTypes = { ["boolean"] = true, ["content"] = true, ["date"] = true, ["line"] = true, ["number"] = true, ["string"] = true, ["unknown"] = true, ["url"] = true, ["wiki-file-name"] = true, ["wiki-page-name"] = true, ["wiki-template-name"] = true, ["wiki-user-name"] = true, ["unbalanced-wikitext"] = true,

-- the following three are old syntax ["string/line"] = "line", ["string/wiki-page-name"] = "wiki-page-name", ["string/wiki-user-name"] = "wiki-user-name" }

---Table that contains all expected keys in the ``params`` or root JSON objects ---of the TemplateData JSON code; the values in these tables are "content flags" ---that are needed by ``processTempldataJson`` local ValidJsonKeys = { params = { -- all valid JSON keys in the "params" object ["aliases"] = "table", ["autovalue"] = "string", ["default"] = "string table I18N nowiki", ["deprecated"] = "boolean string", ["description"] = "string table I18N", ["example"] = "string table I18N nowiki", ["label"] = "string table I18N", ["inherits"] = "string", ["required"] = "boolean", ["suggested"] = "boolean", ["type"] = "string" },	root = { -- all valid JSON keys in the root object ["description"] = "string table I18N", ["format"] = "string", ["maps"] = "table", ["params"] = "table", ["paramOrder"] = "table", ["sets"] = "table" } }

- -- Utility functions

---Append the ``errorMsg`` string to the global ---error string, ``Data.stringWithAllErrors``. ---@param errorMsg string The error message to append local function Fault(errorMsg) if Data.stringWithAllErrors then Data.stringWithAllErrors = string.format("%s *** %s", Data.stringWithAllErrors, errorMsg) else Data.stringWithAllErrors = errorMsg end end -- Fault

---Reduce all consecutive spaces and newlines to a single space, ---a bit like HTML does. ---@param inputText string ---@return string local function collapseWhitespace(inputText) return inputText:gsub("%s*\n%s*", " "):gsub("%s%s+", " ") end -- collapseWhitespace

- -- Regular functions

---Find the position of the ``parameterName`` in the ``Data.templdataJson``. ---Be intelligent about it, i.e. search for a JSON object that has a key that ---is recognized to be associated with a parameter object. ---@param parameterName string The name of the parameter to search for ---@param init number The position to start the search at, defaults to 1 ---@return number pos The position of the parameter local function findPositionOfParameterObjectInJson(parameterName, init) -- JsonParameterPattern has the regex for finding the parameter object, -- but it is missing the parameter name, so add it, but it -- must be prepared first, i.e. special regex characters in	-- the parameter name must be escaped: local formatStringForPattern = parameterName :gsub("%%", "%%%%") -- escape the escape character :gsub("([%-.+*?^$%[%]])", "%%%1") -- escape other special chars local parameterPattern = string.format(JsonParameterPattern, formatStringForPattern)

-- find the position of the parameter name in the JSON local startIndex, endIndex = string.find(Data.templdataJson, parameterPattern, init)

local result

-- check if there is a valid parameter object at the position of the parameter name -- that we found, and if not, retry in the text that follows it	while startIndex and not result do		-- everything after the parameter name local remainingText = string.sub(Data.templdataJson, endIndex + 1)

-- the first key of the parameter object local slice = remainingText:match("^%s*\"([^\"]+)\"s*:") or remainingText:match("^%s*'([^']+)'%s*:")

if (slice and ValidJsonKeys.params[slice]) or remainingText:match("^%s*%}") then -- the first key of the parameter object is a recognized key -- or the parameter object is empty result = endIndex else -- there is no valid parameter object at the position of the parameter name -- that we found, so retry in the text that follows it			startIndex, endIndex = string.find(Data.templdataJson, parameterPattern, endIndex) end end

return result end -- findPositionOfParameterObjectInJson

---Return a MediaWiki system message whose name begins with ``templatedata-``, ---localized text for the current language. ---@param msgName string The name of the system message after ``templatedata-`` ---@return any local function getLocalizedText(msgName) return mw.message.new("templatedata-" .. msgName) :inLanguage(Data.slang) -- translate to target language :plain -- transform to wikitext end -- getLocalizedText

---Attempt to return the Boolean value of an input. ---@param toCheck string|boolean|nil The input to retrieve the Boolean value of ---@return boolean result True if ``toCheck`` is not empty and not "0", or a Boolean; false otherwise local function checkBool(toCheck) local inputType = type(toCheck) local result if inputType == "string" then result = trim(toCheck) result = (result ~= "" and result ~= "0") elseif inputType == "boolean" then -- input already is boolean, simply return it		result = toCheck else -- input is neither string nor boolean result = false end return result end -- checkBool

---Turn the error messages stored in the global ``Data.stringWithAllErrors`` ---into an "error" `` `` and return it, add the error category if necessary. ---Also add the error messages to the error text that will be displayed above the preview. ---@return string local function getAllErrorMessages local errorStringToReturn

if Data.stringWithAllErrors then -- all error messages wrapped in an "error"-classed local errorElement = mw.html.create("span") :addClass("error") :wikitext(Data.stringWithAllErrors) errorStringToReturn = tostring(errorElement)

-- display the error messages above the preview mw.addWarning("TemplateData " .. Data.stringWithAllErrors)

-- add error category if Config.maintenanceCateName then errorStringToReturn = errorStringToReturn .. ""		end end

return errorStringToReturn or "" end -- getAllErrorMessages

---Reduce runs of spaces, including newlines, to a single space, so the ---whole string is on one line. Leave `` `` blocks alone, but ---remove the tags themselves. Manually expand ````s. ---@param inputText string Text to process ---@return string local function handleNoexportAndWhitespaceAndP(inputText) local result

-- handle noexport if inputText:find(" ", 1, true) then local i = 1 local startIndex, endIndex = inputText:find(" ", i, true) result = ""

-- handle nested tags while startIndex do			if startIndex > 1 then result = result .. collapseWhitespace(inputText:sub(i, startIndex - 1)) end i = endIndex + 1 startIndex, endIndex = inputText:find(" ", i, true) if startIndex then result = result .. inputText:sub(i, startIndex - 1) i = endIndex + 1 startIndex, endIndex = inputText:find(" ", i, true) else Fault("missing closing tag ") end end result = result .. inputText:sub(i)

else result = collapseWhitespace(inputText) end

-- handle result = result:gsub("%{%{p%|(.-)%}%}", " ")

return result end -- handleNoexportAndWhitespaceAndP

---Extract the value of the key that matches the wiki language (``Data.slang``) ---from the ``langTable``. For instance, if the wiki language is "en" and the ---``langTable`` is ``{["pl"]="foo", ["en"]="bar"}``, return "bar". --- ---Make several attempts to find the wiki language: Exact match, partial match, ---match of fallbacks. Use any of the languages if neither of the attempts is ---successful. ---@param langTable table The table from which to extract; keys should be valid ---language codes and values should be the localized texts of the respective language ---@return string|nil text The localized text in the wiki language ---@return table|nil restTable The ``langTable`` without the key that has ``text`` as value local function extractWikilangText(langTable) -- normalize the ``langTable``: remove languages for which -- the value is not a string or is an empty string; -- also, while at it, count the languages local langTableNormalized = {} local langCount = 0 for langCode, localizedText in pairs(langTable) do		if type(localizedText) == "string" then localizedText = trim(localizedText) if localizedText ~= "" then langTableNormalized[langCode] = localizedText langCount = langCount + 1 end end end

if langCount == 0 then -- there are no languages with valid strings in the langTable return nil, nil end

-- the value in the ``langTableNormalized`` at the key that -- equals the wiki language (i.e. the text that we are searching for) -- or, if there is only one language in the table, that language local textInWikilangOrInOnlylang

-- the ``langTableNormalized`` without the ``textInWikilangOrInOnlylang`` local restTable

for langCode, localizedText in pairs(langTableNormalized) do		if localizedText then if langCount == 1 then -- there is only one language in the langTableNormalized, -- so take its value textInWikilangOrInOnlylang = localizedText elseif langCode:lower == Data.slang then -- there is more than one language and we have reached -- the wiki language textInWikilangOrInOnlylang = localizedText -- remove the wiki language from the table langTableNormalized[langCode] = nil -- store the remaining languages restTable = langTableNormalized break end end end

if not textInWikilangOrInOnlylang then -- the langTableNormalized contains more than one language, -- but not the wiki language

-- hence, search again for the wiki language, but this time, -- look for keys that begin with the wiki language code plus -- a hyphen (e.g. match "zh-Hans" if wiki lang is "zh") -- (but "zh-Foo bar" also, for that matter) local langPattern = "^" .. Data.slang .. "%-"		for langCode, localizedText in pairs(langTableNormalized) do			if localizedText and langCode:lower:match(langPattern) then textInWikilangOrInOnlylang = localizedText langTableNormalized[langCode] = nil restTable = langTableNormalized break end end end

if not textInWikilangOrInOnlylang then -- still haven't found the wiki language, so search for -- fallbacks of the wiki language local others = mw.language.getFallbacksFor(Data.slang) table.insert(others, "en") -- always add EN as the last fallback for i = 1, #others do			local fallbackLang = others[i] if langTableNormalized[fallbackLang] then textInWikilangOrInOnlylang = langTableNormalized[fallbackLang] langTableNormalized[fallbackLang] = nil restTable = langTableNormalized break end end end

if not textInWikilangOrInOnlylang then -- searching for fallbacks still didn't yield any results, -- so now we can only resort to picking any language for langCode, localizedText in pairs(langTableNormalized) do			if localizedText then textInWikilangOrInOnlylang = localizedText langTableNormalized[langCode] = nil restTable = langTableNormalized break end end end

if restTable then -- report invalid language tags for langCode, localizedText in pairs(restTable) do			if localizedText then -- match any 2- to 3-ASCII-letter string, optionally appended -- with a hyphen and more ASCII letters, and optionally surrounded -- by spaces local baseCode = langCode:match("^%s*(%a%a%a?)%s*$") or langCode:match("^%s*(%a%a%a?)%-%a*%s*$") if not baseCode or not mw.language.isKnownLanguageTag(baseCode) then Fault(string.format("Invalid ", langCode)) end end end end

return textInWikilangOrInOnlylang, restTable end -- extractWikilangText

---For each of the parameters defined in ``Data.heirs``, inherit ---the parameter information and update the parameter's entries in ---``Data.params`` and ``Data.tree.params``, including setting the ---``inherits`` object to nil. Also remove the parameter from ---``Data.heirs`` if inheriting was performed successfully. local function applyInheritance -- count number of parameters in Data.heirs local paramCount = 0 for _ in pairs(Data.heirs) do		paramCount = paramCount + 1 end

if paramCount == 0 then -- Data.heirs is empty, nothing to process return end

local dataParams = Data.params local dataTreeParams = Data.tree.params

local paramsToHandle = paramCount

-- this following slightly awkward double loop facilitates -- easily avoiding circular inheritances; its functionality -- is basically: "for each param in Data.heirs, perform the	-- inheritance" for _ = 1, paramCount do		for paramChild, paramParent in pairs(Data.heirs) do			if paramParent and not Data.heirs[paramParent] then paramsToHandle = paramsToHandle - 1

-- do not handle this child again Data.heirs[paramChild] = nil dataTreeParams[paramChild].inherits = nil

-- inherit paraminfo (label, type, etc.) from the parent -- to the child: first get all the paraminfo from parent, -- then add the paraminfo from the child to that and -- overwrite data where necessary

local newParaminfoForChild = {}

-- copy paraminfo from parent to temporary table for k, v in pairs(dataParams[paramParent]) do					newParaminfoForChild[k] = v				end

-- add paraminfo from child to temporary table if dataParams[paramChild] then for k, v in pairs(dataParams[paramChild]) do						if type(v) ~= "nil" then newParaminfoForChild[k] = v						end end end

-- the temporary table newParaminfoForChild now contains -- all of the child's paraminfo plus the paraminfo -- inherited from the parent, so apply it				dataParams[paramChild] = newParaminfoForChild

-- repeat the process for Data.tree.params now -- (the first time above was for Data.params)

newParaminfoForChild = {} for k, v in pairs(dataTreeParams[paramParent]) do					newParaminfoForChild[k] = v				end for k, v in pairs(dataTreeParams[paramChild]) do					if type(v) ~= "nil" then newParaminfoForChild[k] = v					end end dataTreeParams[paramChild] = newParaminfoForChild end end end

if paramsToHandle > 0 then local errorString for paramChild, paramParent in pairs(Data.heirs) do			if paramParent then if errorString then errorString = errorString .. " &#124; " .. paramChild else errorString = "Circular inherits: " .. paramChild end end end Fault(errorString) end end -- applyInheritance

---Make a description string for the entire template or for a specific parameter, ---based on the information passed in the input ``tableWithDescription``. That input table ---needs to have a ``description`` key whose value is either the description string directly, ---or a table with two elements where the first one is a description string and the second ---one an array of description details. --- ---Use the global ``Config.textIfDescriptionIsMissing`` here and set the global ---``Data.templateDescriptionIsMissing`` here. ---@param tableWithDescription table Table with a ``description`` key ---@param shouldFailIfNoDescription boolean Whether to fail if no description can be gotten, only has an effect if the global var is set ---@return table html The HTML of the description string local function makeTemplateOrParamDescription(tableWithDescription, shouldFailIfNoDescription) local descriptionOutput = mw.html.create("div") local outputAddition -- will be appended to the output

if tableWithDescription and tableWithDescription.description then if type(tableWithDescription.description) == "string" then -- description is a simple string, just display it			descriptionOutput:wikitext(tableWithDescription.description) else -- description is not a string, but (likely) a table, so			-- display its first element as description and display its -- second element as a list descriptionOutput:wikitext(tableWithDescription.description[1])

outputAddition = mw.html.create("ul") if not Config.alwaysDisplayRawTempldataTable then -- hide list of description details outputAddition:addClass("templatedata-maintain") :css("display", "none") end for k, v in pairs(tableWithDescription.description[2]) do				outputAddition:node(mw.html.create("li")					:node(mw.html.create("code"):wikitext(k))					:node(mw.html.create("br"))					:wikitext(handleNoexportAndWhitespaceAndP(v))) end end

elseif Config.textIfDescriptionIsMissing and shouldFailIfNoDescription then descriptionOutput:addClass("error") :wikitext(Config.textIfDescriptionIsMissing) Data.templateDescriptionIsMissing = true

else -- either ``tableWithDescription`` is nil or it doesn't have a ``description`` key, -- and either there is no text for missing descriptions defined or we should fail silently, -- so don't output anything return end

if outputAddition then -- combine outputAddition and descriptionOutput in a div return mw.html.create("div") :node(descriptionOutput) :node(outputAddition) end return descriptionOutput end -- makeTemplateOrParamDescription

---Fill ``Data.order`` with the names of all parameters (based on ``Data.tree.params``), ---sorted in a way that represents their order in the TemplateData JSON code. local function sortParametersByTheirOrderInJson local paramCount = 0 local nameOfFirstParam -- only needed if there is only one parameter

-- iterate over all parameters in an unspecified order -- to find out if there are 0, 1, or >= 2 parameters for parameterName, _ in pairs(Data.tree.params) do		if paramCount == 0 then -- we are at the very first parameter Data.order = {} paramCount = 1 nameOfFirstParam = parameterName else -- we are at the second parameter, this is all -- that we need to know for now, so stop looping paramCount = 2 break end end

if paramCount < 2 then -- there is no or only one parameter, so there is no		-- order to determine – simply add the param to the array if nameOfFirstParam then table.insert(Data.order, nameOfFirstParam) end return end

-- there are at least 2 parameters, so continue

-- for each parameter, get its position in the JSON and -- attach its name to that position, then later sort the -- positions and restore the names from that; -- with this method, it doesn't matter that pairs yields -- an unspecified order

local parameterPositions = {} -- array of parameter positions local parameterPositionToName = {} -- table that stores the parameter name for each position

-- iterate over all parameters in an unspecified order for parameterName, _ in pairs(Data.tree.params) do

-- determine the position of this parameter local parameterPosition = findPositionOfParameterObjectInJson(parameterName, 1)

if parameterPosition then -- save the position of this parameter to the array of positions table.insert(parameterPositions, parameterPosition)

-- attach the name of this parameter to its position parameterPositionToName[parameterPosition] = parameterName

-- try to find the parameter a second time if findPositionOfParameterObjectInJson(parameterName, parameterPosition) then Fault(string.format("Parameter '%s' detected twice", parameterName)) end else Fault(string.format("Parameter '%s' not detected", parameterName)) end

end

-- iterate over the table that stored the parameter names with their -- positions in the numerical order of positions and add the parameter -- names to Data.order table.sort(parameterPositions) for paramPosition = 1, #parameterPositions do		-- turn current position into name local parameterName = parameterPositionToName[parameterPositions[paramPosition]] table.insert(Data.order, parameterName) end end -- sortParametersByTheirOrderInJson

---Fill the global ``Data.order`` array with the parameter names, ---ordered in the correct way. If ``Data.tree.paramOrder`` is defined, ---i.e. if there is a ``paramOrder`` array in the TemplateData JSON code, ---then use that, otherwise use the order in which the parameters are listed ---in the TemplateData JSON code. local function getParameterOrder if not Data.templdataJson then return end

if Data.tree["paramOrder"] then -- there is already an explicit order defined, so simply copy it		Data.order = {} for i = 1, #Data.tree["paramOrder"] do			table.insert(Data.order, Data.tree["paramOrder"][i]) end else -- there is no order explicitly defined, so get it from how the -- parameters are defined in the TemplateData JSON code sortParametersByTheirOrderInJson end end -- getParameterOrder

---If the ``key`` is a number, set the ``data-sort-value`` attribute of ---the ``cell`` to a zero-padded, five-character string representation of ---the number. ---@param cell table The cell whose attribute is to be adjusted ---@param key string The string which is a number or not ---@return table cell The cell with the adjusted ``data-sort-value`` attribute local function adjustSortvalueForNumericalKey(cell, key) if key:match("^%d+$") then -- key is a number, so set sort-value to a zero-padded, -- five-character string representation of the number cell:attr("data-sort-value", string.format("%05d", tonumber(key))) end return cell end -- adjustSortvalueForNumericalKey

---Make a row for the parameter outputtable with information about one parameter. ---Use the parameter's entry in the global ``Data.tree`` for that. ---@param parameterName string The name of the parameter for which to make the row ---@return table paramRow The `` `` element with the information local function makeParamRow(parameterName) -- whether this row should not be marked with a red border local legal = true

local fine = function(paramName) if paramName == trim(paramName) and paramName ~= "" then return not paramName:find("%|=\n") and not paramName:find("%s%s") end return false end

-- get data about this parameter local paraminfo = Data.tree.params[parameterName]

-- turn empty strings in the paraminfo into falses for k, v in pairs(paraminfo) do		if v == "" then paraminfo[k] = false end end

local Modes = { -- enum ["Required"] = 1, ["Suggested"] = 2, ["Optional"] = 3, ["Deprecated"] = 4 }	-- inverse of the enum, so that e.g. statusNames[1] == "required" local statusNames = {} for statusname, statusvalue in pairs(Modes) do		table.insert(statusNames, statusvalue, statusname:lower) end

-- prepare status (needed for border-left on first (label) table cell) local mode if paraminfo.required then mode = Modes.Required if paraminfo.deprecated then Fault(string.format("Required deprecated ", parameterName)) legal = false end elseif paraminfo.deprecated then mode = Modes.Deprecated elseif paraminfo.suggested then mode = Modes.Suggested else mode = Modes.Optional end local status = statusNames[mode]

-- make cell 1: label local paramLabelCell = mw.html.create("td") local sortkey = paraminfo.label or parameterName paramLabelCell = adjustSortvalueForNumericalKey(paramLabelCell, sortkey) paramLabelCell:wikitext(sortkey) -- label styling, depending on parameter mode (deprecated/required/...) paramLabelCell:addClass("templatedata-doc-param") paramLabelCell:addClass("param-" .. status)

-- make cell 2: name and aliases local codeElem = mw.html.create("code") codeElem:wikitext(parameterName) if not fine(parameterName) then codeElem:addClass("error") Fault(string.format("Bad ID params. ", parameterName)) legal = false paramLabelCell:attr("data-sort-value", " " .. sortkey) end

local paramNameCell = mw.html.create("td"):node(codeElem) paramNameCell = adjustSortvalueForNumericalKey(paramNameCell, parameterName)

-- aliases if type(paraminfo.aliases) == "table" then local errorWithAnyAlias = false

-- iterate over all aliases, add them to the cell with the parameter name for _, alias in pairs(paraminfo.aliases) do			paramNameCell:tag("br")

if type(alias) == "string" then if fine(alias) then paramNameCell:node(mw.html.create("code"):wikitext(trim(alias))) else errorWithAnyAlias = true paramNameCell:node(mw.html.create("span")						:addClass("error")						:css("font-style", "italic")						:wikitext("string")					) end else errorWithAnyAlias = true paramNameCell:node(mw.html.create("code")					:addClass("error")					:wikitext(type(alias))				) end end

if errorWithAnyAlias then local invalidvalue = string.format("params. .aliases", parameterName) Fault(getLocalizedText("invalid-value"):gsub("$1", invalidvalue)) legal = false end end paramNameCell:css("font-size", "92%"):css("white-space", "nowrap")

-- make cell 3: description local descriptionCell = mw.html.create("td") local descriptionText = makeTemplateOrParamDescription(paraminfo) if descriptionText then descriptionCell:node(descriptionText) end

-- details (below description): default, example, auto if paraminfo.default or paraminfo.example or paraminfo.autovalue then local details = { "default", "example", "autovalue" } local allDetails = mw.html.create("dl"):cssText("font-size: small;")

-- iterate over the three types of details for i = 1, #details do			local thisDetailType = details[i] -- current type: default, example, or autovalue local detaildata = paraminfo[thisDetailType] if detaildata then -- label for the detail (e.g. "Auto value: ") local thisDetailLabel = mw.html.create("dt"):wikitext(getLocalizedText("doc-param-" .. thisDetailType) .. ": ") if (string.len(detaildata) < 80) then thisDetailLabel:cssText("float: left; margin-right: 1.6em; font-weight: normal;") end allDetails:node(thisDetailLabel)

-- content of the detail local thisDetailContent = mw.html.create("dd") if paraminfo.type == "boolean" then -- special formatting for parameters with Boolean values if detaildata == "0" then thisDetailContent:wikitext(Config.paramDetailValueFalse) elseif detaildata == "1" then thisDetailContent:wikitext(Config.paramDetailValueTrue) else thisDetailContent:wikitext(" ") end else thisDetailContent:wikitext(" ") end allDetails:node(thisDetailContent) end end descriptionCell:node(allDetails) end

-- make cell 4: type local typeCell = mw.html.create("td") if paraminfo.type then

-- Convert the parameter type to modern syntax if necessary. If conversion -- was necessary, then ``paramTypeInModernSyntax`` will hold the name of -- the parameter in modern syntax. -- Otherwise, it will be ``true`` if the parameter type is known and valid, and ``nil`` if not. local paramTypeInModernSyntax = ValidParameterTypes[paraminfo.type]

if paramTypeInModernSyntax then if type(paramTypeInModernSyntax) == "string" then -- the parameter type is in old syntax, so update it in the data source Data.params[parameterName].type = paramTypeInModernSyntax typeCell:wikitext(getLocalizedText("doc-param-type-" .. paramTypeInModernSyntax)) :tag("br") typeCell:node(mw.html.create("span")					:addClass("error")					:wikitext(paraminfo.type)) Data.oldSyntaxInAnyParameterType = true else -- the parameter type is in modern syntax, nothing to change typeCell:wikitext(getLocalizedText("doc-param-type-" .. paraminfo.type)) end else -- the parameter type is not defined in the list of valid parameter types Data.params[parameterName].type = "unknown" typeCell:addClass("error") :wikitext("INVALID") Fault(getLocalizedText("invalid-value"):gsub("$1", string.format("params. .type", parameterName))) legal = false end else typeCell:wikitext(getLocalizedText("doc-param-type-unknown")) end

-- make cell 5: status local statusText = mw.html.create("div") :wikitext(getLocalizedText("doc-param-status-" .. status)) if mode == Modes.Required or mode == Modes.Deprecated then -- emphasize the status for required and deprecated params statusText:css("font-weight", "bold") -- add deprecation details, if provided if type(paraminfo.deprecated) == "string" then statusText:node(mw.html.create("div")				:css("font-size", "92%"):css("font-style", "italic")				:wikitext(paraminfo.deprecated)			) end end local statusCell = mw.html.create("td") -- sort by status order, not by status name :attr("data-sort-value", tostring(mode)) :node(statusText)

-- combine the five cells into a table row local paramRow = mw.html.create("tr") :attr("id", mw.uri.anchorEncode("templatedata:" .. parameterName)) -- for linking :node(paramLabelCell) :node(paramNameCell) :node(descriptionCell) :node(typeCell) :node(statusCell) :newline if not legal then paramRow:addClass("templatedata-doc-param-illegal") end

return paramRow end -- makeParamRow

---Create the table that displays information about the parameters ---from ``Data.tree`` and ``Data.tree.params``. Include header and styling. ---@return table|nil html The table object (or div if a wrapper is defined) local function makeParamsTable if not Data.tree or not Data.tree.params then -- there is nothing to display return end

local paramsTable = mw.html.create("table") :addClass("terraria") :addClass("lined") :addClass("templatedata-doc")

-- make header local headerRow = mw.html.create("tr") :node(mw.html.create("th")			:attr("colspan", "2")			:wikitext(getLocalizedText("doc-param-name"))) :node(mw.html.create("th")			:wikitext(getLocalizedText("doc-param-desc"))) :node(mw.html.create("th")			:wikitext(getLocalizedText("doc-param-type"))) :node(mw.html.create("th")			:wikitext(getLocalizedText("doc-param-status")))

paramsTable:newline :node(headerRow) :newline

-- fill Data.order getParameterOrder

if Data.order then -- make table sortable if there are multiple parameters if #Data.order > 1 then paramsTable:addClass("sortable") end

-- add table row for each parameter for i = 1, #Data.order do			paramsTable:node(makeParamRow(Data.order[i])) end end

-- apply custom styling if necessary and return if Config.stylesForParamstable then paramsTable:cssText(Config.stylesForParamstable) end if Config.stylesForParamstableWrapper then return mw.html.create("div") :cssText(Config.stylesForParamstableWrapper) :node(paramsTable) else return paramsTable end end -- makeParamsTable

---Turn the HTML of ``Data.outputWrapper`` into a string, ---append all error messages. ---@return string local function outputhtmlToStringAndAppendErrors local stringToReturn

if Data.outputWrapper then -- regular output, formatted table stringToReturn = tostring(Data.outputWrapper) elseif Data.templdataWikitext then -- only raw templatedata table stringToReturn = Data.templdataWikitext else stringToReturn = "" end

return stringToReturn .. getAllErrorMessages end -- outputhtmlToStringAndAppendErrors

---Find TemplateData JSON within the page source of the ``Data.pageTitleObject``. ---Search for data enclosed in `` ... `` ---and for data enclosed in ``|JSON=...|data=1``. ---@return string|nil data The data that was found, ``nil`` if none found local function findJsonInPage ---@diagnostic disable-next-line: undefined-field local pageText = Data.pageTitleObject:getContent

-- search for a templatedata opening tag ("opener") local openerStartIndex, openerEndIndex = string.find(pageText, " ", 1, true) local tags = true -- the string that was found is enclosed in templatedata tags

if not openerStartIndex then -- couldn't find a templatedata opening tag, so search for template opening string ("opener") openerStartIndex, openerEndIndex = string.find(pageText, "|JSON=", 1, true) tags = false end

if openerStartIndex then -- find the closing part of the enclosure ("closer") local closerStartIndex if tags then -- the data is enclosed in templatedata tags, so search for the closing tag closerStartIndex = pageText:find(" ", openerEndIndex, true) else -- the data is in a template call, so search for the closing braces closerStartIndex = pageText:find("|data=1}}", openerEndIndex, true) end if closerStartIndex then -- extract the data, now that we have the indices of the enclosure return trim(string.sub(pageText, openerEndIndex + 1, closerStartIndex - 1)) end end return end -- findJsonInPage

---Remove most markup from the ``inputText``: ---* Turn newlines into spaces ---* Remove everything in `` `` tags ---* Replace ````s with quotes ---* Remove/replace all wiki markup ---* Escape HTML entities ---@param inputText string ---@return string result local function removeMarkup(inputText) -- (This function is called for the template description and	-- all parameter properties whose "content flags" contain "I18N",	-- and its result is put into Data.treeForExport.)

local result

if inputText then -- newlines to spaces result = inputText:gsub("\n", " ") -- remove noexport if result:find(" ", 1, true) then result = result:gsub(" (.*) ", "") end -- to quotes result = result:gsub("%{%{p%|(.-)%}%}", "&#34;%1&#34;") -- wiki markup result = plaintext._main(result) -- escape HTML entities if result:find("&", 1, true) then result = mw.text.decode(result) end end

return result end -- removeMarkup

---Make a JSON string with TemplateData JSON code, from the ---information that was previously collected (``Data.treeForExport``, ---``Data.order``, and ``Data.params`` in particular). ---@return string json The TemplateData JSON code local function makeTempldataJsonFromData local jsonString if Data.treeForExport then -- Data.treeForExport contains everything that is not -- parameter information, e.g. template description, so		-- begin with that, and replace the closing brace with a comma jsonString = mw.text.jsonEncode(Data.treeForExport):gsub("%}$", ",") else jsonString = "{" end

-- add parameter information jsonString = jsonString .. "\n\"params\":{" if Data.order then local paramsJson = {} -- table of parameter JSON strings to concatenate

-- iterate over parameters for i = 1, #Data.order do			local parameterName = Data.order[i] table.insert(paramsJson,				mw.text.jsonEncode(parameterName) .. -- param name				":" ..				mw.text.jsonEncode(Data.params[parameterName]) -- param info			) end jsonString = jsonString .. table.concat(paramsJson, ",\n")

end jsonString = jsonString .. "\n}\n}"

return jsonString end -- makeTempldataJsonFromData

---Take the ``Data.templdataJsonTurnedLua`` table and process it, ---which includes removal of wiki markup and the like. ---Put the data in the tables ``Data.tree`` and ``Data.treeForExport``. ---The function should be called once with a nil ``parameterName``, to process ---the root TemplateData JSON object, and then once for each parameter. ---@param parameterName string|nil The name of the parameter for which to perform the operations local function processTempldataJson(parameterName)

-- helper function for making error messages local f = function (a, at) local result if at then result = string.format(" ", at) else result = "root" end if a then result = string.format("%s ", result, a)		end return result end

local parent -- the table to iterate over if parameterName then -- we are iterating over one parameter object of		-- the (Lua representation of the) TemplateData JSON code parent = Data.templdataJsonTurnedLua["params"][parameterName] else -- we are iterating over the root element of		-- the (Lua representation of the) TemplateData JSON code parent = Data.templdataJsonTurnedLua end

if type(parent) ~= "table" then Fault(f .. " needs to be of  type") return end

local dataToPutIntoTree

-- tables that will be filled with the contents of the JSON object, -- will be set to Data.tree or Data.tree.params (Data.treeForExport or Data.params resp.) -- (depending on whether we are iterating over one param or root) once needed local treeTarget, treeForExportTarget

-- name of the parameter of this function call, -- nil when calling this function for the root JSON object local thisParameter

-- table of keys that are valid for the parent JSON object local validKeys -- The values of this table are "content flags" that define the content type -- of the respective values in the JSON object. -- For example, the "description" key in this table (when a ``parameterName`` is not defined) -- has the content flags "string table I18N", which means that it can be a string or a table of languages, -- which accurately represents its characteristics in the root TemplateData JSON object.

if parameterName then validKeys = ValidJsonKeys.params if type(parameterName) == "number" then thisParameter = tostring(parameterName) else thisParameter = parameterName end else validKeys = ValidJsonKeys.root end

-- iterate over the children of the parent JSON object for jsonKey, jsonValue in pairs(parent) do

-- get the "content flags" for this JSON key, e.g. "boolean" or "string table I18N" local contentFlags = validKeys[jsonKey]

if contentFlags then local valueType = type(jsonValue)

if valueType == "string" then jsonValue = trim(jsonValue) end

if contentFlags:find(valueType, 1, true) then

-- put the contents of this JSON object into Data.tree and Data.treeForExport

if contentFlags:find("I18N", 1, true) then

if valueType == "string" then dataToPutIntoTree = handleNoexportAndWhitespaceAndP(jsonValue) else local translated -- get the string for the current language from the table of defined languages jsonValue, translated = extractWikilangText(jsonValue) if jsonValue then if translated and jsonKey == "description" then dataToPutIntoTree = { [1] = handleNoexportAndWhitespaceAndP(jsonValue), [2] = translated -- the rest of the language table, minus our language }							else dataToPutIntoTree = handleNoexportAndWhitespaceAndP(jsonValue) end else dataToPutIntoTree = false end end

if jsonValue then if contentFlags:find("nowiki", 1, true) then dataToPutIntoTree = mw.text.nowiki(jsonValue) else jsonValue = removeMarkup(jsonValue) end end

else

if jsonKey == "params" and not parameterName then -- we are iterating over the root TemplateData JSON object -- and are now at the "params" child, so don't do anything with it						jsonValue = nil dataToPutIntoTree = nil

elseif jsonKey == "format" and not parameterName then -- we are iterating over the root TemplateData JSON object -- and are now at the "format" child jsonValue = mw.text.decode(jsonValue) dataToPutIntoTree = jsonValue

elseif jsonKey == "inherits" then -- we are iterating over one parameter object -- and are now at the "inherits" child dataToPutIntoTree = jsonValue

-- set the heirs of the parameter that we are iterating over -- in the global inheritance table if not Data.heirs then Data.heirs = {} end Data.heirs[thisParameter] = jsonValue

jsonValue = nil

elseif valueType == "string" then -- the current child is not any of the three above -- and is a string jsonValue = mw.text.nowiki(jsonValue) dataToPutIntoTree = jsonValue

else -- the current child is not any of the three above -- and is not a string dataToPutIntoTree = jsonValue end end

-- put data into tree if type(dataToPutIntoTree) ~= "nil" then if not treeTarget then -- this is the first child for which dataToPutIntoTree is not nil if parameterName then -- we are iterating over one parameter object, so							-- put the data into Data.tree.params if not Data.tree.params then Data.tree.params = {} end Data.tree.params[thisParameter] = {} treeTarget = Data.tree.params[thisParameter] else -- we are iterating over the root TemplateData JSON object, so							-- put the data in Data.tree Data.tree = {} treeTarget = Data.tree end end treeTarget[jsonKey] = dataToPutIntoTree dataToPutIntoTree = false end

-- put data into treeForExport if type(jsonValue) ~= "nil" then if not treeForExportTarget then -- this is the first child for which jsonValue is not nil if parameterName then -- we are iterating over one parameter object, so							-- put the data into Data.params if not Data.params then Data.params = {} end Data.params[thisParameter] = {} treeForExportTarget = Data.params[thisParameter] else -- we are iterating over the root TemplateData JSON object, so							-- put the data in Data.treeForExport Data.treeForExport = {} treeForExportTarget = Data.treeForExport end end treeForExportTarget[jsonKey] = jsonValue end else -- the type of this child of the JSON object is not among the -- "content flags" that are defined for this JSON key Fault(string.format( "Type  bad for %s", contentFlags, f(jsonKey, thisParameter) ))			end else -- the name of this child of the JSON object is not among the ``validKeys`` Fault("Unknown component " .. f(jsonKey, thisParameter)) end end end -- processTempldataJson

---Return an HTML string with the entire output: ---template description, TOC, parameters table, and format string. ---@return table div The `` `` element that wraps the output local function makeEntireHtmlOutput local div = mw.html.create("div")

-- template description if not Config.hideDescription then local templateDescription = makeTemplateOrParamDescription(Data.tree, true) if templateDescription then div:node(templateDescription) end end

-- table of contents if Data.showToc then local toc = mw.html.create("div") if Config.classForToc then toc:addClass(Config.classForToc) end toc:css("margin-top", "0.5em") :wikitext("") div:newline :node(toc) :newline end

-- main table with template parameters local paramsTable = makeParamsTable if paramsTable then if Data.showToc then local heading = mw.html.create("h2"):wikitext(getLocalizedText("doc-params")) div:node(heading) :newline end div:node(paramsTable) end

-- note about template transclusion format if Data.tree and Data.tree["format"] then local formatStyle = string.lower(Data.tree["format"]) if formatStyle ~= "inline" and formatStyle ~= "block" then -- format is neither inline nor block, so it is a custom formatting string formatStyle = "custom" end

-- display description about the formatting style local p = mw.html.create("p") :wikitext(getLocalizedText("doc-format-" .. formatStyle))

if formatStyle == "custom" then -- append the custom formatting style, wrapped in 			p:newline :node(mw.html.create("code")					:wikitext(Data.tree["format"])				) end

div:node(p) end return div end -- makeEntireHtmlOutput

---Process the TemplateData JSON code and build the HTML output from that. ---Append an invisible `` `` block, if necessary. ---When this function is done, ``Data.outputWrapper`` will hold the entire output. local function processJsonAndMakeOutputFromIt

Data.outputWrapper = mw.html.create("div"):addClass("mw-templatedata-doc-wrap") if Data.hideEntireOutput then ---@diagnostic disable-next-line: undefined-field Data.outputWrapper:cssText("display:none;") end

-- fill Data.tree/Data.treeForExport from Data.templdataJsonTurnedLua -- for the root TemplateData JSON object, i.e. things like -- template description and template transclusion format processTempldataJson

-- fill Data.tree/Data.treeForExport from Data.templdataJsonTurnedLua -- for each parameter, i.e. param name, description, etc.	if Data.treeForExport then if type(Data.templdataJsonTurnedLua["params"]) == "table" then for paramname, _ in pairs(Data.templdataJsonTurnedLua["params"]) do				processTempldataJson(paramname) end -- Data.heirs was filled in processTempldataJson -- and contains the inheritances of all parameters if Data.heirs then applyInheritance end end end

-- build HTML and append it to output ---@diagnostic disable-next-line: undefined-field Data.outputWrapper:node(makeEntireHtmlOutput)

-- build raw templatedata table and append it to output if not Data.dontMakeRawTempldataTable then

Data.templdataJsonPlain = makeTempldataJsonFromData

if not TemplateData.frame then -- there's no frame to call the parser function from return end

local templatedataTagWrapper = mw.html.create("div") Data.templdataWikitext = TemplateData.frame:callParserFunction{ name = "#tag", args = { "templatedata", Data.templdataJsonPlain } }		templatedataTagWrapper:wikitext(Data.templdataWikitext)

if Config.alwaysDisplayRawTempldataTable then -- simply display raw templatedata table, -- ignore Data.showRawTempldataTable ---@diagnostic disable-next-line: undefined-field Data.outputWrapper:node(mw.html.create("hr")) ---@diagnostic disable-next-line: undefined-field Data.outputWrapper:node(templatedataTagWrapper) else -- wrap raw templatedata table in a collapsible element, -- respect Data.showRawTempldataTable templatedataTagWrapper:addClass("mw-collapsible-content") local wrapperCollapse = mw.html.create("div") :addClass("mw-collapsible") :addClass("mw-collapsed") :css("font-size", "85%") :wikitext("Test of raw TemplateData output: ") :node(templatedataTagWrapper) if not Data.showRawTempldataTable then wrapperCollapse:css("display", "none") end ---@diagnostic disable-next-line: undefined-field Data.outputWrapper:node(wrapperCollapse) end end end -- processJsonAndMakeOutputFromIt

---Remove commented-out lines from ``Data.templdataJson``. local function removeComments string.gsub(Data.templdataJson,		"([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])",		"%1%3"	) end -- removeComments

---Fill the global ``Config`` and ``Data`` tables based on the module and template ---arguments. Retrieve the TemplateData JSON code from the page. ---@param moduleArgs table The parameters passed when invoking this module ---@param templateArgs table The parameters passed when transcluding the template that invokes this module ---@return string output The entire output string local function main(moduleArgs, templateArgs)

-- set Config parameters with input from module parameters for k, v in pairs(ConfigAliases) do		if moduleArgs[k] and moduleArgs[k] ~= "" then Config[v] = moduleArgs[k] end end Config.alwaysDisplayRawTempldataTable = checkBool(templateArgs.debug or moduleArgs.debug) Config.hideDescription = not templateArgs.description

-- language for the process: from input, or wiki default language code (disregarding user language) Data.slang = templateArgs.lang or moduleArgs.lang or mw.language.getContentLanguage:getCode

Data.hideEntireOutput = checkBool(templateArgs.hide or moduleArgs.hide) and not Config.alwaysDisplayRawTempldataTable Data.dontMakeRawTempldataTable = checkBool(templateArgs.lazy or moduleArgs.lazy) and not Config.alwaysDisplayRawTempldataTable Data.showToc = checkBool(templateArgs.TOC) Data.showRawTempldataTable = checkBool(templateArgs.showRaw)

-- get TemplateData JSON code

local source -- the TemplateData JSON code

-- for that, first check the template transclusion: $JSON and $1 if templateArgs.JSON then source = templateArgs.JSON elseif templateArgs[1] then local templateArg1 = trim(templateArgs[1]) local start = templateArg1:sub(1, 1) local charseq = mw.ustring.char(127, 39, 34, 96, 85, 78, 73, 81) --  ' " ` U N I Q		if start == "<" then			Data.templdataWikitext = templateArg1		elseif start == "{" then			source = templateArg1		elseif mw.ustring.sub(templateArg1, 1, 8) == charseq then			Data.templdataWikitext = templateArg1		end	end

-- if the template transclusion did not have any useable TemplateData, -- then check for TemplateData JSON code on the current page if not source then Data.pageTitleObject = mw.title.getCurrentTitle source = findJsonInPage end

-- if the current page does not contain any TemplateData JSON code either, -- then check for TemplateData JSON code on the subpage defined in Config -- (most likely a documentation subpage) if not source then ---@diagnostic disable-next-line: undefined-field local currentPageNameUnprefixed = Data.pageTitleObject.text -- title of the current page without namespace ---@diagnostic disable-next-line: undefined-field local currentPageNamePrefixed = Data.pageTitleObject.prefixedText -- title of the current page with namespace

if Config.patternForMatchingSubpage and Config.patternForCreatingSubpage and not string.match(currentPageNameUnprefixed, Config.patternForMatchingSubpage) then -- only if current page is not the subpage local subpageName = string.format(Config.patternForCreatingSubpage, currentPageNamePrefixed) -- change the pageTitleObject to the one of the subpage (since findJsonInPage uses the pageTitleObject) Data.pageTitleObject = mw.title.new(subpageName)

---@diagnostic disable-next-line: undefined-field if Data.pageTitleObject.exists then source = findJsonInPage end end end

--[[	this part seems unwanted?	if the TemplateData JSON code came from the doc subpage, then it would actually be wanted to make	a functional tag for the template main page, so that it gets recognized by VisualEditor.	anyway, if this part is active, particularly the second to last line, then the data used by the VE seems	to be taken from the tags inside the call on the doc subpage – and no	processing occurs whatsoever.

-- if the TemplateData JSON code came from the subpage, -- then set Data.dontMakeRawTempldataTable to true if not Data.dontMakeRawTempldataTable and Config.patternForMatchingSubpage then if not Data.pageTitleObject then Data.pageTitleObject = mw.title.getCurrentTitle end Data.dontMakeRawTempldataTable = string.match(Data.pageTitleObject.text, Config.patternForMatchingSubpage) end ]]

TemplateData.getPlainJSON(source) -- process JSON and make all output

return outputhtmlToStringAndAppendErrors

end -- main

- -- TemplateData table functions

---Check the age of this implementation (``TemplateData.serial``) ---against some minimum (``assert``). ---Called when invoking the module with the ``failsafe`` function. ---@param assert any The invocation input. ---@return string|boolean result The age of this implementation if it is younger than the ``assert``, false otherwise TemplateData.failsafe = function(assert) if not assert or assert <= TemplateData.serial then return TemplateData.serial else return false end end -- TemplateData.failsafe

---Put the ``source`` TemplateData JSON code into the global ``Data.templdataJson``, ---turn it into Lua tables and process it (removing markup and making the output ---table (i.e. filling ``Data.outputWrapper`` with all HTML output)), then generate ---TemplateData JSON code from that again and return it ---@param source string The TemplateData JSON code to be processed ---@return string json The processed TemplateData JSON code TemplateData.getPlainJSON = function(source)

if type(source) ~= "string" then -- there's no usable TemplateData JSON code return end

Data.templdataJson = source -- copy to global variable

-- prepare the TemplateData JSON code removeComments

-- turn the TemplateData JSON code into a Lua table Data.templdataJsonTurnedLua = mw.text.jsonDecode(Data.templdataJson)

if Data.templdataJsonTurnedLua then processJsonAndMakeOutputFromIt

if Data.oldSyntaxInAnyParameterType then Fault('the parameter types "string/line", "string/wiki-page-name", and "string/wiki-user-name" are deprecated') end if Data.templateDescriptionIsMissing then Fault(Config.textIfDescriptionIsMissing) end

elseif not Data.templdataWikitext then Fault("fatal JSON error") end

return Data.templdataJsonPlain end -- TemplateData.getPlainJSON

---Function for simulating a ``go`` call from another module. TemplateData.test = function(moduleArgs, templateArgs) TemplateData.frame = mw.getCurrentFrame return main(moduleArgs, templateArgs) end -- TemplateData.test

- -- main return object

return {

---Main entry function for invoking the module from a template. ---Wraps ``main`` in an exception handler. ---@param frame table ---@return string go = function(frame) TemplateData.frame = frame local successful, result = pcall(main, frame.args, frame:getParent.args) if not successful then Fault("INTERNAL: " .. result) result = getAllErrorMessages end return result end,

---Versioning interface. ---@param frame table ---@return string result The age of the implementation of this module if the input is older; an empty string otherwise failsafe = function(frame) local since

local frameType = type(frame) if frameType == "table" then since = frame.args[1] elseif frameType == "string" then since = frame end

if since then since = trim(since) if since == "" then since = false end end

return TemplateData.failsafe(since) or "" end,

---Interface for usage by other modules. ---@return table TemplateData = function return TemplateData end

}