Module:LibraryUtil

-- From MediaWiki source file 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) or )		:gsub('\nstack traceback:\n', )		:match('in function ([^\n]+)') or ) :gsub('^\'(.-)\'$', '%1') stack = (stack:match('[<>:]') or stack == '') and '?' or stack return stack end

local function indexOf(t, value) if type(t) ~= 'table' then return -1 end local i = 1 while t[i] do		if t[i] == value then return i end i = i+1 end return -1 end

local function typeMatches(val, types, nilOk, numberOk) 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 and numberOk) end for i = 1, #types, 1 do		local v = types[i] 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

return table.concat(types, '/') end

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

typeMatches = typeMatches getParentName = getParentName makeArgNumber = makeArgNumber generateMsg = generateMsg

function 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 == 'table' and #types > 7) then return false end if (tp == 'string' or tp == 'table') and #types == 0 then return true 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: checkType -- -- modes: --  type checker: checkType(name?: string, pos: number, value: any, types: string|table, nilOk?: boolean) --  no value error: checkType(name: 'no value', pos: number, types?: string|table, level?: number)

-- Options to types: --  *name: Changes the function to the value provided --  *base: Makes the value provided have the correct number base - function checkType(name, argIdx, arg, expectTypes, nilOk) local isConstructor, tpName if name == true then name = nil isConstructor = true end tpName = type(name) if (validateTypes(arg) and tpName == "number" and not validateTypes(expectTypes)) or (name == nil and argIdx == nil) then nilOk = expectTypes expectTypes = arg or 'string' arg = argIdx or nil argIdx = name or 1 name = nil end name = name or expectTypes.name local t = {} local tpArg = type(arg) t.checkType = error tpName = type(name) local level = isConstructor and 1 or (tpName == "number" and name or 0) local matches = name ~= 'no value' and typeMatches(arg, expectTypes, nilOk, (expectTypes and expectTypes.numberOk)) local isError = (tpName == "string" and name ~= 'no value') or not matches local fName if isError then fName = (name ~= 'no value' and tpName ~= 'number') and name or getParentName(name == 'no value' and expectTypes or level) end if name == 'no value' then t.checkType(generateMsg(fName, argIdx, arg and 'no value' or 'value expected', arg), (expectTypes or level)+3) end if expectTypes.base and (expectTypes.base < 2 or expectTypes.base > 36) then error('the option "base" must be between 2 and 36', 2) end local numArg = tonumber(arg, expectTypes.base) if expectTypes and #expectTypes == 0 then if tpArg == 'string' and numArg then return numArg end return arg end if not validateTypes(expectTypes) then error('invalid types', 2) end if expectTypes.base and not numArg then local msg = generateMsg(fName, argIdx, 'value is not convertable to base '..expectTypes.base) t.checkType(msg, level+3) elseif expectTypes.base and numArg then return numArg end if not matches then local msg = generateMsg(fName, argIdx, tpArg, expectTypes) t.checkType(msg, level+3) end return arg end

local function allTypes(...) local t = {} local args = { ... }	for i=1,select('#', ...) do		table.insert(t, type(args[i])) end return table.concat(t, '\t') end

function checkArgs(types, ...) checkType(1, types, { 'string', 'table' }) if type(types) == 'string' then types = { types } end 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		local tpTypes = type(types[i]) -- If index is not a string or table if tpTypes ~= "table" and tpTypes ~= "string" then t.checkTypeArgs(('invalid value (%s) at index #%d in argument #1 for \'%s\''):format(tpTypes, 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 curTypes.name = fName -- Else check its type as normal checkType((level ~= 0 and level+1 or 1), i, val, curTypes, nilOk) end end return ... end

checkTypeArgs = checkArgs

function 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 alertDeprecation(...) local name, useInstead, level = checkTypeArgs({ 		'string', { 'string', nilOk=true }, { 'number', nilOk=true } 	}, ...) 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 '' ), (level or 0)+3) end

