Module:Table
From IdleOn MMO Wiki
Documentation for this module may be created at Module:Table/doc
local lu = require('Module:LibraryUtil')
local checkType = lu.checkTypeLight
-- Begin Exports
local p = {}
-- 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
local function defaultKeySort(a, b)
return a < b
end
---------------------------------------------------------------------------------
-- function: merge(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(target, ...)
local seen = {}
checkType('merge', 1, target, 'table')
local function merge(target, merger)
if type(merger) == 'table' then
if p.isSequence(merger) then
p.push(target, unpack(merger))
else
for key, value in pairs(merger) do
local tp = type(value)
if tp == 'table' and not seen[value] then
seen[value] = 1
target[key] = target[key] or {}
merge(target[key], value)
else
target[key] = value
end
end
end
else
p.push(target, merger)
end
end
if select('#', ...) > 1 then
for i, v in ipairs{ ... } do
merge(target, v)
end
else
merge(target, ...)
end
return target
end
---------------------------------------------------------------------------------
-- function: mergeRight(target: table, ...items: any)
--
-- Works like `.merge()` except in reverse.
---------------------------------------------------------------------------------
function p.mergeRight(target, ...)
local seen = {}
checkType('mergeRight', 1, target, 'table')
local function merge(target, merger)
if type(merger) == 'table' then
if p.isSequence(merger) then
p.unshift(target, unpack(merger))
else
for key, value in pairs(merger) do
local tp = type(value)
if tp == 'table' and not seen[value] then
seen[value] = 1
target[key] = target[key] or {}
merge(target[key], value)
else
target[key] = value
end
end
end
else
p.unshift(target, merger)
end
end
if select('#', ...) > 1 then
for i, v in p.reverseIpairs{ ... } do
merge(target, v)
end
else
merge(target, ...)
end
return target
end
---------------------------------------------------------------------------------
-- function: mergeNamed(target: table, ...items: any)
--
-- This function takes each entry in `...items` and adds only items with named keys to the table.
-- If the item is not a table, it is ignored.
-- Else, it adds each of its items to the end of `t1`.
---------------------------------------------------------------------------------
function p.mergeNamed(target, ...)
local seen = {}
checkType('merge', 1, target, 'table')
local function merge(target, merger)
if type(merger) == 'table' then
for key, value in pairs(merger) do
if type(key) ~= 'number' then
local tp = type(value)
if tp == 'table' and not seen[value] then
seen[value] = 1
target[key] = target[key] or {}
merge(target[key], value)
else
target[key] = value
end
end
end
end
end
if select('#', ...) > 1 then
for i, v in ipairs{ ... } do
merge(target, v)
end
else
merge(target, ...)
end
return target
end
---------------------------------------------------------------------------------
-- function: unpackRaw(t: table)
--
-- Unpacks a table like `unpack()`, but works with iteration rather than C function.
---------------------------------------------------------------------------------
function p.unpackRaw(t)
checkType('unpackRaw', 1, t, { 'table' })
local ret = {}
local i = 1
while t[i] do
p.push(ret, t[i])
i = i+1
end
return unpack(ret)
end
---------------------------------------------------------------------------------
-- function: length(t: table)
--
-- Gets the length of `t`, as the `#` (length operator) does not work on tables.
-- This function counts all items. To restrict count type, use table.xlength.
----------------------------------------------------------------------------------
function p.length(t)
checkType('length', 1, t, 'table')
local i = 0
for k in pairs(t) do
i = i + 1
end
return i
end
---------------------------------------------------------------------------------
-- function: xlength(t: table, countNamed?: boolean, countPositional?: boolean)
--
-- Gets the length of `t`, as the `#` (length operator) does not work on tables.
-- Use countNamed and countPositional to restrict which to count.
-- This function runs slower than table.length.
----------------------------------------------------------------------------------
function p.xlength(t, countNamed, countPositional)
checkType('length', 1, t, 'table')
checkType('length', 2, countNamed, 'boolean', true)
checkType('length', 3, countPositional, 'boolean', true)
countNamed = countNamed == nil and true or countNamed
countPositional = countPositional == nil and true or countPositional
local i = 0
for k in pairs(t) do
if type(k) == 'number' then
if countPositional then
i = i + 1
end
else
if countNamed then
i = i + 1
end
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('slice', 1, t, { 'table' })
checkType('slice', 2, startIndex, { 'number' }, true)
checkType('slice', 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, ...)
checkType('push', 1, t, 'table')
for _, v in ipairs{ ... } 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('makeReadonlyTable', 1, t, 'table', true)
checkType('makeReadonlyTable', 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: isStaticClass(t: table)
--
-- Checks if a class is static.
---------------------------------------------------------------------------------
function p.isStaticClass(t)
local mt = getmetatable(t)
return type(t) == 'table' and mt and mt.__isClass and not mt.__constructorCalled
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
---------------------------------------------------------------------------------
-- Base helper methods for .makeClass()
---------------------------------------------------------------------------------
local baseMethods = {}
local baseStaticMethods = {}
function baseMethods:instanceof(class)
checkType('instanceof', 1, class, { 'table' })
assertTrue(p.isClass(class), 'class expected', 2)
assertTrue(p.isStaticClass(class), 'the class must be static', 2)
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
function baseStaticMethods:checkSelf(toCheck)
if type(toCheck) ~= 'table' or toCheck.static.constructor ~= self.constructor then
local method = getStackName()
error(string.format(
'%sinvalid self object. Did you call .%s() with a dot instead of a colon, i.e. ' ..
'%s%s(...) instead of %s%s(...), or did you call the method directly from the prototype?',
self.name and self.name .. ': ' or '', method, self.name and self.name .. '.' or '.', method, self.name and self.name .. ':' or ':', method
), 3)
end
end
function baseStaticMethods:extendPrototype(methods)
checkType('extendPrototype', 1, methods, { 'table' })
p.merge(self.prototype, methods)
return self
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)
local tpConstructor = type(constructor)
if tpConstructor == 'table' and (methodIsClass or (methods == nil and parentClass == nil and p.length(options) == 0)) and not constructorIsClass then
parentClass = methods
methods = constructor
origConstructor, constructor = nil, nil
elseif constructorIsClass then
parentClass = constructor
origConstructor, constructor = nil, nil
elseif tpConstructor == 'table' and type(methods) == 'table' then
options = methods
methods = nil
parentClass = nil
elseif tpConstructor == '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',
'name',
}
local reservedNames = p.sequenceToSet{
'parent',
'methods',
'static',
'__proto__',
'super',
'__constructorCalled',
'__isClass',
'name',
}
local allowedTypes = p.sequenceToSet{
'static',
}
if constructor == nil then
constructor = parentClass and tmpChildFunc or tmpFunc
end
function util:methodType(f, searchType)
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)
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,
name = options.name,
__isClass = true,
}
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
p.merge(mt.methods, baseMethods)
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
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 mType == 'static' or mType2 == 'static' then
return rawset(mt.static, k, v[2])
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
elseif k == '__tostring' and t2.name then
mt[k] = function(self)
return '[class ' .. name .. ']'
end
end
end
mt.__metatable = mt
setmetatable(t1, mt)
local constructorValue = constructor(t1, ...)
if t1.parent and p.isStaticClass(t1.parent) 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.name = options.name
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
p.merge(mt.methods, baseStaticMethods)
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 mType, mType2 = util:methodType(value)
if staticReservedNames[k] then
return mt[k]
elseif mType == 'static' or mType == '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)
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
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, ...)
checkType('unshift', 1, t, 'table')
if select('#', ...) > 1 then
for _, v in p.reverseIpairs{ ... } 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')
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: mapNamed(t: table, callback: function(v: any, i?: number, t?: table) => any)
--
-- Similar to map, but iterates through a table
-- Creates a new table and populates with results from the callback function.
---------------------------------------------------------------------------------
function p.mapNamed(t, callback)
checkType('mapWith', 1, t, 'table')
checkType('mapWith', 2, callback, 'function')
local ret = {}
for k, v in pairs(t) do
ret[k] = callback(k, v, t)
end
return ret
end
---------------------------------------------------------------------------------
-- function: mapWith(t: table, callback: function(v: any, i?: number, t?: table) => any)
--
-- Maps over a table and sets the corresponding key with the callback's return value.
---------------------------------------------------------------------------------
function p.mapWith(t, callback)
checkType('mapWith', 1, t, 'table')
checkType('mapWith', 2, callback, 'function')
for k, v in pairs(t) do
t[k] = callback(k, v, t)
end
return t
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.
---------------------------------------------------------------------------------
function p.filter(t, callbackfn)
checkType('filter', 1, t, 'table')
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: filterWith(t: table, callbackfn: function(v: any, i?: number, t?: table))
--
-- Returns the same table with any values that did not pass the filter function removed.
---------------------------------------------------------------------------------
function p.filterWith(t, callbackfn)
checkType('filterWith', 1, t, 'table')
checkType('filterWith', 2, callbackfn, 'function')
local temp = p.from(t)
p.empty(t)
for i, v in ipairs(temp) do
if callbackfn(v, i, t) then
p.push(t, v)
end
end
return t
end
---------------------------------------------------------------------------------
-- function: filterNamed(t: table, callbackfn: function(v: any, i?: number, t?: table))
--
-- Creates a new table with all key/value pairs that pass the test implemented by the provided function.
---------------------------------------------------------------------------------
function p.filterNamed(t, callbackfn)
checkType('filterNamed', 1, t, 'table')
checkType('filterNamed', 2, callbackfn, 'function')
local ret = {}
for k, v in pairs(t) do
if callbackfn(k, v, t) then
ret[k] = v
end
end
return ret
end
---------------------------------------------------------------------------------
-- function: filterWithNamed(t: table, callbackfn: function(v: any, i?: number, t?: table))
--
-- Return the same table with any key/value pairs that did not pass the filter function removed.
---------------------------------------------------------------------------------
function p.filterWithNamed(t, callbackfn)
checkType('filterWithNamed', 1, t, 'table')
checkType('filterWithNamed', 2, callbackfn, 'function')
local temp = p.from(t)
p.empty(t)
for k, v in pairs(temp) do
if callbackfn(k, v, t) then
t[k] = v
end
end
return t
end
---------------------------------------------------------------------------------
-- function: filterEntriesByKey(t: table, keys: Sequence<any>)
--
-- Removes the keys in `keys` from `t`.
---------------------------------------------------------------------------------
function p.filterEntriesByKey(t, keys)
checkType('filterEntriesByKey', 1, t, 'table')
checkType('filterEntriesByKey', 2, keys, 'table', true)
return p.filterNamed(t, function(k)
return not p.includes(keys or {}, k)
end)
end
---------------------------------------------------------------------------------
-- function: removeLast(t: table)
--
-- Removes the last element of the table and returns it.
---------------------------------------------------------------------------------
function p.removeLast(t)
checkType('removeLast', 1, t, 'table')
return table.remove(t, p.length(t, false))
end
p.pop = p.removeLast
---------------------------------------------------------------------------------
-- function: removeFirst(t: table)
--
-- Removes the First element of the table and returns it.
---------------------------------------------------------------------------------
function p.removeFirst(t)
checkType('removeFirst', 1, t, 'table')
return table.remove(t, 1)
end
p.shift = p.removeFirst
---------------------------------------------------------------------------------
-- function: flat(t: table)
--
-- Takes a sequence table and flattens all elements down into a single-depth table recursively.
---------------------------------------------------------------------------------
function p.flat(t)
checkType('flat', 1, t, { 'table' })
assertTrue(p.isSequence(t), 'bad argument #1 to flat (table is not a sequence)', 2)
local ret = {}
local seen = {}
local function _recurse(t)
for i, v in ipairs(t) do
if type(v) == 'table' and not seen[v] then
_recurse(v)
seen[v] = 1
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('flatMap', 1, t, 'table')
checkType('flatMap', 2, cb, { 'function' })
return p.map(p.flat(t), cb)
end
---------------------------------------------------------------------------------
-- function: Map(items: table)
--
-- Creates a Map.
---------------------------------------------------------------------------------
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, 'Map index is nil', 2)
assertFalse(isNaN(k), 'Map index is NaN', 2)
self[k] = v
return self
end
function Map:get(k)
assertFalse(k == nil, 'Map index is nil', 2)
assertFalse(isNaN(k), 'Map index is NaN', 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('forEach', 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
function Map:values()
local ret = {}
for _, val in ipairs(keys) do
p.push(ret, val)
end
return ret
end
function Map:entries()
local ret = {}
for i = 1, #keys do
ret[i] = { keys[i], self[keys[i]] }
end
return ret
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
local function __ipairs(t)
local i, len = 0, #keys
return function()
i = i+1
if keys[i] and i <= len then
return i, ret[keys[i]], keys[i]
else
return nil, nil, nil
end
end
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,
__ipairs = __ipairs,
__tostring = function() return 'map' end
})
return ret
end
---------------------------------------------------------------------------------
-- function: Set(t)
--
-- Turns a table into a set.
---------------------------------------------------------------------------------
function p.Set(t)
checkType('Set', 1, t, 'table', true)
t = t or {}
local ret = {}
local keys = {}
local size = 0
local reservedKeys = {
['size'] = 1,
}
for k, v in pairs(t) do
if type(k) == 'number' then
if not keys[v] then size = size + 1 end
keys[v] = 1
else
if not keys[k] then size = size + 1 end
keys[k] = 1
end
end
local Set = {}
Set.constructor = p.Set
function Set:has(...)
if select('#', ...) == 1 then
local value = ...
assertFalse(value == nil, 'Set value is nil', 2)
assertFalse(isNaN(value), 'Set value is NaN', 2)
for v in pairs(keys) do
if v == value then
return true
end
end
return false
else
local has = {}
for val in forEachArgs({ 'any', required = 1 }, ...) do
has[val] = self:has(val)
end
return has
end
end
function Set:remove(...)
local removed
local len = select('#', ...)
if len == 1 then
assertFalse(... == nil, 'Set value is nil', 2)
assertFalse(isNaN(...), 'Set value is NaN', 2)
local has = self:has(...)
if has then size = size - 1 end
keys[...] = nil
return has
else
removed = {}
for val in forEachArgs({'any', required = 1}, ...) do
removed[val] = self:remove(val)
end
end
end
function Set:clone()
return p.Set(keys)
end
function Set:add(...)
for val in forEachArgs({'any', required = 1}, ...) do
if not keys[val] then size = size + 1 end
keys[val] = 1
end
return self
end
function Set:find(callbackfn)
checkType('find', 1, callbackfn, { 'function' })
for val in self:pairs() do
if callbackfn(val, self) then
return val
end
end
return nil
end
function Set:each(callbackfn)
checkType('each', 1, callbackfn, { 'function' })
for val in pairs(self) do
callbackfn(val, self)
end
return self
end
function Set:map(callbackfn)
checkType('map', 1, callbackfn, { 'function' })
return self:clone():mapWith(callbackfn)
end
function Set:mapWith(callbackfn)
checkType('mapWith', 1, callbackfn, { 'function' })
for val in pairs(self) do
self:remove(val)
self:add(callbackfn(val, self))
end
return self
end
function Set:pairs()
return pairs(self)
end
function Set:ipairs()
return ipairs(self)
end
function Set:filter(callbackfn)
checkType('filter', 1, callbackfn, { 'function' })
return self:clone():filterWith(callbackfn)
end
function Set:filterWith(callbackfn)
checkType('filterWith', 1, callbackfn, { 'function' })
for val in pairs(self) do
if not callbackfn(val, self) then self:remove(val) end
end
return self
end
function Set:intersection(otherSet)
assertTrue(otherSet and otherSet.constructor == p.Set, 'bad argument #1 to \'intersection\' (compare value must be another set)', 2)
local values = {}
for val in pairs(self) do
if otherSet:has(val) then
p.push(values, val)
end
end
return p.Set(values)
end
function Set:difference(otherSet)
assertTrue(otherSet and otherSet.constructor == p.Set, 'bad argument #1 to \'difference\' (compare value must be another set)', 2)
local values = {}
for val in pairs(self) do
if not otherSet:has(val) then
p.push(values, val)
end
end
return p.Set(values)
end
function Set:symDifference(otherSet)
assertTrue(otherSet and otherSet.constructor == p.Set, 'bad argument #1 to \'symDifference\' (compare value must be another set)', 2)
return self:difference(otherSet):union(otherSet:difference(self))
end
function Set:union(otherSet)
assertTrue(otherSet and otherSet.constructor == p.Set, 'bad argument #1 to \'union\' (other value must be another set)', 2)
local values = {}
for val in pairs(self) do
p.push(values, val)
end
return p.Set(values)
end
function Set:merge(...)
for val, i in forEachArgs({'any', required = 1}, ...) do
checkType(i, val, 'table')
if p.constructor == Set.constructor then
for v in pairs(val) do
self:add(v)
end
elseif not p.isSequence(val) then
for v in pairs(val) do
self:add(v)
end
else
for _, v in ipairs(val) do
self:add(v)
end
end
end
end
function Set:clear()
keys = {}
size = 0
return self
end
function Set:values()
return p.keys(keys)
end
return setmetatable(ret, {
__newindex = function(t, k, v)
assertFalse(reservedKeys[k], 'Set property %q is read-only', 2, k)
if not t[k] then size = size + 1 end
return rawset(keys, k, 1)
end,
__index = function(t, k)
if k == 'size' then
return size
elseif Set[k] then
return Set[k]
else
return t:has(k)
end
end,
__pairs = function(t)
local keys = p.keys(keys)
local i = 0
return function()
i = i+1
if keys[i] and i <= size then
return keys[i]
else
return nil
end
end
end,
__ipairs = function(t)
local keys = p.keys(keys)
local i = 0
return function()
i = i+1
if keys[i] and i <= size then
return i, keys[i]
else
return nil, nil
end
end
end,
__tostring = function()
return string.format('Set: { %s }', table.concat(p.map(p.keys(keys), function(k)
if (type(k) == 'table') then
return 'table'
elseif (type(k) == 'function') then
return 'function'
elseif (type(k) == 'string') then
return string.format('"%s"', string.gsub(k, '"', '\\"'))
else
return k
end
end), ', '))
end
})
end
---------------------------------------------------------------------------------
-- function: find(t: table, compare: any)
--
-- Checks each result found by `compare` and compares it against `t`. If an index is found,
-- If `compare` is not a function, it compares each value to `compare`.
-- the result is returned along with it's index. If no value is found, it returns nil.
---------------------------------------------------------------------------------
function p.find(...)
local t, compare = checkArgs({ 'table', { 'any', emptyOk = true } }, ...)
local isFunction = type(compare) == 'function'
for k, v in pairs(t) do
if type(compare) == 'function' and compare(v, k, t) or compare == v then
return v, k
end
end
return
end
---------------------------------------------------------------------------------
-- function: findIndex(t: table, compare: any)
--
-- Very similar to find(), but returns only the index.
---------------------------------------------------------------------------------
function p.findIndex(...)
local _, ret = p.find(checkArgs({ 'table', { 'any', emptyOk = true } }, ...))
return ret
end
---------------------------------------------------------------------------------
-- function: splice(t: table, start: number, deleteCount?: number, ...items: any)
--
-- Removes/replaces/adds table elements in place. It may do the following functions:
-- *Insert elements at a certain index
-- *Remove a number of a element starting at a sertain index
-- *Replace a number of elements at a certain inxex
-- See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice for more info.
---------------------------------------------------------------------------------
function p.splice(t, start, deleteCount, ...)
checkType('splice', 1, t, { 'table' })
checkType('splice', 2, start, { 'number' })
checkType('splice', 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 and - -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')
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')
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')
return p.some(t, v)
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')
assertTrue(callbackfn ~= nil, 'Compare value must not be nil', 2)
local tp = type(callbackfn)
local i = 0
for k, v in pairs(t) do
i = i+1
if tp == 'function' then
if not callbackfn(k, v, t, i) then
return false
end
else
return v == callbackfn
end
end
return true
end
---------------------------------------------------------------------------------
-- function: some(t: table, callbackfn: (v: any, i: number, t: table) => boolean|any)
--
-- 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')
assertTrue(callbackfn ~= nil, 'Compare value must not be nil', 2)
local tp = type(callbackfn)
for k, v in pairs(t) do
if tp == 'function' then
if callbackfn(k, v, t) then
return true
end
else
if v == callbackfn then
return true
end
end
end
return false
end
---------------------------------------------------------------------------------
-- function: reduce(
-- t: table,
-- callbackfn: (accumlator: any, curVal: any, i?: number, t?: table) => any,
-- 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')
checkType('reduce', 2, callbackfn, 'function')
assertTrue(callbackfn('', '', '', '') ~= nil, 'bad argument #1 to reduce (no return value for callback)', 2)
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: (accumlator: any, curVal: any, i?: number, t?: table) => any,
-- 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')
checkType('reduceRight', 2, callbackfn, 'function')
assertTrue(callbackfn('', '', '', '') ~= nil, 'bad argument #1 to reduceRight (no return value for callback)', 2)
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.
---------------------------------------------------------------------------------
function p.keys(t)
checkType('keys', 1, t, 'table')
local ret = {}
for k, v in pairs(t) do
p.push(ret, k)
end
return ret
end
---------------------------------------------------------------------------------
-- function: namedKeys(t: table)
--
-- Returns a table containing all named keys of this table.
---------------------------------------------------------------------------------
function p.namedKeys(t)
checkType('keys', 1, t, 'table')
local ret = {}
for k, v in pairs(t) do
if type(k) ~= 'number' then
p.push(ret, k)
end
end
return ret
end
---------------------------------------------------------------------------------
-- function: values(t: table)
--
-- Returns a table containing all values of this table.
---------------------------------------------------------------------------------
function p.values(t)
checkType('values', 1, t, 'table')
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)
checkType('entries', 1, t, 'table')
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')
local len = p.length(t)
return function(a, i)
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')
local n = #t
local i = 1
while i < n do
t[i], t[n] = t[n], t[i]
i = i + 1
n = n - 1
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('isEmpty', 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 = string.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 = string.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: keysToList(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')
-- assertTrue(p.isSequence(t), 'bad argument #1 to invert (table is not a sequence)', 2)
local ret = {}
for k, v in pairs(t) do
ret[v] = k
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')
assertTrue(p.isSequence(t), 'bad argument #1 to sequenceToSet (table is not a sequence)', 2)
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: sortedPairsByValue(t: table, keySort?: function|boolean)
--
-- Iterates through a table, sorted using either a default comparison
-- function or a custom keySort function on the value
-- Returns a generator
---------------------------------------------------------------------------------
function p.sortedPairsByValue(t, keySort)
checkType('sortedPairsByValue', 1, t, 'table')
checkType('sortedPairsByValue', 2, keySort, { 'function', 'boolean', 'nil' })
local function toArray(t)
local ret = {}
for k, v in pairs(t) do
p.push(ret, { key = k, value = v })
end
return ret
end
local list = toArray(p.deepCopy(t, true))
if keySort ~= false then
keySort = type(keySort) == 'function' and keySort or defaultKeySort
table.sort(list, function(a, b)
a, b = a.value, b.value
return keySort(a, b)
end)
end
local i = 0
return function()
i = i + 1
local data = list[i]
if data ~= nil then
return data.key, data.value
else
return nil, nil
end
end
end
---------------------------------------------------------------------------------
-- function: toCustomArrayNamed(t: table, customFn?: table|generator)
--
-- Iterates through a named table or using a generator, map each values
-- returned by a custom function into a table
-- There is no counterpart for indexed table because that would be the same as p.map
---------------------------------------------------------------------------------
function p.toCustomArrayNamed(t, customFn)
checkType('toCustomArrayNamed', 1, t, { 'function', 'table' })
checkType('toCustomArrayNamed', 2, customFn, { 'function', 'nil' })
local ret = {}
customFn = type(customFn) == 'function' and customFn or function(v, k) return k end
if type(t) == 'function' then -- t as generator
for k, v in t do
p.push(ret, customFn(v, k, t))
end
else -- t as table
for k, v in pairs(t) do
p.push(ret, customFn(v, k, t))
end
end
return ret
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')
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(v: any, k?: number, t?: table)
--
-- Executes a provided function once for each table element.
---------------------------------------------------------------------------------
function p.each(t, callbackfn)
checkType('each', 1, t, 'table')
checkType('each', 2, callbackfn, 'function')
for k, v in ipairs(t) do
callbackfn(v, k, t)
end
return t
end
---------------------------------------------------------------------------------
-- function: eachNamed(t: table, callbackfn: function(v?: any, k: any, t?: table, i?: number)
--
-- Executes a provided function once for key-value pair in a table
---------------------------------------------------------------------------------
function p.eachNamed(t, callbackfn)
checkType('eachNamed', 1, t, 'table')
checkType('eachNamed', 2, callbackfn, 'function')
local i = 0
for k, v in pairs(t) do
i = i+1
callbackfn(v, k, t, i)
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('format', 1, t, { 'table', 'number', 'string' }, true)
t = t or ''
local tp = type(t)
local mt = getmetatable(t)
if tp == 'string' or tp == 'number' or not (t[1] or ''):match('%%%a') then
if tp == 'number' or mt and mt.__tostring then
return tostring(t)
elseif tp == 'table' then
return table.concat(t, t.sep or t.s or t.seperator or '')
elseif t == '' then
return nil
else
return t
end
end
p.each(t, function(v, k, t)
local tp = type(v)
t[k] =
p.includes({'string', 'number', 'boolean'}, tp)
and tostring(v)
or tp == 'table'
and table.concat(v, v.sep)
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
if type(object) == 'table' then
ct[tp] = (ct[tp] or 0) + 1
doneObj[object] = 'table#' .. ct[tp]
else
doneObj[object] = tostring(object)
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.oldLog(getCodeLocation(), p.unpack(args or {}))
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
error('TableUtil is deprecated')
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('len', 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