Module:Table

- --							Module:Table -- -- This module includes a number of functions for dealing with Lua tables. -- It is a meta-module, meant to be called from other Lua modules, and should -- not be called directly from. -- [ CONTENTS ]- -- The following list is all the functions this module houses. -- --	*function: merge(t1: table [the target table], ...items: any) --	*function: length(t: table, countNamed?: boolean) -- *function: slice(t: table, startIndex?: number, endIndex?: number) --	*function: setPrototype(t: table, proto: table, indexFunc: function) --	*function: isSequence(t: table) --	*function: push(t: table, ...items: any) --	*function: makeReadonlyTable(t: table, realTable: t) --	*function: isClass(t: table) --	*function: makeClass(constructor?: function, methods: table, parentClass?: table, options?: table) --	*function: unshift(t: table, ...items: any) --	*function: map(t: table, callbackfn: function(v: any, i?: number, t?: table) --	*function: filter(t: table, callbackfn: function(v: any, i?: number, t?: table) --	*function: removeLast(t: table) --	*function: removeFirst(t: table) --	*function: flat(t: table) --	*function: flatMap(t: function, cb: function) --	*function: find(t: table, cb: function) --	*function: findIndex(t: table, cb: function) --	*function: findAll(t: table, cb: function) --	*function: splice(t, start, deleteCount, ...) --	*function: fill(t?: table, v: any, startIndex: number, endIndex?: number) --	*function: indexOf(t: table, v: any) --	*function: lastIndexOf(t: table, v: any) --	*function: includes(t: table, v: any) --	*function: every(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) --	*function: some(t: table, callbackfn: function(v: any, i: number, t: table) --	*function: keys(t: table) --	*function: entries(t: table) --	*function: keySubset(t: table) --	*function: reverseIpairs(t: table) --	*function: reverse(t: table) --	*function: empty(t: table) --	*function: isEmpty(t: table) --	*function: clean(t: table) --	*function: affixNums(t: table, prefix?: string, suffix?: string) --	*function: numData(t: table, compress?: boolean) --	*function: compressSparseArray(t: table) --	*function: sparseIpairs(t: table) --	*function: keysToList(t: table, keySort?: function|boolean, checked?: boolean) --	*function: invert(t: table) --	*function: from(t: table) --	*function: sequenceToSet(t: table) --	*function: sortedPairs(t: table, keySort?: boolean) --	*function: recursiveConcat(t: table, sep?: string, i?: number, j?: number) --	*function: each(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) --	*function: format(t: table) --	*function: deepCopy(t: table) --	*function: dumpObject(t: table) --	*function: logObject(t: table) --	*function: tableUtil(t?: table) -- --[ ATTRIBUTION ] -- Some documentation text of this module was taken form the MDN JS guide -- (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference). -- -- Some Functions were taked from `Module:TableTools` at `en.wikipedia.org`. - local yesno = require('Module:Yesno') local lu = require('Module:LibraryUtil') local checkType, checkType, customArgError = lu.checkType, lu.checkType, lu.customArgError

--Begin Exports local p = {}

-- Set 'type' function to custom one to force use in other modules local _type = type type = nil local function __type(v) local tp = _type(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

-- Load table library if this module is loaded as "table" p.concat = table.concat p.maxn = table.maxn p.remove = table.remove p.insert = table.insert p.sort = table.sort p.unpack = function(t) local mt = getmetatable(t) local __m = (mt or {}).__unpack if __m then if type(__m) == "function" then return __m(t) else return __m end else local _, __ = pcall(unpack, t)		return _ == false and error(__:gsub('%?', 'unpack'), 2) or unpack(t) end end p.pack = function(...) return { ..., n=select('#', ...) } end

- -- Local Helper functions - local function isPositiveInteger(v) return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge end

local function isNaN(v) return type(v) == 'number' and tostring(v) == '-nan' end

local function flattenTable(arr, cb) local i = 0 while arr[i] do		local v = arr[i] if type(v) == "table" then flattenTable(v, arr) else cb(v, i, arr) end end end

- -- function: merge(t1: table [the target table], ...items: any) -- -- This function takes each entry in `...items` and adds it to the table. -- If the item is not a table, it adds it to the table like `push`. -- Else, it destructures the table and adds each of its items to the end of `t1`. - function p.merge(t1, ...) checkType('merge', 1, t1, 'table')

local function _merge(i, v)		if type(v) == "table" then if p.isSequence(v) then for i, value in ipairs(v) do					p.push(t1, value) end else for key, value in pairs(v) do					if type(key) == "number" then p.push(t1, value) else t1[key] = value end end end else p.push(t1, v)		end end

if select('#', ...) > 1 then for i, v in forEachArgs({ 'any' }, ...) do			_merge(i, v)		end else _merge(1, ...) end return t1 end

- -- function: length(t: table, countNamed?: boolean) -- -- Gets the length of `t`, as the `#` (length operator) does not work on tables -- with named indexes (named indexes can be skipped if the second parameter is omitted). -- function p.length(t, countNamed) checkType(1, t, 'table') checkType(2, countNamed, 'boolean', true) local i = 0 for k in pairs(t) do		if countedNamed then if type(k) == "number" then i = i + 1 end else i = i + 1 end end return i end

- -- function: slice(t: table, startIndex: table, endIndex: table) -- -- method returns a shallow copy of a portion of an table into a new table object -- selected from start to end (end not included) where start and end represent the index of -- items in that table. The original table will not be modified. -- -- ^source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice - function p.slice(t, startIndex, endIndex) checkType(1, t, { 'table' }) checkType(2, startIndex, { 'number' }, true) checkType(3, endIndex, { 'number' }, true) if not endIndex and not startIndex then return p.from(t) end local ret, len = {}, #t startIndex = startIndex or 1 endIndex = endIndex or len if startIndex < 0 then startIndex = len+startIndex-1 end if endIndex < 0 then endIndex = len+endIndex-1 end for i, v in ipairs(t) do		if i >= startIndex and i <= endIndex then p.push(ret, v)		end end return ret end

- -- function: setPrototype(t: table, proto: table, indexFunc: function) -- -- Sets up a metatable prototype with a table. - function p.setPrototype(t, proto, indexFunc, parentProto) checkType('setPrototype', 1, t, 'table') checkType('setPrototype', 2, proto, 'table') checkType('setPrototype', 3, indexFunc, 'function', true) checkType('setPrototype', 4, parentProto, 'table', true) local mt = {} mt.__proto = proto or {} parentProto = parentProto or {} if p.length(parentProto) ~= 0 then mt.__proto.prototype = parentProto end indexFunc = indexFunc or function(t, k, proto, parentProto) if k == 'prototype' then return mt.__proto else return mt.__proto[k] or (p.length(parentProto or {}) ~= 0 and mt.__proto.prototype[k] or nil) end end mt.__index = function(t, k)		return indexFunc(t, k, mt.__proto, mt.__proto.prototype) end return setmetatable(t, mt) end

- -- function: isSequence(t: table) -- -- Checks if the table is a sequence. - function p.isSequence(t) checkType('isSequence', 1, t, 'table') return p.every(t, function(i) return type(i) == "number" and i > 0 end) end

- -- function: push(t: table, ...items: any) -- -- Appends each of `...items` to the end of `t`. - function p.push(t, ...) local items = p.pack(...) checkType('push', 1, t, 'table') for _, v in ipairs(items) do		table.insert(t, v)	end return t end

- -- function: makeReadonlyTable(t: table, realTable: t) -- -- Makes a read-only table where the real table is a dummy table. - function p.makeReadonlyTable(t, metatable) checkType(1, t, 'table', true) checkType(2, metatable, 'table', true) local tmp, mt, readableMt = {}, {} metatable = metatable or {} t = t or {} metatable.__index, metatable.__newindex, metatable.__pairs, metatable.__ipairs, metatable.__metatable = nil, nil, nil, nil, nil for k, v in pairs(metatable) do		mt[k] = v	end mt.__realTable = t	local function __index(t, k)		return mt.__realTable[k] end local function __newindex return error('table is read-only', 2) end local function __pairs return pairs(mt.__realTable) end

local function __ipairs return ipairs(mt.__realTable) end local function len return #mt.__realTable end mt.__index = __index mt.__newindex = __newindex mt.__pairs = __pairs mt.__ipairs = __ipairs mt.__len = len mt.__metatable = {}

return setmetatable(tmp, mt) end

- -- function: isClass(t: table) -- -- Determines if the table is a class. - function p.isClass(t) return type(t) == 'table' and (getmetatable(t) or {}).__isClass or false end

- -- function: makeClass(constructor?: function, methods: table, parentClass?: table, options?: table) -- -- Creates a class table. - function p.makeClass(constructor, methods, parentClass, options) checkType('makeClass', 1, constructor, { 'function', 'table', 'nil' }) checkType('makeClass', 2, methods, { 'table', 'nil' }) checkType('makeClass', 3, parentClass, { 'table', 'nil' }) checkType('makeClass', 4, options, 'table', true) local options = options or {} local ignoreTableRet = unpack{ options.ignoreTableRet or options.ignoreTable or options.ignore, }	local origConstructor = constructor function tmpFunc(t1) return t1	end function tmpChildFunc(t1, ...) t1.super(...) return t1	end local get, set = 'get', 'set' local methodIsClass = p.isClass(methods) local constructorIsClass = p.isClass(constructor) if type(constructor) == "table" and (methodIsClass or (methods == nil and parentClass == nil and p.length(options) == 0)) then parentClass = methods methods = constructor origConstructor, constructor = nil, nil elseif constructorIsClass then parentClass = constructor origConstructor, constructor = nil, nil elseif type(constructor) == 'function' and methodIsClass then parentClass = methods methods = nil end methods = methods ~= nil and methods or {} if methods.constructor then constructor = methods.constructor elseif not methods.constructor and type(origConstructor) == "table" then constructor = parentClass and tmpChildFunc or tmpFunc end local util = {} local t2 = {} local staticReservedNames = p.sequenceToSet{ 'parent', 'methods', 'super', '__constructorCalled', '__isClass', 'prototype', }	local reservedNames = p.sequenceToSet{ 'parent', 'methods', 'static', '__proto__', 'super', '__constructorCalled', '__isClass', }

local allowedTypes = p.sequenceToSet{ 'static', 'get', 'set', }	if constructor == nil then constructor = parentClass and tmpChildFunc or tmpFunc end

function util:methodType(f, searchType) checkType(2, searchType, 'string', true) if searchType then searchType = searchType:lower end

if type(f) == "function" then return "function" end if type(f) ~= "table" then return false end if type(f[2]) ~= "function" then return false end local f1 = f[1] local tpF1 = type(f1) if tpF1 == 'table' then if #f1 == 1 then if searchType then return f1[1] == searchType and searchType or false else return f1[1] end else if searchType then return f1[1] == searchType and searchType or f1[2] == searchType and searchType or false else return unpack(f1) end end elseif tpF1 == 'string' and searchType then return f1 == searchType and searchType or false else return f1		end end function util:filterMethods(t, _type, invert) checkType(1, t, 'table') local ret = {} for k, v in pairs(t) do			local tmp = util:methodType(v, _type) == _type local compare if invert then compare = not tmp else compare = tmp end if compare then ret[k] = v			end end return ret end

function util:new(...) local methods = util:filterMethods(methods, 'static', true) local mt = { methods = methods, parent = parentClass, static = t2, __isClass = true, setters = {}, getters = {}, }		-- Manage Setters for k, v in pairs(methods) do			local mType, mType2 = util:methodType(methods) if mType == get or mType2 == get then mt.getters[k] = v;			elseif mType == set or mType2 == set then mt.setters[k] = v			end end for k, v in pairs(t2.prototype) do			methods[k] = v		end local t1 = {} local metatableMethods = {} local nonFuncMethods = util:filterMethods(methods, false) for k, v in pairs(methods) do			if not reservedNames[k] then t1[k] = nonFuncMethods[k] end end if parentClass and (getmetatable(parentClass) or {}).__constructorCalled == true then for k, v in pairs(parentClass) do				if not reservedNames[k] then t1[k] = v				end end end

mt.__proto__ = mt.methods mt.super = function(...) if mt.parent == nil then error('the class must have a parent when calling the super constructor', 2) end for k, v in pairs(mt.parent) do				if not reservedNames[k] then t1[k] = v				end end mt.parent = mt.parent:constructor(...) return mt.parent end mt.methods.getmetatable = getmetatable function mt.methods:instanceof(class) checkType(1, class, { 'table' }) customArgError(p.isClass(class) == false, 'instanceof', 1, 'class expected') customArgError(getmetatable(class).__constructorCalled == true, 'instanceof', 1, 'the class must be static') if not self.parent then return false end local tmp = self while tmp.parent do				if tmp.parent.static == class then return true else tmp = tmp.parent end end return false end mt.__index = function(_, k)			local value

if reservedNames[k] then return mt[k] elseif reservedNames[k] and (k == '__isClass' or k == '__constructorCalled') then return nil end if parentClass then value = mt.methods[k] or (mt.parent.methods or {})[k] else value = mt.methods[k] end if mt.getters[k] or (mt.parent and mt.parent.getters[k]) then return mt.getters[k](t1) else return value end return value end mt.__newindex = function(_, k, v)			local mType, mType2 = util:methodType(v) if metatableMethods[k] then rawset(mt.methods, k, metatableMethods[k]) rawset(mt, k, metatableMethods[k]) return mt.methods[k] elseif reservedNames[k] then formattedError('class property %q is readonly', 2, k) -- 			elseif type(v) == "function" then				return rawset(mt.methods, k, v)			elseif mType == 'set' or mType2 == 'set' then				mt.setters[k] = v				return rawset(mt.setters, k, v[2])			elseif mType == 'get' or mType2 == 'get' then				mt.getters[k] = v				return rawset(mt.setters, k, v[2])			elseif mType == 'static' or mType2 == 'static' then				return rawset(mt.static, k, v[2]) -- elseif mt.setters[k] or (mt.parent and mt.parent.setters[k]) then return mt.setters[k](t1, k, v)			else return rawset(t1, k, v)			end end

local function methodExists(index) local ind = mt.methods[index] or (mt.parent and mt.parent.methods or {})[index] if ind then return true end end metatableMethods.__tostring = function return t1:__tostring(t1) end metatableMethods.__concat = function(a, b) return t1:__concat(a, b) end metatableMethods.__call = t1.__call metatableMethods.__pairs = function return t1:__pairs(t1, t1.methods, t1.static) end metatableMethods.__ipairs = function return t1:__ipairs(t1, t1.methods, t1.static) end metatableMethods.__add = function(a, b) return t1:__add(a, b) end metatableMethods.__sub = function(a, b) return t1:__sub(a, b) end metatableMethods.__mul = function(a, b) return t1:__mul(a, b) end metatableMethods.__div = function(a, b) return t1:__div(a, b) end metatableMethods.__mod = function(a, b) return t1:__mod(a, b) end metatableMethods.__pow = function(a, b) return t1:__pow(a, b) end metatableMethods.__unm = function(a, b) return t1:__unm(a, b) end --Custom metamethods metatableMethods.__tonumber = function(a) return t1:__tonumber(a) end metatableMethods.__type = function(a, b) return t1:__type(a, b) end metatableMethods.__unpack = function(a, b) return t1:__type(a, b) end metatableMethods.__toboolean = function(a) return t1:__toboolean(a, b) end metatableMethods.__totable = function(a, b, c, d) return t1:__totable(a, b, c, d) end metatableMethods.__tconcat = function(a, b, c, d) return t1:__tconcat(a, b, c, d) end

for k, v in pairs(metatableMethods) do			if methodExists(k) then mt[k] = v			end end mt.__metatable = mt --{			__constructorCalled = true,			__isClass = true,		} setmetatable(t1, mt) local constructorValue = constructor(t1, ...) if t1.parent and (getmetatable(t1.parent) or {}).__constructorCalled == false then error('makeClass: parent class constructor must be called if the child constructor was called', 2) end mt.__constructorCalled = true local ret if ignoreTableRet or type(constuctorValue) == "table" then ret = constructorValue else ret = t1		end if ret == nil then ret = t1		end return ret end

local metatableMethods, mt = {}, {} mt.parent = (parentClass or {}).static mt.methods = util:filterMethods(methods, 'static') or {} mt.prototype = p.merge(util:filterMethods(methods, 'static', true), (parentClass or {}).prototype or {}) or {} mt.methods.constructor, mt.prototype.constructor = util.new, util.new

mt.__isClass = true mt.__constructorCalled = false mt.__call = util.new mt.__index = function(_, k)		local value if mt.parent ~= nil then value = mt.methods[k] or mt.parent[k] or (mt.parent.methods or {})[k] else value = mt.methods[k] end local function mType(type) return util:methodType(value, type) end -- if mType('get') == 'get' then -- 	return util:execMethod(value, t2) -- end if staticReservedNames[k] then return mt[k] elseif mType('static') == 'static' then return value[2] else return value end end mt.__newindex = function(_, k, v)		local function mType(type) return util:methodType(v, type) end if metatableMethods[k] then rawset(mt.methods, k, v)			rawset(mt, k, metatableMethods[k]) elseif staticReservedNames[k] then formattedError('class property %q is readonly', 2, k)		elseif type(v) == "function" then rawset(mt.methods, k, v) -- Disabled currently, will be reworked at a later date. -- elseif mType('get') == 'get' or mType('set') == 'set' then -- 	rawset(t2.methods, k, v)		-- elseif util:methodType(t2[k], 'set') == 'set' then -- 	return util:execMethod(t2[k], t2, v, k)		elseif util:methodType(v, 'static') then rawset(mt, k, v[2]) else rawset(t2, k, v)		end return t2[k] end

local function methodExists(index) local ind = (mt.methods or {})[index] or ((mt.parent or {}).methods or {})[index] if ind then return true end end

metatableMethods.__tostring = function return t2:__tostring(t2) end metatableMethods.__concat = function(a, b) return t2:__concat(a, b) end metatableMethods.__pairs = function return t2:__pairs(t2, t2.methods) end metatableMethods.__ipairs = function return t2:__ipairs(t2, t2.methods) end metatableMethods.__add = function(a, b) return t2:__add(a, b) end metatableMethods.__sub = function(a, b) return t2:__sub(a, b) end metatableMethods.__mul = function(a, b) return t2:__mul(a, b) end metatableMethods.__div = function(a, b) return t2:__div(a, b) end metatableMethods.__mod = function(a, b) return t2:__mod(a, b) end metatableMethods.__pow = function(a, b) return t2:__pow(a, b) end metatableMethods.__unm = function(a, b) return t2:__unm(a, b) end --Custom metamethods metatableMethods.__tonumber = function(a) return t2:__tonumber(a) end metatableMethods.__type = function(a, b) return t2:__type(a, b) end metatableMethods.__unpack = function(a, b) return t2:__type(a, b) end metatableMethods.__toboolean = function(a) return t2:__toboolean(a, b) end metatableMethods.__totable = function(a, b, c, d) return t2:__totable(a, b, c, d) end metatableMethods.__tconcat = function(a, b, c, d) return t2:__tconcat(a, b, c, d) end for k, v in pairs(metatableMethods) do		if methodExists(k) then mt[k] = v		end end mt.__metatable = { __isClass = true, __constructorCalled = false, }	return setmetatable(t2, mt) end makeClass = p.makeClass

function p.test(...) local Class = p.makeClass{ "$", constructor = function error end } Class.prototype.test = function return "$" end Class.prototype.__tostring = function error end return Class end

- -- function: unshift(t: table, ...items: any) -- -- Prepends each of `...items` to the front of `t`. - function p.unshift(t, ...) local items = p.pack(...) checkType('unshift', 1, t, 'table') if items.n > 1 then for _, v in ipairs(items) do			table.insert(t, 1, v)		end else table.insert(t, 1, ...) end return t end

- -- function: map(t: table, callbackfn: function(v: any, i?: number, t?: table)) -- -- Creates a new table and populates with results from the callback function. -- ---[ CALLBACK PARAMETERS ]- -- The callback to `map` has three values passed to it described above. -- `v`, the value of the table index, `i`, the table index, and `t`, the actual table. -- `map` will popluate the new table with the return values of this function. - function p.map(t, callbackfn) checkType('map', 1, t, 'table') customArgError(not p.isSequence(t), 'map', 1, 'provided table is not a sequence') checkType('map', 2, callbackfn, 'function') local ret = {} for i, v in ipairs(t) do		ret[#ret+1] = callbackfn(v, i, t) 	end return ret end

- -- function: filter(t: table, callbackfn: function(v: any, i?: number, t?: table)) -- -- Creates a new table with all elements that pass the test implemented by the provided function. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(v: any, i?: number, t?: table) -- -- The callback to `filter` has three values passed to it described above. -- `v`, the value of the table index, `i`, the table index, and `t`, the actual table. -- `filter` will popluate the new table with the value of the old table if the returned value -- from the callback is true. - function p.filter(t, callbackfn) checkType('filter', 1, t, 'table') customArgError(not p.isSequence(t), 'filter', 1, 'provided table is not a sequence') checkType('filter', 2, callbackfn, 'function') local ret = {} for i, v in ipairs(t) do		if callbackfn(v, i, t) then p.push(ret, v)		end end return ret end

- -- function: removeLast(t: table) -- -- Removes the last element of the table and returns it. - function p.removeLast(t) checkType('removeLast', 1, t, 'table') customArgError(not p.isSequence(t), 'removeLast', 1, 'provided table is not a sequence')

return table.remove(t, p.length(t)) end

- -- function: removeFirst(t: table) -- -- Removes the First element of the table and returns it. - function p.removeFirst(t) checkType('removeFirst', 1, t, 'table') customArgError(not p.isSequence(t), 'removeFirst', 1, 'provided table is not a sequence')

return table.remove(t, 1) end

- -- function: flat(t: table) -- -- Takes a sequence table and flattens all elements down into a single-depth table recursively. - function p.flat(t) checkType(1, t, { 'table' }) customArgError(not p.isSequence(t), 'flat', 1, 'table is not a sequence') local ret = {} local function _recurse(t) for i, v in ipairs(t) do			if type(v) == 'table' then _recurse(v) else p.push(ret, v)			end end end _recurse(t) return ret end

- -- function: flatMap(t: function, cb: function) -- -- calls flat on the `t` then calls map on the flattened table with `cb`. - function p.flatMap(t, cb) checkType(1, t, 'table') checkType(2, cb, { 'function' }) return p.map(p.flat(t), cb) end

- -- function: Map(items: table) -- -- - function p.Map(items) local ret = {} local keys = {} local seenkeys = {} local Map = {} Map.getters = {} function Map.getters:size return p.length(self, true) end function Map:set(k, v)		assertFalse(k == nil, 'table index is nil', 2) self[k] = v		return self end function Map:get(k, v)		assertFalse(k == nil, 'table index is nil', 2) self[k] = v		return self end function Map:remove(k) for i = 1, #keys do			if keys[i] == k then table.remove(keys, i)				break end end self[k] = nil return self end

function Map:clear p.empty(self) p.empty(keys) return self end function Map:forEach(cb) checkType(1, cb, 'function', true) for k, v in pairs(self) do			(cb or mw.log)(k, v, self) end return self end function Map:keys return keys end local function __pairs(t) local i, len = 0, #keys return function i = i+1 if keys[i] and i <= len then return keys[i], ret[keys[i]] else return nil, nil end end end function Map:entries local ret = {} for i = 1, #keys do			ret[i] = { keys[i], self[keys[i]] } end return ret end Map.constructor = p.Map

setmetatable(ret, {		__index = function(t, k)			if Map.getters[k] then				return Map.getters[k](t)			elseif k ~= 'getters' then				return Map[k]			end			return nil		end,		__newindex = function(t, k, v)			if seenkeys[k] and not t[k] then				for i = 1, #keys do					if keys[i] == k then						table.remove(keys, i)						break					end				end			end			rawset(seenkeys, k, 1)			table.insert(keys, k)			rawset(t, k, v)		end,		__pairs = __pairs,	})

return ret end

- -- function: find(t: table, cb: function) -- -- Checks each result found by `cb` and compares it against `t`. If an index is found, -- the result is returned along with it's index. If no value is found, it returns nil. - function p.find(t, cb) checkType(1, t, { 'table' }) checkType(2, cb, { 'function' }) local i = 1 while t[i] ~= nil do		if cb(t[i], i, t) then return t[i], i		end i = i+1 end return nil end

- -- function: findIndex(t: table, cb: function) -- -- Very similar to find, but returns only the index. - function p.findIndex(t, cb) checkType(1, t, { 'table' }) checkType(2, cb, { 'function' }) local _, ret = p.find(t, cb) return ret end

- -- function: findAll(t: table, cb: function) -- -- Very similar to find, but returns a table of all values found. - function p.findAll(t, cb) checkType(1, t, { 'table' }) checkType(2, cb, { 'function' }) local ret = {} local i = 1 while t[i] ~= nil do		if cb(t[i], i, t) then p.push(ret, { value=t[i], index=i }) end i = i+1 end return ret end

- -- function: splice(t: table, start: number, deleteCount?: number, ...items: any) -- -- - function p.splice(t, start, deleteCount, ...) checkType(1, t, { 'table' }) checkType(2, start, { 'number' }) checkType(3, deleteCount, { 'number' }, true) start = (start and start ~= 0) and start or 1 local ret, len = {}, #t count = 0 if - -start < 0 then start = len+start+1 end maxCount = - -deleteCount or math.huge for i = start, len, 1 do		count = count+1 if count > maxCount then break end p.push(ret, table.remove(t, start)) end if select('#', ...) > 0 then for _, v in forEachArgs({ 'any' }, ...) do			table.insert(t, start, v)		end end return ret end

- -- function: fill(t?: table, v: any, startIndex: number, endIndex?: number) -- -- Sets the table index starting at `startIndex` with the value of `v` ending at `endIndex`. -- If end is not specified, it defualts to the length of the table. - function p.fill(t, v, startIndex, endIndex) checkType('fill', 1, t, 'table', true) checkType('fill', 3, startIndex, 'number') checkType('fill', 4, endIndex, 'number', true)

local t = t or {}

for i = startIndex, endIndex or #t, 1 do		t[i] = v	end return t end

- -- function: indexOf(t: table, v: any) -- -- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`. - function p.indexOf(t, value) checkType('indexOf', 1, t, 'table') customArgError(not p.isSequence(t), 'indexOf', 1, 'provided table is not a sequence') local index p.some(t, function(k, v) 		if v == value then			index = k			return true 		else			return false		end	end) return index or -1 end

- -- function: lastIndexOf(t: table, v: any) -- -- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`. - function p.lastIndexOf(t, v)	checkType('lastIndexOf', 1, t, 'table') customArgError(not p.isSequence(t), 'lastIndexOf', 1, 'provided table is not a sequence') for i, value in p.reverseIpairs(t) do		if v == value then return i end end return -1 end

- -- function: includes(t: table, v: any) -- -- Checks if `v` is included in any indexes in `t`. - function p.includes(t, _v) checkType('includes', 1, t, 'table') customArgError(not p.isSequence(t), 'includes', 1, 'provided table is not a sequence') return p.some(t, 		function(i, v)			return t[i] == _v 		end	) end

- -- function: every(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number)) -- -- Tests every element from the return value from `callbackfn`. If any elements fail -- the test, it returns false. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(k: any, v?: any, t?: table, i?: number) -- -- If the callback returns false, `every` will consider the test failed and -- return false. --  *`k` is the table key `every` is currently over. --  *`v` is the value of the table key `every` is currently over. --  *`t` is the table `every` was called on. --  *`i` is the number of iterations `every` has iterated over. - function p.every(t, callbackfn) checkType('every', 1, t, 'table') checkType('every', 2, callbackfn, 'function') local i = 0 for k, v in pairs(t) do		i = i + 1 if not callbackfn(k, v, t, i) then return false end end return true end

- -- function: some(t: table, callbackfn: function(v: any, i: number, t: table)) -- -- Tests whether at least one element in the table passes the test implemented -- by the provided function. It returns a Boolean value. -- function p.some(t, callbackfn) checkType('some', 1, t, 'table') checkType('some', 2, callbackfn, 'function') local i = 0 for k, v in pairs(t) do		i = i + 1 if callbackfn(k, v, t, i) then return true end end return false end

- -- function: reduce( --	 t: table, --	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table), --	 initVal: any --) -- -- Executes a reducer callback function on each element of the table, resulting in single output value. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(accumlator: any, curVal: any, i?: number, t?: table) -- -- The callback to `every` has four values passed to it described above. -- `accumlator` is the accumulated value previously returned in the last invocation -- of the callback value of to accumalate. -- `curVal` is the current element being processed in the table. -- `i` is the current index of the processes element. -- `t` is the table `reduce` was called on. - function p.reduce(t, callbackfn, initVal) checkType('reduce', 1, t, 'table') customArgError(not p.isSequence(t), 'reduce', 1, 'provided table is not a sequence') checkType('reduce', 2, callbackfn, 'function') customArgError(callbackfn(, , , ) == nil, 'reduce', 1, 'no return value for callback') local accumulator for i, v in ipairs(t) do		if i == 1 then accumulator = initVal and callbackfn(initVal, v, i, t) or v		elseif i ~= 1 then accumulator = callbackfn(accumulator, v, i, t)		end end return accumulator end

- -- function: reduceRight( --	 t: table, --	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table), --	 initVal: any --) -- -- Executes a reducer callback function on each element of the table from left to right, -- resulting in single output value. Very similar to `reduce`. -- ---[ CALLBACK PARAMETERS ]- -- callbackfn(accumlator: any, curVal: any, i?: number, t?: table) -- -- The callback to `every` has four values passed to it described above. -- `accumlator` is the accumulated value previously returned in the last invocation -- of the callback value of to accumalate. -- `curVal` is the current element being processed in the table. -- `i` is the current index of the processes element. -- `t` is the table `reduceRight` was called on. - function p.reduceRight(t, callbackfn, initVal) checkType('reduceRight', 1, t, 'table') customArgError(not p.isSequence(t), 'reduceRight', 1, 'provided table is not a sequence') checkType('reduceRight', 2, callbackfn, 'function') customArgError(callbackfn(, , , ) == nil, 'reduceRight', 1, 'no return value for callback') local accumulator for i, v, start in p.reverseIpairs(t) do		if i == start then accumulator = initVal and callbackfn(initVal, v, i, t) or v		elseif i ~= start then accumulator = callbackfn(accumulator, v, i, t)		end end return accumulator end

- -- function: keys(t: table) -- -- Returns a table containing all keys of this table in order. - function p.keys(t) local ret = {} for k, v in p.sortedPairs(t) do		p.push(ret, v)	end return ret end

- -- function: entries(t: table) -- -- Returns a table with each subtable containing the tables key in the first value, -- and the original table's value corresponding to that key. - function p.entries(t) local ret = {} for k, v in p.sortedPairs(t) do		p.push(ret, { k, v }) end return ret end

- -- function: keySubset(t: table) -- -- This takes a table and returns an array containing the numbers of any numerical -- keys that have non-nil values, sorted in numerical order. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.keySubset(t) checkType('keySubset', 1, t, 'table') local nums = {} for k, v in pairs(t) do		if isPositiveInteger(k) then nums[#nums + 1] = k		end end

table.sort(nums) return nums end

- -- function: reverseIpairs(t: table) -- -- Returns a iterator function to iterate backwards over a sequence table. -- This works like `ipairs` except it works backwards, and it provides an additional -- value in the iteration, `start`. The `start` value is the index the function -- started iterating at. -- -[ EXAMPLE ] -- for i, v, start in table.reverseIpairs(t) do --	 -- code block -- end - function p.reverseIpairs(t) checkType('reverseIpairs', 1, t, 'table') customArgError(not p.isSequence(t), 'reverseIpairs', 1, 'provided table is not a sequence') local len = p.length(t) return function(a, i)		checkType(2, i, 'number') checkType(1, a, 'table') i = i - 1 local v = a[i] if v ~= nil then return i, v, len end end, t, len+1 end

- -- function: reverse(t: table) -- -- Reverses the table in place. The first array element becomes the last, and -- the last array element becomes the first. - function p.reverse(t) checkType('reverse', 1, t, 'table') customArgError(not p.isSequence(t), 'reverse', 1, 'provided table is not a sequence') local tmp = {} for _, v in p.reverseIpairs(t) do		p.push(tmp, v)	end p.empty(t) for i, v in ipairs(tmp) do		t[i] = v	end return t end

- -- function: empty(t: table) -- -- Empties the table of all keys. - function p.empty(t) checkType('empty', 1, t, 'table')

for k, v in pairs(t) do		t[k] = nil end return t end

- -- function: isEmpty(t: table) -- -- Checks if a table is completly empty - function p.isEmpty(t) checkType('empty', 1, t, 'table')

return next(t) == nil end

- -- function: clean(t: table) -- -- Removes any indexes from the table which are nil, or are not a postive number. - function p.clean(t) checkType('clean', 1, t, 'table') local ret = {} local nums = p.keySubset(t) for _, num in ipairs(nums) do		ret[#ret+1] = t[num] end return ret end

-- function: affixNums(t: table, prefix?: string, suffix?: string) -- -- This takes a table and returns an array containing the numbers of keys with the -- specified prefix and suffix. For example, for the table -- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will -- return {1, 3, 6}. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.affixNums(t, prefix, suffix) checkType('affixNums', 1, t, 'table') checkType('affixNums', 2, prefix, 'string', true) checkType('affixNums', 3, suffix, 'string', true)

local function cleanPattern(s) -- Cleans a pattern so that the magic characters %.[]*+-?^$ are interpreted literally. return s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1') end

prefix = prefix or '' suffix = suffix or '' prefix = cleanPattern(prefix) suffix = cleanPattern(suffix) local pattern = table.concat{ '^', prefix, '([1-9]%d*)', suffix, '$' }

local nums = {} for k, v in pairs(t) do		if type(k) == 'string' then local num = mw.ustring.match(k, pattern) if num then nums[#nums + 1] = tonumber(num) end end end table.sort(nums) return nums end

-- function: numData(t: table, compress?: boolean) -- -- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table -- of subtables in the format -- { [1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'} } -- Keys that don't end with an integer are stored in a subtable named "other". -- The compress option compresses the table so that it can be iterated over with -- ipairs. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.numData(t, compress) checkType('numData', 1, t, 'table') checkType('numData', 2, compress, 'boolean', true) local ret = {} for k, v in pairs(t) do		local prefix, num = mw.ustring.match(tostring(k), '^([^0-9]*)([1-9][0-9]*)$') if num then num = tonumber(num) local subtable = ret[num] or {} if prefix == '' then -- Positional parameters match the blank string; put them at the start of the subtable instead. prefix = 1 end subtable[prefix] = v			ret[num] = subtable else local subtable = ret.other or {} subtable[k] = v			ret.other = subtable end end if compress then local other = ret.other ret = p.compressSparseArray(ret) ret.other = other end return ret end

- -- function: compressSparseArray(t: table) -- -- This takes an array with one or more nil values, and removes the nil values -- while preserving the order, so that the array can be safely traversed with -- ipairs. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.compressSparseArray(t) checkType('compressSparseArray', 1, t, 'table') local ret = {} local nums = p.keySubset(t) for _, num in ipairs(nums) do		ret[#ret + 1] = t[num] end return ret end

- -- function: sparseIpairs(t: table) -- -- This is an iterator for sparse arrays. It can be used like `ipairs`, but can -- handle nil values. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.sparseIpairs(t) checkType('sparseIpairs', 1, t, 'table') local nums = p.keySubset(t) local i = 0 local lim = #nums return function i = i + 1 if i <= lim then local key = nums[i] return key, t[key] else return nil, nil end end end

-- function: keysToKist(t: table, keySort?: function|boolean, checked?: boolean) -- -- Returns a list of the keys in a table, sorted using either a default -- comparison function or a custom keySort function. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`.

function p.keysToList(t, keySort, checked) if not checked then checkType('keysToList', 1, t, 'table') checkType('keysToList', 2, keySort, { 'function', 'boolean', 'nil' }) end local list = {} local index = 1 for key, value in pairs(t) do		list[index] = key index = index + 1 end if keySort ~= false then keySort = type(keySort) == 'function' and keySort or defaultKeySort table.sort(list, keySort) end return list end

--- -- function: invert(t: table) -- -- Replaces keys with thier values and vice versa for thier values. - function p.invert(t) checkType("invert", 1, t, "table") customArgError(not p.isSequence(t), 'invert', 1, 'table is not a sequence') local ret = {} for i, v in ipairs(t) do		ret[v] = i	end return ret end

- -- function: from(t: table) -- -- Creates a shallow copy of `t`. This means any subtables and functions will be shared. -- Use `deepCopy` for a deep copy function. - function p.from(t) checkType("from", 1, t, "table")

local ret = {} for k, v in p.sortedPairs(t) do		ret[k] = v	end return ret end

- -- function: sequenceToSet(t: table) -- -- Creates a shallow copy of `t`. This means any subtables and functions will be shared. -- Use `deepCopy` for a deep copy function. - function p.sequenceToSet(t) checkType('sequenceToSet', 1, t, 'table') customArgError(not p.isSequence(t), 'invert', 1, 'table is not a sequence') local ret = {} p.each(t, function(v)		ret[v] = true	end) return ret end - -- function: sortedPairs(t: table, keySort?: boolean) -- -- Iterates through a table, with the keys sorted using the keysToList function. -- If there are only numerical keys, sparseIpairs is probably more efficient. -- --[ ATTRIBUTION ] -- This function was taken from `en.wikipedia.org` in `Module:TableTools`. - function p.sortedPairs(t, keySort) checkType('sortedPairs', 1, t, 'table') checkType('sortedPairs', 2, keySort, 'function', true) local list = p.keysToList(t, keySort, true) local i = 0 return function i = i + 1 local key = list[i] if key ~= nil then return key, t[key] else return nil, nil end end end

- -- function: recursiveConcat(t: table, sep?: string, i?: number, j?: number) -- -- Takes all values of this table and any subtables then recursively goes through then -- and adds them to new table, then joins concatnates that table. - function p.recursiveConcat(t, sep, k, l)	checkType('recursiveConcat', 1, t, 'table') customArgError(not p.isSequence(t), 'recursiveConcat', 1, 'provided table is not a sequence') checkType('recursiveConcat', 2, sep, 'string', true) checkType('recursiveConcat', 3, k, 'number', true) checkType('recursiveConcat', 4, l, 'number', true)

return table.concat(p.flat(t), sep or "", k, l) end

- -- function: each(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) -- -- Executes a provided function once for each array element. - function p.each(t, callbackfn)	checkType(1, t, 'table')	checkType(2, callbackfn, "function")	for k, v in ipairs(t) do		callbackfn(v, k, t)	end	return t end

- -- function: format(t: table) -- -- Takes the table and takes each value as an argument and puts it through `string.format`. - function p.format(t) checkType(1, t, { 'table', 'number', 'string' }, true) t = t or '' if type(t) == "string" or type(t) == "number" or not (t[1] or ''):match('%%%a') then return (function			if type(t) == "number" then				return tostring(t)			elseif type(t) == "table" then				return table.concat(t, t.sep or t.s or t.seperator or "")			else				return s			end		end) end p.each(t, function(v, k, t)		t[k] = p.includes({'string', 'number', 'boolean'}, type(v)) and tostring(v) or v	end) local success, result = pcall(string.format, unpack(t)) if not success then local match = { result:match('bad argument #(%d+) .-%(.- expected, got (.-)%)') } if not match[1] then match = { result:match('bad argument #(%d+) .-%((.-)%)') } end if match[1] then error(string.format('invalid value (%s) at index %s in table for \'format\'', match[2] ~= 'no value' and match[2] or 'nil', match[1]), 2) else error(result, 2) end end return result end

- -- function: deepCopy(t: table) -- -- Recursively goes through the table and copies it preserving all identities of -- the subtables. -- [ ATTRIBUTION ]-- -- This function was taken from `en.wikipedia.org` from `Module:TableTools`. - local function _deepCopy(orig, includeMetatable, already_seen) -- Stores copies of tables indexed by the original table. already_seen = already_seen or {} local copy = already_seen[orig] if copy ~= nil then return copy end if type(orig) == 'table' then copy = {} for orig_key, orig_value in pairs(orig) do			copy[_deepCopy(orig_key, includeMetatable, already_seen)] = _deepCopy(orig_value, includeMetatable, already_seen) end already_seen[orig] = copy if includeMetatable then local mt = getmetatable(orig) if mt ~= nil then local mt_copy = _deepCopy(mt, includeMetatable, already_seen) setmetatable(copy, mt_copy) already_seen[mt] = mt_copy end end else -- number, string, boolean, etc copy = orig end return copy end

function p.deepCopy(orig, noMetatable, already_seen) checkType("deepCopy", 3, already_seen, "table", true) return _deepCopy(orig, not noMetatable, already_seen) end

- -- function: dumpObject(t: table) -- -- Takes the table and recursively goes through each and every key, creating -- a string repersenting the whole table -- ---[ ATTRIBUTION ]-- -- This function was taken from the media wiki source file (../includes/engines/LuaCommon/lualib/mw.lua) - function p.dumpObject(object) local doneTable = {} local doneObj = {} local ct = {} local function sorter(a, b)		local ta, tb = type(a), type(b) if ta ~= tb then return ta < tb		end if ta == 'string' or ta == 'number' then return a < b		end if ta == 'boolean' then return tostring(a) < tostring(b) end return false -- Incomparable end local function _dumpObject(object, indent, expandTable) local tp = type(object) if tp == 'number' or tp == 'nil' or tp == 'boolean' then return tostring(object) elseif tp == 'string' then return string.format("%q", object) elseif tp == 'table' then if not doneObj[object] then local s = tostring(object) if s == 'table' then ct[tp] = (ct[tp] or 0) + 1 doneObj[object] = 'table#' .. ct[tp] else doneObj[object] = s					doneTable[object] = true end end if doneTable[object] or not expandTable then return doneObj[object] end doneTable[object] = true

local ret = { doneObj[object], ' {\n' } local mt = getmetatable(object) if mt then ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = 'metatable = ' ret[#ret + 1] = _dumpObject(mt, indent + 2, false) ret[#ret + 1] = "\n" end

local doneKeys = {} for key, value in ipairs(object) do				doneKeys[key] = true ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = _dumpObject(value, indent + 2, true) ret[#ret + 1] = ',\n' end local keys = {} for key in pairs(object) do				if not doneKeys[key] then keys[#keys + 1] = key end end table.sort(keys, sorter) for i = 1, #keys do				local key = keys[i] ret[#ret + 1] = string.rep(" ", indent + 2) ret[#ret + 1] = '[' ret[#ret + 1] = _dumpObject(key, indent + 3, false) ret[#ret + 1] = '] = ' ret[#ret + 1] = _dumpObject(object[key], indent + 2, true) ret[#ret + 1] = ",\n" end ret[#ret + 1] = string.rep(" ", indent) ret[#ret + 1] = '}' return table.concat(ret) else if not doneObj[object] then ct[tp] = (ct[tp] or 0) + 1 doneObj[object] = table.concat{ tostring(object), '#', ct[tp] } end return doneObj[object] end end return _dumpObject(object, 0, true) end p.dump = p.dumpObject - -- function: logObject(t: table) -- -- Calls `dumpObject` on the table then logs it using `mw.log`. - function p.logObject(...) local args for i, v, _ in forEachArgs({'any'}, ...) do		if i == 1 then args = _ end _[i] = p.dumpObject(v) end return mw.log(p.unpack(args)) end p.log = p.logObject

- -- function: tableUtil(t?: table) -- -- Takes the table and makes all the methods above availble. It also includes -- an option to set a metatable to this table. - function p.tableUtil(...) local t, metatable = ... local len = select('#', ...) local doTable if not (((type(t) == "table" or type(metatable) == "table") or t == nil and metatable == nil) and len <= 2) then t = { ... }	end local methods = {} local t = t or {} local mt = { __index = function(_, k)			return methods[k] end, }	if metatable or getmetatable(t) then local metatable = getmetatable(t) or metatable if metatable.__index then setmetatable(methods, { __index = metatable.__index }) metatable.__index = nil end for k, v in pairs(metatable) do			mt[k] = v		end end local keys = { rawset = rawset, rawget = rawget, pairs = pairs, ipairs = ipairs, getmetatable = getmetatable, setmetatable = setmetatable, next = next, pcall = pcall, xpcall = xpcall, tostring = tostring, tonumber = tonumber, type = type, unpack = unpack, len = function(self, countNamed) checkType(1, countNamed, 'boolean', true) if countNamed then return p.length(self) else return #self end end, select = function(self, ops) return select(ops, unpack(self)) end, }	for k, v in pairs(keys) do		methods[k] = v		end for k, v in pairs(p) do		methods[k] = v	end

return setmetatable(t, mt) end

--Finish Module/Exports return p