Module:TemplateData

--- A library used to process other modules' arguments and to output matching -- template data. -- @script TemplateData

local p = {}

-- general helper functions local function quote(s) return string.format('"%s"', s) end

local function ensureTable(v) if type(v) == "table" then return v end return {v} end

-- metatable helper functions

--- Try getting from nonDefaults, then from defaults. local function getWithDefault(tbl, k)	local value = tbl.nonDefaults[k] if value ~= nil then return value end return tbl.defaults[k] end

--- Main generator function for pairs. local function pairsWithDefaultGenerator(data) local tbl, params, i = unpack(data) i = i + 1 if i > #params then return end local k = params[i] local v = getWithDefault(tbl, k)	data[3] = i	return k, v end

--- Main generator function for ipairs. local function ipairsWithDefaultGenerator(tbl, i)	i = i + 1 local v = getWithDefault(tbl, i)   if v ~= nil then return i, v	end end

local METATABLE = { __name = "ArgsWithDefault", __index = getWithDefault, __newindex = function(self, k, v)		self.nonDefaults[k] = v	end, __pairs = function(self) local paramSet = {} for k, _ in pairs(self.nonDefaults) do			paramSet[k] = true end for k, _ in pairs(self.defaults) do			paramSet[k] = true end local params = {} for k, _ in pairs(paramSet) do			table.insert(params, k)		end return pairsWithDefaultGenerator, {self, params, 0} end, __ipairs = function(self) return ipairsWithDefaultGenerator, self, 0 end, }

--	A table with configurable default values for properties.	@{TemplateData.processArgs} returns objects of this type.	In addition to acting as a regular map-style table, these tables include two	sub-tables that allow access to default value and explicitly-set values, respectively.	@type TableWithDefaults

--	@factory TableWithDefaults	@return {TableWithDefaults} A new table with defaults. -- function p.createObjectWithDefaults -- @export local out = { --			A table of default entries.			Initially, defaults come from the argument configuration, but they can be			changed retroactively after the table has been created.			Not included in `pairs(TableWithDefaults)` or `ipairs(TableWithDefaults)`. defaults={}, --			A table of manually-assigned entries.			Useful for checking whether a property was set explicitly.			(Assinging to the `TableWithDefaults` directly will update this table.)			Not included in `pairs(TableWithDefaults)` or `ipairs(TableWithDefaults)`. nonDefaults={} }	setmetatable(out, METATABLE) return out end --- @type end

local CONFIG_DEFAULTS = {} local CONFIG_TRANSFORMS = {} -- note: self.name is set in `p.wrapConfig`, so it doesn't need a default function CONFIG_DEFAULTS:displayName return self.name end function CONFIG_TRANSFORMS:displayName(value) return tostring(value) end function CONFIG_TRANSFORMS:alias(value) return ensureTable(value) end function CONFIG_DEFAULTS:displayAlias if self.alias then local name = tostring(self.name) if self.displayName ~= name then -- swap name and displayName if displayName is an alias local output = {} for i, v in ipairs(self.alias) do				v = tostring(v) if v == self.displayName then output[i] = name else output[i] = v				end end return output else return self.alias end end end function CONFIG_DEFAULTS:description if self.type == "1" then return "Set to 1 to " .. self.config.trueDescription end end function CONFIG_DEFAULTS:summary if self.description then -- take everything up to (and including) the first period return self.description:match('^.-%.') or self.description end end function CONFIG_DEFAULTS:example if self.type == "1" then return 1 end end function CONFIG_TRANSFORMS:example(value) if type(value) == "table" then return quote(table.concat(value, '", "')) else return quote(value) end end function CONFIG_DEFAULTS:displayDefault if self.default then return quote(self.default) end end function CONFIG_DEFAULTS:displayType if self.type == "1" then return "boolean" end if self.type == "number" then return "number" end return "line" end

