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.
This module exports the following functions.

Contents

parse

parse(frameArgs, templateSpec)

This function validates template input and parses it into a table for use in the rest of the module.

Parameters
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 mw.addWarning.
Examples
InputOutputStatus
Positional arguments are assigned to their names.
local args = {"OoT", page = "Boss Key"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "game",
      desc = "A game",
    },
    page = {
      desc = "A page on the wiki",
    },  
  }
})
{
  page = "Boss Key",
  game = "OoT",
}
Green check.svg
nil
Green check.svg
Special parameter ... is used to parse an array of trailing template arguments
local args = {"OoT", "MM", "TWW", "TP"}
return utilsArg.parse(args, {
  params = {
    ["..."] = {
      name = "games",
    },
  },
})
{
  games = {"OoT", "MM", "TWW", "TP"},
}
Green check.svg
nil
Green check.svg
... used with other positional args
local args = {"foo", "bar", "OoT", "MM", "TWW", "TP"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "foo"
    },
    [2] = {
      name = "bar",
    },
    ["..."] = {
      name = "games",
    },
  },
})
{
  foo = "foo",
  bar = "bar",
  games = {"OoT", "MM", "TWW", "TP"},
}
Green check.svg
nil
Green check.svg
Validation of required arguments.
local args = {nil, nil, "Baz"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "foo",
      required = true,
    },
    [2] = {
      name = "bar",
      required = "Category:Custom Category Name",
    },
    [3] = {
      name = "baz",
      required = true,
    },
  }
})
{ baz = "Baz" }
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
    "Category:Custom Category Name",
  },
  args = {
    bar = {
      {
        category = "Category:Custom Category Name",
        msg = "<code>bar</code> parameter is required.",
      },
    },
    foo = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>foo</code> parameter is required.",
      },
    },
  },
}
Green check.svg
Validation of deprecated parameters.
local args = {
  oldArg = "foo",
  oldArg2 = "bar",
}
return utilsArg.parse(args, {
  params = {
    oldArg = {
      deprecated = true
    },
    oldArg2 = {
      deprecated = "Category:Custom Deprecation Category",
    },
  }
})
{
  oldArg = "foo",
  oldArg2 = "bar",
}
Green check.svg
{
  categories = {
    "Category:Custom Deprecation Category",
    "Category:Pages using deprecated parameters in template calls",
  },
  args = {
    oldArg = {
      {
        category = "Category:Pages using deprecated parameters in template calls",
        msg = "<code>oldArg</code> is deprecated but has value <code>foo</code>.",
      },
    },
    oldArg2 = {
      {
        category = "Category:Custom Deprecation Category",
        msg = "<code>oldArg2</code> is deprecated but has value <code>bar</code>.",
      },
    },
  },
}
Green check.svg
Using an unknown parameter counts as an error.
local args = {
  foo = "bar"
}
return utilsArg.parse(args, {
  params = {} -- template has no args defined and yet "foo" is used as one
})
{}
Green check.svg
{
  categories = {
    "Category:Pages using unknown parameters in template calls",
  },
  args = {
    foo = {
      {
        message = "No such parameter <code>foo</code> is defined for this template. Value: <code>bar</code>",
        category = "Category:Pages using unknown parameters in template calls",
      },
    },
  },
}
Green check.svg
Can parse numbers
local args = {
  foo = "9000",
}
return utilsArg.parse(args, {
  params = {
    foo = {
      type = "number"
    }
  }
})
{ foo = 9000 }
Green check.svg
nil
Green check.svg
Returns an error if a non-number is passed when a number is expected.
local args = {
  foo = "notANumber",
}
return utilsArg.parse(args, {
  params = {
    foo = {
      type = "number"
    }
  }
})
{ foo = "notANumber" }
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    foo = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = '<code>foo</code> is expected to be a number but was: <code>"notANumber"</code>',
      },
    },
  },
}
Green check.svg
Default values
local args = {[3] = "", [4] = ""}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "someParamWithDefault",
      type = "string",
      default = "foo",
    },
    [2] = {
      name = "someParamWithDefaultNumber",
      type = "number",
      default = 1,
    },
    [3] = {
      name = "param3",
      type = "string",
      default = "bar",
      nilIfEmpty = true,
    },
    [4] = {
      name = "param4",
      type = "string",
      default = "baz",
    }
  }
})
{
  param4 = "",
  someParamWithDefaultNumber = 1,
  someParamWithDefault = "foo",
  param3 = "bar",
}
Green check.svg
nil
Green check.svg
trim can be set on a parameter so that utilsString.trim is called for the argument.
local args = {" foo   \n"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "someParam",
      trim = true,
    },
  }
})
{ someParam = "foo" }
Green check.svg
nil
Green check.svg
nilIfEmpty can be set on a parameter so that utilsString.nilIfEmpty is called for the argument.
local args = {
  foo = ""
}
return utilsArg.parse(args, {
  params = {
    foo = {
      nilIfEmpty = true,
    },
  }
})
{}
Green check.svg
nil
Green check.svg
split can be set on a parameter so that utilsString.split is called for the argument.
local args = {
  foo = "  a, b  , c"
}
return utilsArg.parse(args, {
  params = {
    foo = {
      trim = true,
      split = true,
    }
  }
})
{
  foo = {"a", "b", "c"},
}
Green check.svg
nil
Green check.svg
split using a custom splitting pattern
local args = {
  foo = "abc"
}
return utilsArg.parse(args, {
  params = {
    foo = {
      split = "",
    }
  }
})
{
  foo = {"a", "b", "c"},
}
Green check.svg
nil
Green check.svg
If nilIfEmpty and required are set, then the argument is invalid if it is an empty string.
local args = {""}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "game",
      nilIfEmpty = true,
      required = true,
      desc = "A game",
    },
  }
})
{}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    game = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>game</code> parameter is required.",
      },
    },
  },
}
Green check.svg
If trim, nilIfEmpty, and required are set, then the argument is invalid if it is a blank string.
local args = {"  \n"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "game",
      desc = "A game",
      trim = true,
      nilIfEmpty = true,
      required = true,
    },
  }
})
{}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    game = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>game</code> parameter is required.",
      },
    },
  },
}
Green check.svg
enum validation.
local args = {"Kooloo", "Limpah", "ALttZ"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "triforce1",
      enum = {"Courage", "Power", "Wisdom"}
    },
    [2] = {
      name = "triforce2",
      enum = {"Courage", "Power", "Wisdom", reference = "[[Triforce]]"},
    },
    [3] = {
      name = "game",
      enum = Franchise.enum(), -- from [[Module:Franchise]]
    }
  }
})
{
  triforce2 = "Limpah",
  game = "ALttZ",
  triforce1 = "Kooloo",
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
    "Category:Pages using invalid arguments in template calls",
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    triforce2 = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>triforce2</code> has unexpected value <code>Limpah</code>. For a list of accepted values, refer to [[Triforce]].",
      },
    },
    game = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>game</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
    },
    triforce1 = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = '<code>triforce1</code> has unexpected value <code>Kooloo</code>. The accepted values are: <code>{"Courage", "Power", "Wisdom"}</code>',
      },
    },
  },
}
Green check.svg
split is used to parse comma-separated strings as arrays. Each array item can be validated against an enum.
local args = {
  games = "OoT, fakeGame, BotW",
}
return utilsArg.parse(args, {
  params = {
    games = {
      split = true,
      enum = Franchise.enum(),
    },
  },
})
{
  games = {"OoT", "fakeGame", "BotW"},
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    games = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>games[2]</code> has unexpected value <code>fakeGame</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
    },
  },
}
Green check.svg
sortAndRemoveDuplicates can be used alongside split and enum. Entries are sorted to match the sort order of the enum.
local args = {
  games = "BotW, BotW, fakeGame, OoT, OoT",
}
return utilsArg.parse(args, {
  params = {
    games = {
      split = true,
      enum = Franchise.enum(),
      sortAndRemoveDuplicates = true,
    },
  },
})
{
  games = {"OoT", "BotW"},
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    games = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>games[3]</code> has unexpected value <code>fakeGame</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
    },
  },
}
enum can be written as a function, when the list of acceptable values depends on the value of another argument.
local validTermsForGame = {
  OoT = {"Dinolfos"},
  TP = {"Dynalfos"},
}
local args = {"TP", "Dinolfos"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "game",
      required = true,
    },
    [2] = {
      name = "term",
      enumDependsOn = "game",
      enum = function(game)
        return validTermsForGame[game]
      end,
    }
  }
})
{
  term = "Dinolfos",
  game = "TP",
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    term = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = '<code>term</code> has unexpected value <code>Dinolfos</code>. The accepted values are: <code>{"Dynalfos"}</code>',
      },
    },
  },
}
Green check.svg
If enumDependsOn refers to a required parameter, then enum is not evaluated when that parameter is nil.
local validTermsForGame = {
  OoT = {"Dinolfos"},
  TP = {"Dynalfos"},
}
local args = {nil, "Dinolfos"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "game",
      required = true,
    },
    [2] = {
      name = "term",
      enumDependsOn = "game",
      enum = function(game)
        return validTermsForGame[game]
      end,
    }
  }
})
{ term = "Dinolfos" }
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    game = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>game</code> parameter is required.",
      },
    },
  },
}
Green check.svg
Altogether now
local args = {"Dinolfos", games = "OoT, MM", plural = nil}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "term",
      nilIfEmpty = true,
      required = true,
    },
    games = {
      split = true,
      enum = Franchise.enum(),
    },
    plural = {
      deprecated = true,
    }
  }
})
{
  term = "Dinolfos",
  games = {"OoT", "MM"},
}
Green check.svg
nil
Green check.svg
local args = {"", games = "YY, ZZ", plural = "true"}
return utilsArg.parse(args, {
  params = {
    [1] = {
      name = "term",
      nilIfEmpty = true,
      required = true,
    },
    games = {
      split = true,
      enum = Franchise.enum(),
    },
    plural = {
      deprecated = true,
    },
  }
})
{
  plural = "true",
  games = {"YY", "ZZ"},
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
    "Category:Pages using deprecated parameters in template calls",
    "Category:Pages using invalid arguments in template calls",
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    plural = {
      {
        category = "Category:Pages using deprecated parameters in template calls",
        msg = "<code>plural</code> is deprecated but has value <code>true</code>.",
      },
    },
    games = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>games[1]</code> has unexpected value <code>YY</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>games[2]</code> has unexpected value <code>ZZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
    },
    term = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>term</code> parameter is required.",
      },
    },
  },
}
Green check.svg
trim, nilIfEmpty, and validators such as enum are applied to individual trailing arguments
local args = {"\n  OoT", "", "MM ", "ALttZ"}
return utilsArg.parse(args, {
  params = {
    ["..."] = {
      name = "games",
      trim = true,
      nilIfEmpty = true,
      enum = Franchise.enum()
    },
  }
})
{
  games = {"OoT", "MM", "ALttZ"},
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    games = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>games[3]</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
      },
    },
  },
}
Green check.svg
repeatedGroup
local args = {
  tab1 = "Tab 1",
  content1 = "Content 1",
  
  tab2= "Tab 2",
  content2 = "Content 2",
  
  -- missing tab3, content3
  
  tab4 = "Tab 4",
  -- missing content4
  
  --missing tab5
  content5 = "Content 5",
}
return utilsArg.parse(args, {
  params = {
    ["tab"] = { required = true },
    ["content"] = { required = true },
  },
  repeatedGroup = {
    name = "tabs",
    params = {"tab", "content"},
  },
})
{
  tabs = {
    {
      tab = "Tab 1",
      content = "Content 1",
    },
    {
      tab = "Tab 2",
      content = "Content 2",
    },
    { tab = "Tab 4" },
    { content = "Content 5" },
  },
}
Green check.svg
{
  categories = {
    "Category:Pages using invalid arguments in template calls",
    "Category:Pages using invalid arguments in template calls",
  },
  args = {
    tab4 = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>tab4</code> parameter is required.",
      },
    },
    content3 = {
      {
        category = "Category:Pages using invalid arguments in template calls",
        msg = "<code>content3</code> parameter is required.",
      },
    },
  },
}
Green check.svg