function customArgError(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

function forEachArgs(types, ...) checkType(1, types, { 'string', 'table' }) local startIndex = types.startIndex and types.startIndex-1 or 0 local required = types.required or 0 local i = 0+(startIndex or 0) local args = { ... }	local lim = select('#', ...) local ind = indexOf(types, 'any') local t = {} t.checkType = error return function i = i + 1 if lim lim then if ind ~= -1 then t.checkType(generateMsg(getParentName, i, 'value expected'), 3) elseif ind == -1 then checkType('no value', i, types, 1) end end if i <= lim then if ind == -1 then checkType(1, i, args[i], types, types.nilOk) end return i, args[i], args else return nil, nil end end, ... end

function makeCheckSelfFunction(libraryName, varName, selfObj, selfObjDesc) if type(varName) == 'table' then varName = libraryName selfObjDesc = selfObj end if type(libraryName) == 'table' then selfObj = libraryName libraryName = varName end return function(self, method) if self ~= selfObj then method = method or getParentName; ({ checkSelf=error })['checkSelf'](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 or 'self object', method, varName, method, varName, method ), 3)		end end end

function formattedError(formatStr, level, ...) checkType(1, formatStr, { 'string' }, true) checkType(2, level, { 'number' }, true) local formatStr = type(formatStr) == 'string' and formatStr or 'unknown error' return ({ formattedError=error })['formattedError'](string.format(formatStr, ...), tonumber(level) == 0 and level or (level or 1)+1) end

function formattedAssert(v, formatStr, level, ...) checkType(2, formatStr, { 'string' }, true) checkType(3, level, { 'number' }, true) local formatStr = type(formatStr) == 'string' and formatStr or 'assertion failed!' if not v then formattedError(formatStr, level == 0 and level or (level or 0)+1, ...) else return v	end end

function assertFalse(v, formatStr, level, ...) checkType(2, formatStr, { 'string' }, true) checkType(3, level, { 'number' }, true) local formatStr = type(formatStr) == 'string' and formatStr or 'assertion failed!' if v then formattedError(formatStr, level == 0 and level or (level or 0)+1, ...) else return v	end end

assertTrue = formattedAssert

function pipeline(...) local value = checkArgs('any', ...) local prependArgs = { value } local t, functionFound for k, v in forEachArgs({ 'any', startIndex=2 }, ...) do		if type(v) ~= 'function' then if #prependArgs == 0 then prependArgs[#prependArgs+1] = value end prependArgs[#prependArgs+1] = v		else t = { pcall(v, unpack(prependArgs)) } functionFound = true assertTrue(t[1], 'Exception in calling function in que posistion #%d: %s', 2, k, t[2]) table.remove(t or {}, 1) prependArgs = t		end end if functionFound then return unpack(t) else return ... end end

function dedent(s, isSpaces) checkType(1, s, 'string') s = s:gsub('^[ \n\r\v]*(.-)[ \n\r\v]*$', '%1'):gsub('\\([%a\"\'%d]+)', {		['t'] = '\t',		['n'] = '\n',		['r'] = '\r',		['v'] = '\v',		['f'] = '\f',		['a'] = '\a',		['b'] = '\b',		['"'] = '\"',		['\] = '\,	})	local lines = mw.text.split(s, '\n')	local overallLen = {}	local isSpaces = true	-- Check spaces	for i, v in ipairs(lines) do		local _, num = v:find('^\t+')		if num then			isSpaces = false		end	end	for i, v in ipairs(lines) do		local _, num		if isSpaces then			_, num = v:find('^ +')		else			_, num = v:find('^\t+')		end		num = isSpaces and num/4 or num		table.insert(overallLen, #overallLen+1, num)	end	local overallLen = math.min(unpack(overallLen) or 0)	for i, v in ipairs(lines) do		lines[i] = v:gsub('^'..(not isSpaces and ('\t'):rep(overallLen) or (' '):rep(overallLen*4)), '')	end	return table.concat(lines, '\n') end

return _G