local CONFIG_WRAPPER_METATABLE = { __index = function(self, key) -- use value from config if present local value = self.config[key] -- else try to generate a default value if not value then local getter = CONFIG_DEFAULTS[key] if getter then value = getter(self) end end if not value then return nil end -- if value is present (and not false), run transform if needed local transform = CONFIG_TRANSFORMS[key] if transform then value = transform(self, value) end -- memoize and return self[key] = value return value end }

function p.wrapConfig(config, key) local output = {config = config, name = config.name or key} setmetatable(output, CONFIG_WRAPPER_METATABLE) return output end

--- Processes arguments based on given argument configs. -- @param      {table} args The table of arguments to process. -- @param      {ArgumentConfigTable} argConfigs The argument configs to use when processing `args`. -- @param[opt] {table} options Extra options -- @param[opt] {boolean} options.preserveDefaults Set true to treat `args` as a `TableWithDefaults` --             and use its `defaults` and `nonDefaults` to set default/non-default values for --             arguments that use the same name in both `args` and `argConfigs` --             (i.e., this currently does not work with aliases). -- @return     {TableWithDefaults} The processed version of the arguments. function p.processArgs(args, paramConfigs, options) local preserveDefaults = false if type(options) == "table" and options.preserveDefaults and type(args.defaults) == "table" and type(args.nonDefaults) == "table" then preserveDefaults = true end local out = p.createObjectWithDefaults for key, config in pairs(paramConfigs) do		key = config.name or key local value, default if preserveDefaults then value = args.nonDefaults[key] default = args.defaults[key] or config.default else value = args[key] default = config.default end if value == nil and config.alias then for _, alias in ipairs(ensureTable(config.alias)) do				value = args[alias] if value ~= nil then break end end end if config.type == "1" then if value ~= nil then value = value == "1" or value == 1 or value == true end default = default or false elseif config.type == "number" then value = tonumber(value) end out.nonDefaults[key] = value -- note that default values will not be processed by type out.defaults[key] = default end -- note that multiple layers of defaultFrom are not supported for key, config in pairs(paramConfigs) do		key = config.name or key if config.defaultFrom then local to = key local from = config.defaultFrom -- allow defaulting from params that won't appear in output -- use explicit default if param not found out.defaults[to] = out.nonDefaults[from] or out.defaults[from] or args[from] or out.defaults[to] end if config.error and out[key] == nil then local message = config.error if type(message) == "boolean" then message = "Please specify a value for the required argument \"" .. key .. "\"" end error(message, -1) end end return out end

--- Creates and returns template data for display on a page. -- @param      {ArgumentConfigList} argConfigs Argument configs to base the template data on. --             Must use list form in order for parameter order to be predictable. -- @param      {table} options Any other top-level keys to add to the template data. --             Usually used to add `description` and `format`. -- @param[opt] {Frame} frame Current frame. If not supplied, template data will be returned as a raw string, --             which is useful for previewing in console, but will not display properly when output to wiki articles. -- @return     {string} The template data. function p.templateData(paramConfigs, options, frame) local json = require("Module:JSON") local params = {} local paramOrder = {} local output = { params=params, paramOrder=paramOrder }	for key, val in pairs(options) do		output[key] = val end for key, config in ipairs(paramConfigs) do		config = p.wrapConfig(config) params[config.displayName] = { -- allow false to behave the same as nil for all properties aliases=config.displayAlias, label=config.label, description=config.description, type=config.displayType, default=config.displayDefault, example=config.example, auto=config.auto, required=config.status=="required" or nil, suggested=config.status=="suggested" or nil, deprecated=config.status=="deprecated" or nil, }		table.insert(paramOrder, config.displayName) end if frame then return frame:extensionTag("templatedata", json.encode(output)) else return " "..json.encode(output).." " end end

local function addLine(o) -- note: putting divs into code elements is not valid HTML, -- so instead use a span with CSS return o:tag('span'):css('display', 'block') end