local p = {}
local h = {}
local validators = {}

local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local utilsVar = require("Module:UtilsVar")

h.templatePage = mw.getCurrentFrame():getParent():getTitle()
h.instanceCounter = utilsVar.counter("instanceCounter"..h.templatePage)
h.instanceCounter.increment()

function p.parse(frameArgs, templateSpec)
	local args = {}
	local unknownParams = utilsTable.clone(frameArgs)
	
	local repeatedParams = templateSpec.repeatedGroup and templateSpec.repeatedGroup.params or {}
	local repeatedParamsMap = utilsTable.invert(repeatedParams)
	local isRepeated = h.isRepeated(repeatedParamsMap)
	
	local err = {
		args = {},
		categories = {},
	}
	-- Parse ordinary args
	for k, v in pairs(templateSpec.params) do
		if not repeatedParamsMap[k] then
			args[v.name or k] = h.parseArg(frameArgs[k], v)
			unknownParams[k] = nil
		end
	end
	
	-- Parse repeatedGroup args
	local repeated = templateSpec.repeatedGroup and templateSpec.repeatedGroup.name
	if repeated then
		for k, v in pairs(unknownParams) do
		local isRepeated, index, param = isRepeated(k)
			if isRepeated then
				local paramSpec = templateSpec.params[param]
				args[repeated] = args[repeated] or {}
				args[repeated][index] = args[repeated][index] or {}
				utilsTable.merge(args[repeated][index], {
					[param] = h.parseArg(v, paramSpec)
				})
				unknownParams[k] = nil
			end
		end
		args[repeated] = utilsTable.compact(args[repeated]) -- in case a number is accidentally skipped for a whole "row"
	end
	
	-- Parse variadic args
	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
			unknownParams[i] = nil
			i = i + 1
		end
	end
	
	-- Validate
	for paramKey, paramSpec in pairs(templateSpec.params) do
		if not repeatedParamsMap[paramKey] then -- repeated args are an edge case for validation and need to be handled separately
			local paramName = paramSpec.name or paramKey
			local paramValue = args[paramName]
			h.validateAndAddErrors(err, args, paramSpec, paramName, paramValue)
		else
			for i in ipairs(args[repeated]) do
				local paramName = paramKey..i -- no need to check paramSpec.name as repeated params should always be named arguments
				local paramValue = args[repeated][i][paramKey]
				h.validateAndAddErrors(err, args, paramSpec, paramName, paramValue)
			end
		end
	end
	
	-- Do any post processing such as sorting and removing duplicates
	for k, v in pairs(templateSpec.params) do
		local arg = args[v.name or k]
		if arg and v.enum and v.split and v.sortAndRemoveDuplicates then
			args[v.name or k] = utilsTable.intersection(v.enum, arg)
		end
	end
	
	-- Handle any args left that don't have corresponding params defined
	for k, v in pairs(unknownParams) do
		-- value is not strictly necessary but can make it easier to search for the invalid usage when the template is used several times on a page
		local errMsg = string.format("No such parameter <code>%s</code> is defined for this template. Value: <code>%s</code>", k, v)
		h.warn(errMsg)
		err.args[k] = {{
			category = "Category:Pages using unknown parameters in template calls",
			message = errMsg
		}}
		err.categories = utilsTable.concat(err.categories, "Category:Pages using unknown parameters in template calls")
	end
	if #err.categories == 0 then
		err = nil
	end
	if err and mw.title.getCurrentTitle().nsText == "User" then
		err.categories = {}
	end
	return args, err
