July 25, 2020

👭 Knight Challenge #11 👬

Want to try your hand at these challenges? There's a couple of things you can do!
From writing, to research, to images, find your preferred way to contribute with our eleventh theme: Couples!

Latest Announcements

Module:UtilsValidate

From Zelda Wiki, the Zelda encyclopedia
Jump to: navigation, search
local p = {}

local i18n = require("Module:I18n")
local s = i18n.getString
local utilsError = require("Module:UtilsError")
local utilsTable = require("Module:UtilsTable")

local function code(s)
	return string.format("<code>%s</code>", s)
end

local function err(str, name, path, options, vars)
	vars = utilsTable.merge(vars or {}, {
		path = code(name .. utilsTable.printPath(path))
	})
	local msg = i18n.getString(str, vars)
	
	local options = options or {}
	local quiet = options.quiet
	local stackTrace = options.stackTrace
	local isUsageError = options.isUsageError
	
	if isUsageError then
		local invokeFrameTitle = mw.getCurrentFrame():getParent():getTitle()
		if mw.title.new(invokeFrameTitle).nsText == "Template" then
			msg = string.format("Misuse of [[%s]]: %s", invokeFrameTitle, msg)
		end
	end
	
	if not quiet then
		utilsError.warn(msg, {
			traceBack = stackTrace,
			omitFrames = 2
		})
	end
	return msg
end

function p.required(value, name, path, isKey, options)
	if value == nil then
		return err("msg.required", name, path, options)
	end
end

function p.nonEmpty(value, name, path, isKey, options)
	local isEmpty = 
		value == nil 
		or type(value) == "string" and value == ""
		or type(value) == "table" and utilsTable.isEqual({}, value)
	if isEmpty then
		return err("msg.empty", name, path, options, {
			actualValue = code(utilsTable.print(value))
		})
	end
end

function p.deprecated(value, name, path, isKey, options)
	if value ~= nil then
		return err("msg.deprecated", name, path, options, {
			value = code(value)
		})
	end
end

function p.type(expectedType)
	return function(value, name, path, isKey, options)
		local actualType = type(value)
		if value ~= nil and actualType ~= expectedType then
			local msg = isKey and "msg.typeKey" or "msg.type"
			return err(msg, name, path, options, {
				expectedType = code(expectedType),
				actualType = code(actualType),
				key = isKey and code(tostring(value)) or nil,
			})
		end
	end
end

local function enumDetails(enum)
	if enum.reference then
		return s("msg.enumReference", {
			referencePage = enum.reference
		})
	else
		return s("msg.enumAccepted", {
			values = code(utilsTable.print(enum, true))
		})
	end
end
			
function p.enum(acceptedValues)
	return function (value, name, path, isKey, options)
		if type(acceptedValues) == "function" then
			acceptedValues = acceptedValues()
		end
		-- Sometimes the argument is a list of values. It's easier to pretend that's always the case.
		local values = value
		local multivalue = type(value) == "table"
		if not multivalue then
			values = { value }
		end
		for k, value in ipairs(values) do
			value = mw.text.trim(value)
			if not utilsTable.keyOf(acceptedValues, value) then
				local path = utilsTable.concat(path or {}, multivalue and k or nil)
				local msg = isKey and "msg.enumKey" or "msg.enum"
				return err(msg, name, path, options, {
					actualValue = code(value),
					enumDetails = enumDetails(acceptedValues),
					key = code(tostring(value)),
				})
			end
		end
	end
end
function p._enum(acceptedValues)
	return function (value, name, path, isKey, options)
		if type(acceptedValues) == "function" then
			acceptedValues = acceptedValues()
		end
		-- Sometimes the argument is a list of values. It's easier to pretend that's always the case.
		local values = value
		local multivalue = type(value) == "table"
		if not multivalue then
			values = { value }
		end
		local errors = {}
		for k, value in ipairs(values) do
			value = mw.text.trim(value)
			if not utilsTable.keyOf(acceptedValues, value) then
				local path = utilsTable.concat(path or {}, multivalue and k or nil)
				local msg = isKey and "msg.enumKey" or "msg.enum"
				table.insert(errors, err(msg, name, path, options, {
					actualValue = code(value),
					enumDetails = enumDetails(acceptedValues),
					key = code(tostring(value)),
				}))
			end
		end
		return errors
	end
end

i18n.loadStrings({
	en = {
		msg = {
			empty = "${path} must be non-empty but is ${actualValue}.",
			required = "${path} is required but is <code>nil</code>.",
			type = "${path} is type ${actualType} but type ${expectedType} was expected.",
			typeKey = "${path} key ${key} is type ${actualType} but type ${expectedType} was expected.",
			deprecated = "${path} is deprecated but has value ${value}.",
			
			enum = "${path} has unexpected value ${actualValue}. ${enumDetails}",
			enumKey = "${key} is not an acceptable key for ${path}. ${enumDetails}",
			enumAccepted = "The accepted values are: ${values}",
			enumReference = "For a list of accepted values, refer to ${referencePage}.",
		},
	},
})

return p