function p.syntax(paramConfigs, frame) frame = frame or mw.getCurrentFrame local templateName = frame.args[1] or mw.title.new(frame:getTitle).text local params = {} local maxNameLength = 0 for key, config in ipairs(paramConfigs) do		config = p.wrapConfig(config) local param = { name = config.displayName, nameLength = #config.displayName, summary = config.summary, aliases = config.displayAlias, status = config.status, -- (not currently displayed) }		maxNameLength = math.max(maxNameLength, param.nameLength) table.insert(params, param) end local indent = maxNameLength + 4 -- 1ch from '|' + 3ch from ' = ' local output = mw.html.create('code'):css({		display='inline-block',		['line-height']='1.5',		['white-space']='pre-wrap',		['text-indent']=-indent..'ch',		['padding-left']='calc('..indent..'ch + 4px)', -- 4px from default padding	}) addLine(output):wikitext("") return output end

--- A table (either list or map form) of @{ArgumentConfig} configurations for arguments. -- --  Modules will create such tables and pass them to @{TemplateData.processArgs} -- and @{TemplateData.templateData} to control how arguments are processed and -- what template data gets output, respectively. -- @see @{ArgumentConfigList} and @{ArgumentConfigMap} -- @see @{ArgumentConfig} for details on each argument's configuration -- @type ArgumentConfigTable

--- An @{ArgumentConfigTable} that acts as a list of @{ArgumentConfig}. -- @type ArgumentConfigList

--- An @{ArgumentConfigTable} that acts as a map from argument name keys to @{ArgumentConfig} values. -- @type ArgumentConfigMap

-- Configuration for a single argument. Modules will create tables that match this type for their @{ArgumentConfigTable}s. @type ArgumentConfig

--	Argument name. If not provided,	defaults to the key of this `ArgumentConfig` in the table that contains it.	@property[opt] {string} ArgumentConfig.name	-- --	Display name for documentation.	Typically used to mark one of the aliases (e.g., "1") to use as the main	name in the documentation while using a more descriptive name in code	(e.g., `args.item_name`).	@property[opt] {string] ArgumentConfig.displayName --table} ArgumentConfig.aliases	--

--	Display name to be used in template data.	Has no effect on argument processing.	@property[opt] {string} ArgumentConfig.label	-- --	Type of argument for automatic conversions.	Supported values:	* `"number"`: uses @{tonumber} on the argument value	* `"1"`: converts argument value a boolean based on whether it's the string `"1"`,	 the number `1`, or the boolean `true`.	@property[opt]  {string} ArgumentConfig.type	-- --	Display type to be used in template data.	Has no effect on argument processing.	Overrides `ArgumentConfig.type` in template data.	@property[opt] {string} ArgumentConfig.displayType	-- --	Default value to use for the argument if the user does not provide any	(or if the `type` conversion produces a nil result).	@property[opt] {any} ArgumentConfig.default	-- --	Display for default value to be used in template data.	Has no effect on argument processing.	Overrides `ArgumentConfig.default` in template data.	@property[opt] {any} ArgumentConfig.displayDefault	-- --boolean} ArgumentConfig.error	-- --	Defaults to "optional".	Set to "required", "suggested", or "deprecated" to set corresponding flags	in the template data.	Has no effect on argument processing.	@property[opt] {string} ArgumentConfig.status	-- --	Default value used in the Visual Editor.	Has no effect on argument processing.	@property[opt] {any} ArgumentConfig.auto	-- --	A description of the argument's purpose.	Has no effect on argument processing.	@property[opt] {string} ArgumentConfig.description	-- --	Only used for arguments with a `type` of `"1"`.	Describes what happens when the argument value is set to 1.	Used to reduce boilerplate in descriptions for boolean args.	@usage "output text without a link"	@property[opt] {string} ArgumentConfig.trueDescription	-- --table} ArgumentConfig.example	--

return p