chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
local new_tab = require "table.new"
|
||||
|
||||
|
||||
local type = type
|
||||
local ipairs = ipairs
|
||||
local tostring = tostring
|
||||
local lower = string.lower
|
||||
local sub = string.sub
|
||||
local fmt = string.format
|
||||
local find = string.find
|
||||
local gsub = string.gsub
|
||||
local byte = string.byte
|
||||
local huge = math.huge
|
||||
|
||||
|
||||
local SPACE_BYTE = byte(" ")
|
||||
local TAB_BYTE = byte("\t")
|
||||
local CR_BYTE = byte("\r")
|
||||
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
||||
_M.join = require("pl.stringx").join
|
||||
|
||||
|
||||
--- splits a string (kept for backward compatibility, use splitn instead).
|
||||
-- just a placeholder to the penlight `pl.stringx.split` function
|
||||
-- @function split
|
||||
_M.split = require("pl.stringx").split
|
||||
|
||||
|
||||
local function split_once_common(value, pattern, plain)
|
||||
if value == nil then
|
||||
return nil, nil
|
||||
elseif pattern == nil then
|
||||
return value, nil
|
||||
elseif pattern == "" then
|
||||
return "", value
|
||||
elseif value == "" then
|
||||
return "", nil
|
||||
end
|
||||
|
||||
local s, e = find(value, pattern, nil, plain)
|
||||
if not s then
|
||||
return value, nil
|
||||
end
|
||||
|
||||
if s == 1 and e == 1 then
|
||||
return "", (sub(value, e + 1))
|
||||
end
|
||||
|
||||
return (sub(value, 1, s - 1)), (sub(value, e + 1))
|
||||
end
|
||||
|
||||
|
||||
--- splits a string once with a plain delimiter.
|
||||
-- @function split_once
|
||||
function _M.split_once(value, delim)
|
||||
return split_once_common(value, delim, true)
|
||||
end
|
||||
|
||||
|
||||
--- splits a string once with a pattern.
|
||||
-- @function split_once
|
||||
function _M.psplit_once(value, pattern)
|
||||
return split_once_common(value, pattern, false)
|
||||
end
|
||||
|
||||
|
||||
-- This code is optimized, you can find microbenchmarks in:
|
||||
-- https://github.com/Kong/kong/pull/14388
|
||||
--
|
||||
-- Note that the results may also vary a bit on different architectures.
|
||||
--
|
||||
-- Here is a small part of that:
|
||||
-- local monotonic_msec = require("resty.core.time").monotonic_msec
|
||||
-- local update_time = ngx.update_time
|
||||
-- local splitn = require("kong.tools.string").splitn
|
||||
-- local TEST_SET = {
|
||||
-- "",
|
||||
-- ",",
|
||||
-- ",,",
|
||||
-- "a,,b",
|
||||
-- "a,b,c",
|
||||
-- "aaa bbb,cccc dddd,eeeeee,ffff,,ggg,hhhhhhh,placeholderplaceholderplaceholderplaceholderplaceholderplaceholderplaceholder,ii",
|
||||
-- "aaa bbb,cccc dddd,eeeeee,ffff,,ggg,hhhhhhh,placeholderplaceholderplaceholderplaceholderplaceholderplaceholderplaceholder,ii, jjj",
|
||||
-- }
|
||||
-- local TEST_COUNT = #TEST_SET
|
||||
-- local ITERATIONS = 1000000
|
||||
-- update_time()
|
||||
-- local s = monotonic_msec()
|
||||
-- for _ = 1, ITERATIONS do
|
||||
-- for i = 1, TEST_COUNT do
|
||||
-- local _ = splitn(TEST_SET[i], ",")
|
||||
-- end
|
||||
-- end
|
||||
-- update_time()
|
||||
-- local e = monotonic_msec()
|
||||
-- print("took: ", e - s, " ms")
|
||||
local function splitn_common(value, pattern, n, plain)
|
||||
local limit = n or huge
|
||||
if limit < 1 or value == nil then
|
||||
return {}, 0
|
||||
|
||||
elseif limit == 1 or pattern == nil then
|
||||
return { value }, 1
|
||||
|
||||
elseif pattern == "" then
|
||||
if value == "" then
|
||||
return { "", "" }, 2
|
||||
end
|
||||
|
||||
local size = #value
|
||||
if size == 1 then
|
||||
if limit == 2 then
|
||||
return { "", value }, 2
|
||||
else
|
||||
return { "", value, "" }, 3
|
||||
end
|
||||
end
|
||||
|
||||
size = limit >= size + 2 and size + 2 or limit
|
||||
local t = new_tab(size, 0)
|
||||
t[1] = ""
|
||||
for i = 2, size do
|
||||
t[i] = sub(value, i - 1, i < size and i - 1 or nil)
|
||||
end
|
||||
return t, size
|
||||
|
||||
elseif value == "" then
|
||||
return { "" }, 1
|
||||
end
|
||||
|
||||
local p = 1
|
||||
local i = 1
|
||||
local t = new_tab(n or 10, 0)
|
||||
::again::
|
||||
if i < limit then
|
||||
local s, e = find(value, pattern, p, plain)
|
||||
if s then
|
||||
t[i] = sub(value, p, s - 1)
|
||||
i = i + 1
|
||||
p = e + 1
|
||||
goto again
|
||||
end
|
||||
end
|
||||
t[i] = sub(value, p)
|
||||
return t, i
|
||||
end
|
||||
|
||||
|
||||
local function noop_iter() end
|
||||
local function once_iter(invariant, control)
|
||||
return invariant ~= control and invariant or nil
|
||||
end
|
||||
local function split_iter(t)
|
||||
local i = t[0] or 1
|
||||
t[0] = i + 1
|
||||
return t[i]
|
||||
end
|
||||
|
||||
|
||||
--- splits a string with a plain delimiter (much faster than the split above).
|
||||
-- @function splitn
|
||||
function _M.splitn(value, delim, n)
|
||||
return splitn_common(value, delim, n, true)
|
||||
end
|
||||
|
||||
|
||||
--- splits a string with a pattern.
|
||||
-- @function psplitn
|
||||
function _M.psplitn(value, pattern, n)
|
||||
return splitn_common(value, pattern, n, false)
|
||||
end
|
||||
|
||||
|
||||
--- string splitting iterator (plain delimiter).
|
||||
-- @function isplitn
|
||||
function _M.isplitn(value, delim, n)
|
||||
local res, count = splitn_common(value, delim, n, true)
|
||||
if count == 0 then
|
||||
return noop_iter
|
||||
elseif count == 1 then
|
||||
return once_iter, res[1]
|
||||
end
|
||||
return split_iter, res
|
||||
end
|
||||
|
||||
|
||||
--- string splitting iterator (pattern delimiter).
|
||||
-- @function ipsplitn
|
||||
function _M.ipsplitn(value, pattern, n)
|
||||
local res, count = splitn_common(value, pattern, n, false)
|
||||
if count == 0 then
|
||||
return noop_iter
|
||||
elseif count == 1 then
|
||||
return once_iter, res[1]
|
||||
end
|
||||
return split_iter, res
|
||||
end
|
||||
|
||||
|
||||
--- strips whitespace from a string.
|
||||
-- @function strip
|
||||
function _M.strip(value)
|
||||
if value == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
-- TODO: do we want to operate on non-string values (kept for backward compatibility)?
|
||||
if type(value) ~= "string" then
|
||||
value = tostring(value) or ""
|
||||
end
|
||||
|
||||
if value == "" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local s = 1 -- position of the leftmost non-whitespace char
|
||||
::spos::
|
||||
local b = byte(value, s)
|
||||
if not b then -- reached the end of the all whitespace string
|
||||
return ""
|
||||
end
|
||||
if b == SPACE_BYTE or (b >= TAB_BYTE and b <= CR_BYTE) then
|
||||
s = s + 1
|
||||
goto spos
|
||||
end
|
||||
|
||||
local e = -1 -- position of the rightmost non-whitespace char
|
||||
::epos::
|
||||
b = byte(value, e)
|
||||
if b == SPACE_BYTE or (b >= TAB_BYTE and b <= CR_BYTE) then
|
||||
e = e - 1
|
||||
goto epos
|
||||
end
|
||||
|
||||
if s ~= 1 or e ~= -1 then
|
||||
value = sub(value, s, e)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
-- Numbers taken from table 3-7 in www.unicode.org/versions/Unicode6.2.0/UnicodeStandard-6.2.pdf
|
||||
-- find-based solution inspired by http://notebook.kulchenko.com/programming/fixing-malformed-utf8-in-lua
|
||||
function _M.validate_utf8(val)
|
||||
local str = tostring(val)
|
||||
local i, len = 1, #str
|
||||
while i <= len do
|
||||
if i == find(str, "[%z\1-\127]", i) then i = i + 1
|
||||
elseif i == find(str, "[\194-\223][\123-\191]", i) then i = i + 2
|
||||
elseif i == find(str, "\224[\160-\191][\128-\191]", i)
|
||||
or i == find(str, "[\225-\236][\128-\191][\128-\191]", i)
|
||||
or i == find(str, "\237[\128-\159][\128-\191]", i)
|
||||
or i == find(str, "[\238-\239][\128-\191][\128-\191]", i) then i = i + 3
|
||||
elseif i == find(str, "\240[\144-\191][\128-\191][\128-\191]", i)
|
||||
or i == find(str, "[\241-\243][\128-\191][\128-\191][\128-\191]", i)
|
||||
or i == find(str, "\244[\128-\143][\128-\191][\128-\191]", i) then i = i + 4
|
||||
else
|
||||
return false, i
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Converts bytes to another unit in a human-readable string.
|
||||
-- @tparam number bytes A value in bytes.
|
||||
--
|
||||
-- @tparam[opt] string unit The unit to convert the bytes into. Can be either
|
||||
-- of `b/B`, `k/K`, `m/M`, or `g/G` for bytes (unchanged), kibibytes,
|
||||
-- mebibytes, or gibibytes, respectively. Defaults to `b` (bytes).
|
||||
-- @tparam[opt] number scale The number of digits to the right of the decimal
|
||||
-- point. Defaults to 2.
|
||||
-- @treturn string A human-readable string.
|
||||
-- @usage
|
||||
--
|
||||
-- bytes_to_str(5497558) -- "5497558"
|
||||
-- bytes_to_str(5497558, "m") -- "5.24 MiB"
|
||||
-- bytes_to_str(5497558, "G", 3) -- "5.120 GiB"
|
||||
--
|
||||
function _M.bytes_to_str(bytes, unit, scale)
|
||||
local u = lower(unit or "")
|
||||
|
||||
if u == "" or u == "b" then
|
||||
return fmt("%d", bytes)
|
||||
end
|
||||
|
||||
scale = scale or 2
|
||||
|
||||
if type(scale) ~= "number" or scale < 0 then
|
||||
error("scale must be equal or greater than 0", 2)
|
||||
end
|
||||
|
||||
local fspec = fmt("%%.%df", scale)
|
||||
|
||||
if u == "k" then
|
||||
return fmt(fspec .. " KiB", bytes / 2^10)
|
||||
end
|
||||
|
||||
if u == "m" then
|
||||
return fmt(fspec .. " MiB", bytes / 2^20)
|
||||
end
|
||||
|
||||
if u == "g" then
|
||||
return fmt(fspec .. " GiB", bytes / 2^30)
|
||||
end
|
||||
|
||||
error("invalid unit '" .. unit .. "' (expected 'k/K', 'm/M', or 'g/G')", 2)
|
||||
end
|
||||
|
||||
|
||||
local SCALES = {
|
||||
k = 1024,
|
||||
K = 1024,
|
||||
m = 1024 * 1024,
|
||||
M = 1024 * 1024,
|
||||
g = 1024 * 1024 * 1024,
|
||||
G = 1024 * 1024 * 1024,
|
||||
}
|
||||
|
||||
function _M.parse_ngx_size(str)
|
||||
assert(type(str) == "string", "Parameter #1 must be a string")
|
||||
|
||||
local len = #str
|
||||
local unit = sub(str, len)
|
||||
local scale = SCALES[unit]
|
||||
|
||||
if scale then
|
||||
len = len - 1
|
||||
|
||||
else
|
||||
scale = 1
|
||||
end
|
||||
|
||||
local size = tonumber(sub(str, 1, len)) or 0
|
||||
|
||||
return size * scale
|
||||
end
|
||||
|
||||
|
||||
local try_decode_base64
|
||||
do
|
||||
local decode_base64 = ngx.decode_base64
|
||||
local decode_base64url = require "ngx.base64".decode_base64url
|
||||
|
||||
local function decode_base64_str(str)
|
||||
if type(str) == "string" then
|
||||
return decode_base64(str)
|
||||
or decode_base64url(str)
|
||||
or nil, "base64 decoding failed: invalid input"
|
||||
|
||||
else
|
||||
return nil, "base64 decoding failed: not a string"
|
||||
end
|
||||
end
|
||||
|
||||
function try_decode_base64(value)
|
||||
if type(value) == "table" then
|
||||
for i, v in ipairs(value) do
|
||||
value[i] = decode_base64_str(v) or v
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
if type(value) == "string" then
|
||||
return decode_base64_str(value) or value
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
end
|
||||
_M.try_decode_base64 = try_decode_base64
|
||||
|
||||
|
||||
local replace_dashes
|
||||
local replace_dashes_lower
|
||||
do
|
||||
local str_replace_char
|
||||
|
||||
if ngx and ngx.config.subsystem == "http" then
|
||||
|
||||
-- 1,000,000 iterations with input of "my-header":
|
||||
-- string.gsub: 81ms
|
||||
-- ngx.re.gsub: 74ms
|
||||
-- loop/string.buffer: 28ms
|
||||
-- str_replace_char: 14ms
|
||||
str_replace_char = require("resty.core.utils").str_replace_char
|
||||
|
||||
else -- stream subsystem
|
||||
str_replace_char = function(str, ch, replace)
|
||||
if not find(str, ch, nil, true) then
|
||||
return str
|
||||
end
|
||||
|
||||
return gsub(str, ch, replace)
|
||||
end
|
||||
end
|
||||
|
||||
replace_dashes = function(str)
|
||||
return str_replace_char(str, "-", "_")
|
||||
end
|
||||
|
||||
replace_dashes_lower = function(str)
|
||||
return str_replace_char(str:lower(), "-", "_")
|
||||
end
|
||||
end
|
||||
_M.replace_dashes = replace_dashes
|
||||
_M.replace_dashes_lower = replace_dashes_lower
|
||||
|
||||
|
||||
return _M
|
||||
Reference in New Issue
Block a user