356 lines
9.7 KiB
Lua
356 lines
9.7 KiB
Lua
local type = type
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local select = select
|
|
local tostring = tostring
|
|
local insert = table.insert
|
|
local setmetatable = setmetatable
|
|
local getmetatable = getmetatable
|
|
|
|
|
|
local _M = {}
|
|
|
|
|
|
_M.EMPTY = require("pl.tablex").readonly({})
|
|
|
|
|
|
--- packs a set of arguments in a table.
|
|
-- Explicitly sets field `n` to the number of arguments, so it is `nil` safe
|
|
_M.pack = function(...) return {n = select("#", ...), ...} end
|
|
|
|
|
|
--- unpacks a table to a list of arguments.
|
|
-- Explicitly honors the `n` field if given in the table, so it is `nil` safe
|
|
_M.unpack = function(t, i, j) return unpack(t, i or 1, j or t.n or #t) end
|
|
|
|
|
|
--- Merges two table together.
|
|
-- A new table is created with a non-recursive copy of the provided tables
|
|
-- @param t1 The first table
|
|
-- @param t2 The second table
|
|
-- @return The (new) merged table
|
|
function _M.table_merge(t1, t2)
|
|
local res = {}
|
|
if t1 then
|
|
for k,v in pairs(t1) do
|
|
res[k] = v
|
|
end
|
|
end
|
|
if t2 then
|
|
for k,v in pairs(t2) do
|
|
res[k] = v
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
|
|
--- Merges two table together but does not replace values from `t1` if `t2` for a given key has `ngx.null` value
|
|
-- A new table is created with a non-recursive copy of the provided tables
|
|
-- @param t1 The first table
|
|
-- @param t2 The second table
|
|
-- @return The (new) merged table
|
|
function _M.null_aware_table_merge(t1, t2)
|
|
local res = {}
|
|
if t1 then
|
|
for k,v in pairs(t1) do
|
|
res[k] = v
|
|
end
|
|
end
|
|
if t2 then
|
|
for k,v in pairs(t2) do
|
|
if res[k] == nil or v ~= ngx.null then
|
|
res[k] = v
|
|
end
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
|
|
--- Checks if a value exists in a table.
|
|
-- @param arr The table to use
|
|
-- @param val The value to check
|
|
-- @return Returns `true` if the table contains the value, `false` otherwise
|
|
function _M.table_contains(arr, val)
|
|
if arr then
|
|
for _, v in pairs(arr) do
|
|
if v == val then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
do
|
|
local floor = math.floor
|
|
local max = math.max
|
|
|
|
local is_array_fast = require "table.isarray"
|
|
|
|
local is_array_strict = function(t)
|
|
local m, c = 0, 0
|
|
for k in pairs(t) do
|
|
if type(k) ~= "number" or k < 1 or floor(k) ~= k then
|
|
return false
|
|
end
|
|
m = max(m, k)
|
|
c = c + 1
|
|
end
|
|
return c == m
|
|
end
|
|
|
|
local is_array_lapis = function(t)
|
|
if type(t) ~= "table" then
|
|
return false
|
|
end
|
|
local i = 0
|
|
for _ in pairs(t) do
|
|
i = i + 1
|
|
if t[i] == nil and t[tostring(i)] == nil then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Checks if a table is an array and not an associative array.
|
|
-- @param t The table to check
|
|
-- @param mode: `"strict"`: only sequential indices starting from 1 are allowed (no holes)
|
|
-- `"fast"`: OpenResty optimized version (holes and negative indices are ok)
|
|
-- `"lapis"`: Allows numeric indices as strings (no holes)
|
|
-- @return Returns `true` if the table is an array, `false` otherwise
|
|
function _M.is_array(t, mode)
|
|
if type(t) ~= "table" then
|
|
return false
|
|
end
|
|
|
|
if mode == "lapis" then
|
|
return is_array_lapis(t)
|
|
end
|
|
|
|
if mode == "fast" then
|
|
return is_array_fast(t)
|
|
end
|
|
|
|
return is_array_strict(t)
|
|
end
|
|
|
|
|
|
--- Checks if a table is an array and not an associative array.
|
|
-- *** NOTE *** string-keys containing integers are considered valid array entries!
|
|
-- @param t The table to check
|
|
-- @return Returns `true` if the table is an array, `false` otherwise
|
|
_M.is_lapis_array = is_array_lapis
|
|
end
|
|
|
|
|
|
--- Deep copies a table into a new table.
|
|
-- Tables used as keys are also deep copied, as are metatables
|
|
-- @param orig The table to copy
|
|
-- @param copy_mt Copy metatable (default is true)
|
|
-- @return Returns a copy of the input table
|
|
function _M.deep_copy(orig, copy_mt)
|
|
if copy_mt == nil then
|
|
copy_mt = true
|
|
end
|
|
local copy
|
|
if type(orig) == "table" then
|
|
copy = {}
|
|
for orig_key, orig_value in next, orig, nil do
|
|
copy[_M.deep_copy(orig_key)] = _M.deep_copy(orig_value, copy_mt)
|
|
end
|
|
if copy_mt then
|
|
setmetatable(copy, _M.deep_copy(getmetatable(orig)))
|
|
end
|
|
else
|
|
copy = orig
|
|
end
|
|
return copy
|
|
end
|
|
|
|
|
|
do
|
|
local clone = require "table.clone"
|
|
|
|
--- Copies a table into a new table.
|
|
-- neither sub tables nor metatables will be copied.
|
|
-- @param orig The table to copy
|
|
-- @return Returns a copy of the input table
|
|
function _M.shallow_copy(orig)
|
|
local copy
|
|
if type(orig) == "table" then
|
|
copy = clone(orig)
|
|
else -- number, string, boolean, etc
|
|
copy = orig
|
|
end
|
|
return copy
|
|
end
|
|
end
|
|
|
|
|
|
--- Merges two tables recursively
|
|
-- For each sub-table in t1 and t2, an equivalent (but different) table will
|
|
-- be created in the resulting merge. If t1 and t2 have a sub-table with the
|
|
-- same key k, res[k] will be a deep merge of both sub-tables.
|
|
-- Metatables are not taken into account.
|
|
-- Keys are copied by reference (if tables are used as keys they will not be
|
|
-- duplicated)
|
|
-- @param t1 one of the tables to merge
|
|
-- @param t2 one of the tables to merge
|
|
-- @return Returns a table representing a deep merge of the new table
|
|
function _M.deep_merge(t1, t2)
|
|
local res = _M.deep_copy(t1)
|
|
|
|
for k, v in pairs(t2) do
|
|
if type(v) == "table" and type(res[k]) == "table" then
|
|
res[k] = _M.deep_merge(res[k], v)
|
|
else
|
|
res[k] = _M.deep_copy(v) -- returns v when it is not a table
|
|
end
|
|
end
|
|
|
|
return res
|
|
end
|
|
|
|
|
|
--- Cycle aware deep copies a table into a new table.
|
|
-- Cycle aware means that a table value is only copied once even
|
|
-- if it is referenced multiple times in input table or its sub-tables.
|
|
-- Tables used as keys are not deep copied. Metatables are set to same
|
|
-- on copies as they were in the original.
|
|
-- @param orig The table to copy
|
|
-- @param remove_metatables Removes the metatables when set to `true`.
|
|
-- @param deep_copy_keys Deep copies the keys (and not only the values) when set to `true`.
|
|
-- @param cycle_aware_cache Cached tables that are not copied (again).
|
|
-- (the function creates this table when not given)
|
|
-- @return Returns a copy of the input table
|
|
function _M.cycle_aware_deep_copy(orig, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
if type(orig) ~= "table" then
|
|
return orig
|
|
end
|
|
|
|
cycle_aware_cache = cycle_aware_cache or {}
|
|
if cycle_aware_cache[orig] then
|
|
return cycle_aware_cache[orig]
|
|
end
|
|
|
|
local copy = _M.shallow_copy(orig)
|
|
|
|
cycle_aware_cache[orig] = copy
|
|
|
|
local mt
|
|
if not remove_metatables then
|
|
mt = getmetatable(orig)
|
|
end
|
|
|
|
for k, v in pairs(orig) do
|
|
if type(v) == "table" then
|
|
copy[k] = _M.cycle_aware_deep_copy(v, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
end
|
|
|
|
if deep_copy_keys and type(k) == "table" then
|
|
local new_k = _M.cycle_aware_deep_copy(k, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
copy[new_k] = copy[k]
|
|
copy[k] = nil
|
|
end
|
|
end
|
|
|
|
if mt then
|
|
setmetatable(copy, mt)
|
|
end
|
|
|
|
return copy
|
|
end
|
|
|
|
|
|
--- Cycle aware merges two tables recursively
|
|
-- The table t1 is deep copied using cycle_aware_deep_copy function.
|
|
-- The table t2 is deep merged into t1. The t2 values takes precedence
|
|
-- over t1 ones. Tables used as keys are not deep copied. Metatables
|
|
-- are set to same on copies as they were in the original.
|
|
-- @param t1 one of the tables to merge
|
|
-- @param t2 one of the tables to merge
|
|
-- @param remove_metatables Removes the metatables when set to `true`.
|
|
-- @param deep_copy_keys Deep copies the keys (and not only the values) when set to `true`.
|
|
-- @param cycle_aware_cache Cached tables that are not copied (again)
|
|
-- (the function creates this table when not given)
|
|
-- @return Returns a table representing a deep merge of the new table
|
|
function _M.cycle_aware_deep_merge(t1, t2, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
cycle_aware_cache = cycle_aware_cache or {}
|
|
local merged = _M.cycle_aware_deep_copy(t1, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
for k, v in pairs(t2) do
|
|
if type(v) == "table" then
|
|
if type(merged[k]) == "table" then
|
|
merged[k] = _M.cycle_aware_deep_merge(merged[k], v, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
else
|
|
merged[k] = _M.cycle_aware_deep_copy(v, remove_metatables, deep_copy_keys, cycle_aware_cache)
|
|
end
|
|
else
|
|
merged[k] = v
|
|
end
|
|
end
|
|
return merged
|
|
end
|
|
|
|
|
|
--- Concatenates lists into a new table.
|
|
function _M.concat(...)
|
|
local result = {}
|
|
for _, t in ipairs({...}) do
|
|
for _, v in ipairs(t) do insert(result, v) end
|
|
end
|
|
return result
|
|
end
|
|
|
|
|
|
local err_list_mt = {}
|
|
|
|
|
|
--- Add an error message to a key/value table.
|
|
-- If the key already exists, a sub table is created with the original and the new value.
|
|
-- @param errors (Optional) Table to attach the error to. If `nil`, the table will be created.
|
|
-- @param k Key on which to insert the error in the `errors` table.
|
|
-- @param v Value of the error
|
|
-- @return The `errors` table with the new error inserted.
|
|
function _M.add_error(errors, k, v)
|
|
if not errors then
|
|
errors = {}
|
|
end
|
|
|
|
if errors and errors[k] then
|
|
if getmetatable(errors[k]) ~= err_list_mt then
|
|
errors[k] = setmetatable({errors[k]}, err_list_mt)
|
|
end
|
|
|
|
insert(errors[k], v)
|
|
else
|
|
errors[k] = v
|
|
end
|
|
|
|
return errors
|
|
end
|
|
|
|
|
|
--- Retrieves a value from table using path.
|
|
-- @param t The source table to retrieve the value from.
|
|
-- @param path Path table containing keys
|
|
-- @return Returns `value` if something was found and `nil` otherwise
|
|
function _M.table_path(t, path)
|
|
local current_value = t
|
|
for _, path_element in ipairs(path) do
|
|
if current_value[path_element] == nil then
|
|
return nil
|
|
end
|
|
|
|
current_value = current_value[path_element]
|
|
end
|
|
|
|
return current_value
|
|
end
|
|
|
|
|
|
return _M
|