Files
2026-07-13 12:32:21 +08:00

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