end

function h.warn(errMessage)
	local fullMessage = string.format("Misuse of [[%s]] at instance %s: %s", h.templatePage, h.instanceCounter.value(), errMessage)
	mw.addWarning(fullMessage)
	-- We also log the message as mw.addWarning currently doesn't work if the editor has enabled the preference "Show previews without reloading the page".
	-- Unfortunately neither mw.addWarning nor mw.log work with the visual editor (even in source mode).
	-- An alternative would be to throw an actual script error as most wikis do.
	-- However, full errors degrade the reader experience. 
	-- Many of our validation errors are not so critical that readers should know that they even exist.
	mw.log(fullMessage)
end

function h.isRepeated(repeatedParamsMap)
	-- @param param a template parameter e.g. "tab1"
	-- @return boolean indicating whether parameter is part of a repeated group
	-- @return number index of the repition, e.g. 1 for "tab1", 2 for "tab2", etc.
	-- @return name of the parameter without the index, e.g. "tab" for "tab1", "tab2", etc.
	return function(param)
		if type(param) == "number" then
			return false
		end
		local name = utilsString.trim(param, "0-9")
		local index = tonumber(string.sub(param, #name + 1))
		if not repeatedParamsMap[name] or not index then
			return false
		end
		return true, index, name
	end
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 arg and param.nilIfEmpty then
		arg = utilsString.nilIfEmpty(arg)
	end
	arg = arg or param.default
	if param.type == "number" then
		local num = tonumber(arg)
		if num then
			arg = num
		end
	end
	return arg
end

function h.validateAndAddErrors(err, args, paramSpec, paramName, paramValue)
	local argErrors, errorCategories = h.validate(paramValue, paramSpec, paramName, args)
	if #argErrors > 0 then
		err.args[paramName] = utilsTable.concat(err.args[paramName] or {}, argErrors)
	end
	err.categories = utilsTable.concat(err.categories, errorCategories)
end

function h.validate(arg, param, paramName, args)
	local errors = {}
	local categories = {}
	local validatorNames = {"required", "deprecated", "enum", "type"} -- do validation in this order
	for i, validatorName in ipairs(validatorNames) do
		local validatorData = param[validatorName]
		if validatorName == "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
		if validatorName == "type" and validatorData and arg == nil then
			validatorData = nil -- we shouldn't do type validation if arg is nil and not required
		end
		local errorMessages, defaultCat
		if validatorData ~= nil then 
			errorMessages, defaultCat = validators[validatorName](validatorData, arg, paramName)
		end
		-- Here we allow custom categories so that editors can do targeted maintenance on a specific template parameter
		-- For example, we deprecate a parameter and use a custom category to clean up all the pages that use that parameter
		local cat = defaultCat
		if (validatorName == "required" or validatorName == "deprecated") and type(validatorData) == "string" then
			cat = validatorData
		end
		if errorMessages then
			for _, err in ipairs(errorMessages) do
				table.insert(errors, {
					msg = err,
					category = cat
				})
				table.insert(categories, cat)
			end
			break -- run only one validator for now as there isn't yet any situtation where it makes sense to run several
		end
	end
	return errors, categories
end

function validators.required(required, value, name)
	if not required then
		return
	end
	if value == nil then
		local err = string.format("<code>%s</code> parameter is required.", name)
		h.warn(err)
		return {err}, "Category:Pages using invalid arguments in template calls"
	end
end
function validators.enum(enum, value, name)
	if not enum then
		return
	end
	-- Sometimes `value` is an "array", sometimes it's just a primitive value
	-- We can simplify the code by folding the latter case into the former
	-- i.e. making `value` always an array
	local isMultiValue = type(value) == "table"
	if not isMultiValue then
		value = { value }
	end
	
	local errors = {}
	for k, v in ipairs(value) do
		if not utilsTable.keyOf(enum, v) then
			local path = name
			if isMultiValue then
				path = path .. string.format("[%s]", k)
			end
			local msg
			if enum.reference then
				msg = string.format("<code>%s</code> has unexpected value <code>%s</code>. For a list of accepted values, refer to %s.", path, v, enum.reference)
			else
				local acceptedValues = utilsTable.print(enum, true)
				msg = string.format("<code>%s</code> has unexpected value <code>%s</code>. The accepted values are: <code>%s</code>", path, v, acceptedValues)
			end
			table.insert(errors, msg)
			h.warn(msg)
		end
	end
	return errors, "Category:Pages using invalid arguments in template calls"
end
function validators.deprecated(deprecated, value, name)
	if not deprecated then
		return
	end
	if value ~= nil then
		local err = string.format("<code>%s</code> is deprecated but has value <code>%s</code>.", name, value)
		h.warn(err)
		return {err}, "Category:Pages using deprecated parameters in template calls"
	end
end
function validators.type(expectedType, value, name)
	if expectedType == "number" and tonumber(value) == nil then
		local msg = "<code>" .. name .. "</code> is expected to be a number but was: <code>" .. utilsTable.print(value) .. "</code>"
		h.warn(msg)
		return {msg}, "Category:Pages using invalid arguments in template calls"
	end
end

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 = {
					{
						baz = "Baz"
					},
					{
						categories = {
							"Category:Pages using invalid arguments in template calls",
							"Category:Custom Category Name",
						},
						args = {
							bar = {
								{
									category = "Category:Custom Category Name",
									msg = "<code>bar</code> parameter is required.",
								},
							},
							foo = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>foo</code> parameter is required.",
								},
							},
						},
					},
				},
			},
			{
				desc = "Validation of deprecated parameters.",
				snippet = "Deprecated",
				expect = {
					{ oldArg = "foo", oldArg2 = "bar" },
					{
						categories = {"Category:Custom Deprecation Category", "Category:Pages using deprecated parameters in template calls"},
						args = {
							oldArg = {
								{
									category = "Category:Pages using deprecated parameters in template calls",
									msg = "<code>oldArg</code> is deprecated but has value <code>foo</code>.",
								},
							},
							oldArg2 = {
								{
									category = "Category:Custom Deprecation Category",
									msg = "<code>oldArg2</code> is deprecated but has value <code>bar</code>.",
								},
							},
						},
					},
				}
			},
			{
				desc = "Using an unknown parameter counts as an error.",
				snippet = "Unkown",
				expect = {
					{}, 
					{
						categories = {"Category:Pages using unknown parameters in template calls"},
						args = {
							foo = {
								{
									message = "No such parameter <code>foo</code> is defined for this template. Value: <code>bar</code>",
									category = "Category:Pages using unknown parameters in template calls",
								},
							},
						},
					}
				},
			},
			{
				desc = "Can parse numbers",
				snippet = "Number",
				expect = {
					{ foo = 9000 },
					nil
				}
			},
			{
				desc = "Returns an error if a non-number is passed when a number is expected.",
				snippet = "InvalidNumber",
				expect = {
					{ foo = "notANumber" },
					{
						categories = {"Category:Pages using invalid arguments in template calls"},
						args = {
							foo = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = '<code>foo</code> is expected to be a number but was: <code>"notANumber"</code>',
								},
							},
						},
					},
				},
			},
			{
				desc = "Default values",
				snippet = "Default",
				expect = {
					{ 
						someParamWithDefault = "foo", 
						someParamWithDefaultNumber = 1, 
						param3 = "bar", 
						param4 = ""
					}
				}
			},
			{
				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 using invalid arguments in template calls"},
						args = {
							game = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>game</code> parameter is required.",
								},
							},
						},
					},
				},
			},
			{
				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 using invalid arguments in template calls"},
						args = {
							game = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>game</code> parameter is required.",
								},
							},
						},
					},
				},
			},
			{
				desc = "<code>enum</code> validation.",
				snippet = "Enum",
				expect = {
					{
						triforce2 = "Limpah",
						game = "ALttZ",
						triforce1 = "Kooloo",
					},
					{
						categories = {
							"Category:Pages using invalid arguments in template calls",
							"Category:Pages using invalid arguments in template calls",
							"Category:Pages using invalid arguments in template calls",
						},
						args = {
							triforce2 = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>triforce2</code> has unexpected value <code>Limpah</code>. For a list of accepted values, refer to [[Triforce]].",
								},
							},
							game = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>game</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
								},
							},
							triforce1 = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									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 using invalid arguments in template calls"},
						args = {
							games = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>games[2]</code> has unexpected value <code>fakeGame</code>. For a list of accepted values, refer to [[Data:Franchise]].",
								}
							}
						}
					}
				}
			},
			{
				desc = "<code>sortAndRemoveDuplicates</code> can be used alongside <code>split</code> and <code>enum</code>. Entries are sorted to match the sort order of the enum.",
				snippet = "SplitEnumSortAndRemoveDuplicates",
				expect = {
					{ games = {"OoT", "BotW"} },
					nil
				},
			},
			{
				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 using invalid arguments in template calls"},
						args = {
							term = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									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 using invalid arguments in template calls"},
						args = {
							game = {
								{
									category = "Category:Pages using invalid arguments in template calls",
						    		msg = "<code>game</code> parameter is required.",
								}
							},
						},
					},
				},
			},
			{
				desc = "Altogether now",
				snippet = "TermStorePass",
				expect = {
					{
						term = "Dinolfos",
						games = {"OoT", "MM"},
					},
					nil
				}
			},
			{
				snippet = "TermStoreFail",
				expect = {
					{
						plural = "true",
						games = {"YY", "ZZ"},
					},
					{
						categories = {
							"Category:Pages using invalid arguments in template calls",
							"Category:Pages using deprecated parameters in template calls",
							"Category:Pages using invalid arguments in template calls",
							"Category:Pages using invalid arguments in template calls",
						},
						args = {
							term = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>term</code> parameter is required.",
								},
							},
							games = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>games[1]</code> has unexpected value <code>YY</code>. For a list of accepted values, refer to [[Data:Franchise]]."
								},
								{
									category = "Category:Pages using invalid arguments in template calls",
									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 using deprecated parameters in template calls",
									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 using invalid arguments in template calls"},
						args = {
							games = {
							{
								category = "Category:Pages using invalid arguments in template calls",
								msg = "<code>games[3]</code> has unexpected value <code>ALttZ</code>. For a list of accepted values, refer to [[Data:Franchise]].",
							}
						},
						},
					},
				},
			},
			{
				desc = "repeatedGroup",
				snippet = "RepeatedGroup",
				expect = {
					{
						tabs = {
							{
								tab = "Tab 1",
								content = "Content 1",
							},
							{
								tab = "Tab 2",
								content = "Content 2",
							},
							{ tab = "Tab 4" },
							{ content = "Content 5" },
						}
					},
					{
						categories = {
							"Category:Pages using invalid arguments in template calls",
							"Category:Pages using invalid arguments in template calls",
						},
						args = {
							tab4 = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>tab4</code> parameter is required.",
								},
							},
							content3 = {
								{
									category = "Category:Pages using invalid arguments in template calls",
									msg = "<code>content3</code> parameter is required.",
								},
							},
						},
					},
				},
			},
		},
	}
}

return p
Advertisement