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

This module takes care of parsing and validating template input, allowing module developers to focus on core logic. It aims to achieve the same goal as Wikipedia's Module:Arguments but with a different approach:

  • Validation is based on a schema that is also used to auto-generate documentation. This guarantees that the documentation is up to date with the validation code.
  • No implicit behaviour. If you want to trim a parameter, you specify trim = true. If you want to treat empty strings as nil, you specify nilIfEmpty = true. Module:Arguments does several things "automagically" by default and lacks clarity as a result.
  • No frame handling. It's up to the caller to consolidate frame.args and frame:getParent().args if needed. This can be done with utilsTable.merge. Most modules typically need only one frame anyway.

Lua error in package.lua at line 80: module 'Module:UtilsArg/Validate' not found.


local p = {}
local h = {}

local i18n = require("Module:I18n")
local s = i18n.getString
local utilsPackage = require("Module:UtilsPackage")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local utilsValidate = require("Module:UtilsValidate")

p = utilsPackage.submodules({
	"Module:UtilsArg/Validate"
}, {
	"Validation"
})

local VALIDATORS = {"required", "enum", "deprecated"}

function p.parse(frameArgs, templateSpec)
	local args = {}
	local err = {
		args = {},
		categories = {},
	}
	for k, v in pairs(templateSpec.params) do
		args[v.name or k] = h.parseArg(frameArgs[k], v)
	end
	local variadicParam = templateSpec.params["..."]
	if variadicParam then
		args[variadicParam.name] = {}
		local i = #templateSpec.params + 1
		while frameArgs[i] do
			local varArg = h.parseArg(frameArgs[i], variadicParam)
			if varArg then
				table.insert(args[variadicParam.name], varArg)
			end
			i = i + 1
		end
	end
	for k, v in pairs(templateSpec.params) do
		local argErrors, errorCategories = h.validate(args[v.name or k], v, v.name or k, args)
		if #argErrors > 0 then
			err.args[v.name or k] = utilsTable.concat(err.args[v.name or k] or {}, argErrors)
		end
		err.categories = utilsTable.concat(err.categories, errorCategories)
	end
	if #err.categories == 0 then
		err = nil
	end
	return args, err
end

function h.parseArg(arg, param)
	if arg and param.trim then
		arg = utilsString.trim(arg)
	end
	if arg and param.split then
		local pattern = type(param.split) == "string" and param.split or nil
		arg = utilsString.split(arg, pattern)
	end
	if param.nilIfEmpty then
		arg = utilsString.nilIfEmpty(arg)
	end
	return arg
end

function h.validate(arg, param, paramName, args)
	local errors = {}
	local categories = {}
	for i, validator in ipairs(VALIDATORS) do
		local validatorData = param[validator]
		if validator == "enum" and param.enum then
			local enum = param.enum
			if type(param.enum) == "function" then
				local dependency = args[param.enumDependsOn]
				enum = dependency and enum(dependency)
			end
			validatorData = enum
		end
		local errorMessages, cat
		if validatorData ~= nil then 
			errorMessages, cat = h[validator](validatorData, arg, paramName)
		end
		for _, err in ipairs(errorMessages or {}) do
			table.insert(errors, {
				msg = err,
				category = cat
			})
			table.insert(categories, cat)
		end
	end
	return errors, categories
end
function h.required(required, value, name)
	if not required then
		return
	end
	local err = utilsValidate.required(value, name)
	if err then
		return {err}, s("cat.invalidArgs")
	end
end
function h.enum(enum, value, name)
	if not enum then
		return
	end
	local err = utilsValidate._enum(enum)(value, name)
	if err then
		return err, s("cat.invalidArgs")
	end
end
function h.deprecated(deprecated, value, name)
	if not deprecated then
		return
	end
	local err = utilsValidate.deprecated(value, name)
	if err then
		return {err}, s("cat.deprecatedArgs")
	end
end

