Module:LibraryUtil

-- From MediaWiki source file

-- Set 'type' function to custom one to force use in other modules local oldType = type type = nil local function __type(v) local tp = oldType(v) local __t = (getmetatable(v) or {}).__type if __t ~= nil then if _type(__t) == "function" then return __t(v, tp) else return __t end else return tp	end end _G.type = __type

local function makeArgNumber(val) return table.concat{ type(val) == 'number' and '#' or '\, val, type(val) ~= "number" and '\ or '' } end

local function getParentName(level) level = (level or 0) + 3 local stack = debug.traceback(, level):gsub('\nstack traceback:\n', ):match('in function ([^\n]+)'):gsub('^\'(.-)\'$', '%1') stack = stack:match('[<>:]') and '?' or stack return stack end

local function indexOf(t, value) if type(t) ~= 'table' then return -1 end for i, v in ipairs(t) do		if v == value then return i 		end end return -1 end

local function typeMatches(val, types, nilOk) local valType = type(val) local tpTypes = type(types) local tn = tonumber(val) local isStringNumber = not not (tn and (types == 'string' or indexOf(types, 'string') ~= -1)) if tpTypes == 'table' and #types == 1 then types = types[1]; tpTypes = 'string' end if valType == 'nil' and nilOk then return true elseif val == nil and types == nil then return false end if tpTypes == 'string' then return (valType == types or (types == 'number' and not not tn)) or isStringNumber end for i, v in ipairs(types) do		if v == valType or (v == 'number' and tn) or isStringNumber then return true end end return false end

local function generateTypes(types) if type(types) == "string" then return types; end local n = #types

if n == 1 then return types[1] end

local typeList = table.concat(types, '/') return typeList end

local function generateMsg(name, index, val, types) types = generateTypes(types) local msg = string.format("bad argument %s to '%s' (%s expected, got %s)",		makeArgNumber(index),		name,		typeList or types,		val  ); return msg end

_G.typeMatches = typeMatches _G.getParentName = getParentName _G.makeArgNumber = makeArgNumber _G.generateMsg = generateMsg

function _G.setRequiredArgs(name, amount, checkTable) for i = 1, amount, 1 do		local v = checkTable[i]

if v == nil then local msg = string.format('failed to execute %q (%d arguments required, but only %d present)',				name,				amount,				i-1			) error(msg, 3) end end end

function _G.checkTypeField(formats, value, types) local valType = type(value) local index, parent, name = unpack{ formats[1] or formats.index or formats.i,		formats[2] or formats.parent or formats.p,		formats[3] or formats.name or formats.n,	} if type(types) == "table" then for _, i in ipairs(types) do			if i:lower == valType then return; end end elseif types:lower == valType then return; end local types = (#types > 1 and type(types) == "table") and table.concat(types, '/') or #types == 1 and types[1] or types

local msg = string.format("bad table index to %s to argument %s in function '%s' (%s expected, got %s)",		makeArgNumber(index),		makeArgNumber(parent),		name,		types,		valType	) error(msg, 3) end

function _G.validateTypes(types) local tp = type(types) local validTypes = { ['string']=true, ['number']=true, ['nil']=true, ['table']=true, ['boolean']=true, ['function']=true, ['any']=true, }	if not types or (tp ~= "table" and tp ~= "string") or types == "" or ((tp == 'string' or tp == 'table') and #types == 0) or (tp == 'table' and #types > 7) then return false end if #types == 1 and tp == 'table' then types = types[1]; tp = 'string' end if tp == "string" then return not not validTypes[types] end for i, v in ipairs(types) do		if not validTypes[v] then return false end end return true end

function _G.checkType(name, argIdx, arg, expectTypes, nilOk) local isConstructor if name == true then name = nil isConstructor = true end tpName = type(name) if (validateTypes(arg) and tpName == "number") or (name == nil and argIdx == nil) then nilOk = expectTypes expectTypes = arg or 'string' arg = argIdx or nil argIdx = name or 1 name = nil end local t = {} t.checkType = error tpName = type(name) if #expectTypes == 0 then return arg end local level = isConstructor and 1 or (tpName == "number" and name or 0) if not validateTypes(expectTypes) then error('invalid types', 2) end if not typeMatches(arg, expectTypes, nilOk) then local msg = generateMsg(tpName == "string" and name or getParentName(level), argIdx, type(arg), expectTypes) t.checkType(msg, level+3) end return arg end

function _G.checkArgs(types, ...) checkType(1, types, 'table') local t = {} -- Number of arguments local n = select('#', ...) t.checkTypeArgs = error local level = type(types.level) == "number" and (types.level >= -1 and types.level or -types.level) or 0 -- Valid types local validTypes = { ['string']=true, ['nil']=true, ['number']=true, ['table']=true, ['boolean']=true, ['function']=true, ['any']=true, ['stringable']=true, }	-- Validate types for i, v in ipairs(types) do		-- If index is not a string or table if type(types[i]) ~= "table" and type(types[i]) ~= "string" then t.checkTypeArgs(('invalid value (%s) at index #%d in argument #1 for \'%s\''):format(type(types[i]), i, getParentName(-1)), 2) end -- If the key contains multiple types if type(v) == "table" then for j, v2 in ipairs(v) do				if not validTypes[v2] then -- If invalid t.checkTypeArgs(('invalid type (%q) at index #%d for table index #%d in argument #1 for \'%s\''):format(v2, j, i, getParentName(-1)), 2) end end else -- If the type itself is invalid if not validTypes[v] then t.checkTypeArgs(('invalid type (%q) at index #%d in argument #1 for \'%s\''):format(v, i, getParentName(-1)), 2) end end end local toIter local len = #types if types.strict then toIter = (len >= n and len or n)	else toIter = len end local fName = getParentName(level) for i = 1, (len >= n and len or n) do		local any -- Variables local curTypes = type(types[i]) ~= "table" and { types[i] } or types[i] local emptyOk = curTypes.emptyOk local nilOk = curTypes.nilOk local val = ({ ... })[i] local argIndex = select('#', ...)+1 if i > #types and types.strict then t.checkTypeArgs(('bad argument #%d to \'%s\' (no value was expected)'):format(i, fName), level+3) end -- Case for nilOk if nilOk and emptyOk == nil then emptyOk = true end -- Special Case if nilOk is false and emptyOk is false if nilOk and emptyOk == false then table.insert(curTypes, 'nil') end -- Special case for the 'any' type if indexOf(curTypes, 'any') ~= -1 or (emptyOk == false and #curTypes == 0) then any = true curTypes = { nilOk = nilOk, emptyOk = emptyOk, }		end -- Sort types table.sort(curTypes) -- Error message #1 local valueExpected = ('bad argument #%d to \'%s\' (value expected)'):format(i, fName) -- If argument is nil, but not empty, and the expected type is 'any' and nilOk is false, error if nilOk == false and any and not (n n) and type(val) == 'nil' then t.checkTypeArgs(valueExpected, level+3) end -- If argument is empty if n n and (not emptyOk or type(val) == 'nil') then if any then -- Value expected error if argument is empty t.checkTypeArgs(valueExpected, level+3) elseif not nilOk or (nilOk and emptyOk == false) then -- No value error t.checkTypeArgs(generateMsg(fName, argIndex, 'no value', generateTypes(curTypes)), level+3) end else -- Else check its type as normal checkType((level ~= 0 and level+1 or 1), i, val, curTypes, nilOk) end end return ... end

_G.checkTypeArgs = checkArgs

function _G.checkTypeMulti(t, types) checkType(1, t, 'table') checkType(2, types, 'table') t.checkTypeMulti = error for i = 1, #types do		checkType(true, i, t[i], types[i]) end end

function _G.alertDeprecation(name, useInstead) local t = {} t.alertDeprecation = error if type(name) == "table" then name, useInstead = unpack{ name.name or name[1], name.useInstead or name.use or name[2], }	end t.alertDeprecation(string.format( 'function %q is deprecated%s', name or getParentName, useInstead and string.format(', use the function %q instead', useInstead) or '' ), 3) end

function _G.makeCheckFunction(name, isConstructor) return function(index, val, expectTypes, nilOk) if not typeMatches(val, expectTypes, nilOk) then local t = {} t.checkType = error msg = generateMsg(name, index, val, expectTypes) t.checkType(msg, isConstructor and 6 or 3) end end end

function _G.assertTrue(cond, name, index, msg, ...) local tmp = { ... }	local t = {} t.assertTrue = error if #tmp == 1 and type(tmp[1]) == "table" then params = tmp[1] else params = tmp end if cond then t.assertTrue(string.format('bad argument to %s to \'%s\' (%s)', makeArgNumber(index), name, params and string.format(msg, unpack(params)) or msg ), 3) end end

_G.customArgError = assertTrue

function _G.forEachArgs(types, startIndex, ...) checkType(1, types, { 'string', 'table' }) checkType(2, startIndex, 'number', true) checkType(3, cb, 'function', true) if not cb then cb = mw.log end local i = 0+(startIndex or 0) local args = { ... }	local lim = select('#', ...) return function i = i + 1 if i <= lim then checkType(1, i, args[i], types, types.nilOk) return i, args[i], args else return nil, nil end end end

function _G.makeCheckSelfFunction(libraryName, varName, selfObj, selfObjDesc) return function (self, method) if self ~= selfObj then error(string.format( "%s: invalid %s. Did you call %s with a dot instead of a colon, i.e. " .. "%s.%s instead of %s:%s?", libraryName, selfObjDesc, method, varName, method, varName, method ), 3)		end end end

return _G