i18n.loadStrings({
	en = {
		cat = {
			invalidArgs = "Category:Pages with Invalid Arguments",
			deprecatedArgs = "Category:Pages with Deprecated Arguments",
		},
	},
})

p.Schemas = {
	parse = {
		frameArgs = {
			type = "any",
			required = true,
			desc = "Table of arguments obtained from {{Scribunto Manual|lib=Frame object|frame object}}.",
		},
		templateSpec = {
			type = "any",
			required = true,
			desc = "[[Module:Documentation#Templates|Template documentation object]].",
		}
	}
}

p.Documentation = {
	parse = {
		desc = "This function validates template input and parses it into a table for use in the rest of the module.",
		params = {"frameArgs", "templateSpec"},
		returns = {
			"A table of arguments parsed from the template input.",
			"A table of validation errors, or nil if there are none. The error messages are also logged using {{Scribunto Manual|lib=mw.addWarning}}.",
		},
		cases = {
			outputOnly = true,
			{
				desc = "Positional arguments are assigned to their names.",
				snippet = "PositionalAndNamedArgs",
				expect = {
					{
						game = "OoT",
						page = "Boss Key",
					},
					nil
				}
			},
			{
				desc = "Special parameter <code>...</code> is used to parse an array of trailing template arguments",
				snippet = "TrailingArgs",
				expect = {
					{
						games = {"OoT", "MM", "TWW", "TP"}
					},
					nil
				}
			},
			{
				desc = "<code>...</code> used with other positional args",
				snippet = "TrailingArgsWithPositionalArgs",
				expect = {
					{
						foo = "foo",
						bar = "bar",
						games = {"OoT", "MM", "TWW", "TP"},
					},
					nil
				}
			},
			{
				desc = "Validation of required arguments.",
				snippet = "RequiredArgs",
				expect = {
					{
						page = "Boss Key"
					},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							game = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>game</code> is required but is <code>nil</code>.",
								},
							},
						},
					},
				}
			},
			{
				desc = "Validation of deprecated arguments.",
				snippet = "Deprecated",
				expect = {
					{ oldArg = "foo" },
					{
						categories = {"Category:Pages with Deprecated Arguments"},
						args = {
							oldArg = {
								{
									category = "Category:Pages with Deprecated Arguments",
									msg = "<code>oldArg</code> is deprecated but has value <code>foo</code>.",
								},
							},
						},
					},
				}
			},
			{
				desc = "<code>trim</code> can be set on a parameter so that [[Module:UtilsString#trim|utilsString.trim]] is called for the argument.",
				snippet = "Trim",
				expect = {{ someParam = "foo" }}
			},
			{
				desc = "<code>nilIfEmpty</code> can be set on a parameter so that [[Module:UtilsString#nilIfEmpty|utilsString.nilIfEmpty]] is called for the argument.",
				snippet = "NilIfEmpty",
				expect = {{}, nil}
			},
			{
				desc = "<code>split</code> can be set on a parameter so that [[Module:UtilsString#split|utilsString.split]] is called for the argument.",
				snippet = "Split",
				expect = {
					{ foo = {"a", "b", "c"} },
				},
			},
			{
				desc = "<code>split</code> using a custom splitting pattern",
				snippet = "SplitPattern",
				expect = {
					{ foo = {"a", "b", "c"} },
				},
			},
			{
				desc = "If <code>nilIfEmpty</code> and <code>required</code> are set, then the argument is invalid if it is an empty string.",
				snippet = "NilIfEmptyWithRequiredArgs",
				expect = {
					{},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							game = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>game</code> is required but is <code>nil</code>.",
								},
							},
						},
					},
				},
			},
			{
				desc = "If <code>trim</code>, <code>nilIfEmpty</code>, and <code>required</code> are set, then the argument is invalid if it is a blank string.",
				snippet = "TrimNilIfEmptyRequired",
				expect = {
					{},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							game = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>game</code> is required but is <code>nil</code>.",
								},
							},
						},
					},
				},
			},
			{
				desc = "<code>enum</code> validation.",
				snippet = "Enum",
				expect = {
					{
						triforce2 = "Limpah",
						game = "ALttZ",
						triforce1 = "Kooloo",
					},
					{
						categories = {
							"Category:Pages with Invalid Arguments",
							"Category:Pages with Invalid Arguments",
							"Category:Pages with Invalid Arguments",
						},
						args = {
							triforce2 = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>triforce2</code> has unexpected value <code>Limpah</code>. For a list of accepted values, refer to [[Triforce]].",
								},
							},
							game = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>game</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
								},
							},
							triforce1 = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = '<code>triforce1</code> has unexpected value <code>Kooloo</code>. The accepted values are: <code>{"Courage", "Power", "Wisdom"}</code>',
								},
							},
						},
					},
				},
			},
			{
				desc = "<code>split</code> is used to parse comma-separated strings as arrays. Each array item can be validated against an <code>enum</code>.",
				snippet = "SplitEnum",
				expect = {
					{
						games = {"OoT", "fakeGame", "BotW"},
					},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							games = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>games[2]</code> has unexpected value <code>fakeGame</code>. For a list of accepted values, refer to [[Data:Franchise]].",
								}
							}
						}
					}
				}
			},
			{
				desc = "<code>enum</code> can be written as a function, when the list of acceptable values depends on the value of another argument.",
				snippet = "EnumDependsOn",
				expect = {
					{
						term = "Dinolfos",
						game = "TP"
					},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							term = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = '<code>term</code> has unexpected value <code>Dinolfos</code>. The accepted values are: <code>{"Dynalfos"}</code>',
								},
							},
						},
					},
				},
			},
			{
				desc = "If <code>enumDependsOn</code> refers to a required parameter, then <code>enum</code> is not evaluated when that parameter is nil.",
				snippet = "EnumDependsOnNil",
				expect = {
					{ term = "Dinolfos" },
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							game = {
								{
									category = "Category:Pages with Invalid Arguments",
						    		msg = "<code>game</code> is required but is <code>nil</code>.",
								}
							},
						},
					},
				},
			},
			{
				desc = "Altogether now",
				snippet = "TermStorePass",
				expect = {
					{
						term = "Dinolfos",
						games = {"OoT", "MM"},
					},
					nil
				}
			},
			{
				snippet = "TermStoreFail",
				expect = {
					{
						plural = "true",
						games = {"YY", "ZZ"},
					},
					{
						categories = {
							"Category:Pages with Invalid Arguments",
							"Category:Pages with Deprecated Arguments",
							"Category:Pages with Invalid Arguments",
							"Category:Pages with Invalid Arguments",
						},
						args = {
							term = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>term</code> is required but is <code>nil</code>.",
								},
							},
							games = {
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>games[1]</code> has unexpected value <code>YY</code>. For a list of accepted values, refer to [[Data:Franchise]]."
								},
								{
									category = "Category:Pages with Invalid Arguments",
									msg = "<code>games[2]</code> has unexpected value <code>ZZ</code>. For a list of accepted values, refer to [[Data:Franchise]]."
								},
							},
							plural = {
								{
									category = "Category:Pages with Deprecated Arguments",
									msg = "<code>plural</code> is deprecated but has value <code>true</code>.",
								},
							},
						},
					},
				},
			},
			{
				desc = "<code>trim</code>, <code>nilIfEmpty</code>, and validators such as <code>enum</code> are applied to individual trailing arguments",
				snippet = "TrailingArgsStringTrimNilIfEmptyEnum",
				expect = {
					{
						games = {"OoT", "MM", "ALttZ"},
					},
					{
						categories = {"Category:Pages with Invalid Arguments"},
						args = {
							games = {
							{
								category = "Category:Pages with Invalid Arguments",
								msg = "<code>games[3]</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
							}
						},
						},
					},
				},
			},
		},
	},
}

return p
Advertisement