460 lines
13 KiB
Lua
460 lines
13 KiB
Lua
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local tostring = tostring
|
|
local type = type
|
|
local concat = table.concat
|
|
local insert = table.insert
|
|
local getn = table.getn
|
|
local str_rep = string.rep
|
|
local sub = string.sub
|
|
local find = string.find
|
|
local reverse = string.reverse
|
|
local gmatch = string.gmatch
|
|
local format = string.format
|
|
local getmetatable = getmetatable
|
|
local nkeys = table.nkeys -- luajit 2.1
|
|
local isarray = table.isarray -- luajit 2.1
|
|
local pcall = pcall
|
|
local select = select
|
|
|
|
local utils = {
|
|
starts_with = string.startswith
|
|
}
|
|
|
|
--- Total number of elements in this table.
|
|
--- Supported __len metamethod and table.nkeys (if possible)
|
|
--- Note: iterators (tables with __paris metamethod) returns 0
|
|
--- @param t table
|
|
--- @param iterate boolean if t is iterator (has __pairs) then iterate it
|
|
--- @return number
|
|
function utils.nkeys(t, iterate)
|
|
local mt = getmetatable(t)
|
|
if mt then
|
|
if mt.__len then
|
|
return t:__len()
|
|
elseif mt.__pairs then -- skip iterators
|
|
if iterate then
|
|
local i = 0
|
|
for _ in mt.__pairs(t) do i = i + 1 end
|
|
return i
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
elseif nkeys then
|
|
return nkeys(t)
|
|
elseif isarray and isarray(t) then
|
|
return getn(t)
|
|
else
|
|
local i = 0
|
|
for _ in pairs(t) do i = i + 1 end
|
|
return i
|
|
end
|
|
end
|
|
|
|
--- Escape any Lua 'magic' characters in a string
|
|
--- @param s string the input string
|
|
--- @return string
|
|
function utils.escape(s)
|
|
return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'))
|
|
end
|
|
|
|
--- Trim any whitespace or present chars
|
|
--- @param s string
|
|
--- @param left boolean
|
|
--- @param right boolean
|
|
--- @param chrs string|nil
|
|
--- @return string
|
|
function utils.trim(s, left, right, chrs)
|
|
if not chrs then
|
|
chrs = '%s'
|
|
else
|
|
chrs = '[' .. utils.escape(chrs) .. ']'
|
|
end
|
|
local f = 1
|
|
local t
|
|
if left then
|
|
local i1, i2 = find(s,'^'..chrs..'*')
|
|
if i2 >= i1 then
|
|
f = i2+1
|
|
end
|
|
end
|
|
if right then
|
|
if #s < 200 then
|
|
local i1,i2 = find(s,chrs..'*$',f)
|
|
if i2 >= i1 then
|
|
t = i1-1
|
|
end
|
|
else
|
|
local rs = reverse(s)
|
|
local i1,i2 = find(rs, '^'..chrs..'*')
|
|
if i2 >= i1 then
|
|
t = -i2
|
|
end
|
|
end
|
|
end
|
|
return sub(s,f,t)
|
|
end
|
|
|
|
--- @param s string
|
|
--- @return string
|
|
function utils.strip(s)
|
|
return utils.trim(s, true, true)
|
|
end
|
|
|
|
--- Count all instances of substring in string.
|
|
--- @param s string
|
|
--- @param subs string
|
|
---
|
|
function utils.strcount(s, subs)
|
|
local i1,i2 = find(s, subs, nil, true)
|
|
local k = 0
|
|
while i1 do
|
|
if i2 > #s then break end
|
|
k = k + 1
|
|
i1,i2 = find(s, subs, i2+1, true)
|
|
end
|
|
return k
|
|
end
|
|
|
|
--- Utility function that finds any patterns that match a long string's an open or close.
|
|
-- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with.
|
|
-- Right now, it simply returns the greatest number of them found.
|
|
-- @param s The string
|
|
-- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches.
|
|
local function has_lquote(s)
|
|
local lstring_pat = '([%[%]])(=*)%1'
|
|
local equals, new_equals, _
|
|
local finish = 1
|
|
repeat
|
|
_, finish, _, new_equals = s:find(lstring_pat, finish)
|
|
if new_equals then
|
|
equals = max(equals or 0, #new_equals)
|
|
end
|
|
until not new_equals
|
|
|
|
return equals
|
|
end
|
|
|
|
--- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
|
|
--- @param s string the string to be quoted.
|
|
--- @return string the quoted string.
|
|
function utils.quote_string(s)
|
|
-- Find out if there are any embedded long-quote sequences that may cause issues.
|
|
-- This is important when strings are embedded within strings, like when serializing.
|
|
-- Append a closing bracket to catch unfinished long-quote sequences at the end of the string.
|
|
local equal_signs = has_lquote(s .. "]")
|
|
|
|
-- Note that strings containing "\r" can't be quoted using long brackets
|
|
-- as Lua lexer converts all newlines to "\n" within long strings.
|
|
if (s:find("\n") or equal_signs) and not s:find("\r") then
|
|
-- If there is an embedded sequence that matches a long quote, then
|
|
-- find the one with the maximum number of = signs and add one to that number.
|
|
equal_signs = ("="):rep((equal_signs or -1) + 1)
|
|
-- Long strings strip out leading newline. We want to retain that, when quoting.
|
|
if s:find("^\n") then s = "\n" .. s end
|
|
local lbracket, rbracket =
|
|
"[" .. equal_signs .. "[",
|
|
"]" .. equal_signs .. "]"
|
|
s = lbracket .. s .. rbracket
|
|
else
|
|
-- Escape funny stuff. Lua 5.1 does not handle "\r" correctly.
|
|
s = ("%q"):format(s):gsub("\r", "\\r")
|
|
end
|
|
return s
|
|
end
|
|
|
|
--- @param t table
|
|
--- @return table
|
|
function utils.keys(t)
|
|
local keys = {}
|
|
for k, _ in pairs(t) do
|
|
insert(keys, k)
|
|
end
|
|
return keys
|
|
end
|
|
|
|
--- Merge two tables and returns lua representation. Value of table are expressions.
|
|
--- @param t1 table|nil
|
|
--- @param t2 table|nil
|
|
--- @return string|nil
|
|
function utils.implode_hashes(t1, t2)
|
|
local r = {}
|
|
if t1 then
|
|
for k,v in pairs(t1) do
|
|
if type(k) == "number" then
|
|
r[#r + 1] = '[' .. k .. '] = ' .. v
|
|
else
|
|
r[#r + 1] = '["' .. k .. '"] = ' .. v
|
|
end
|
|
end
|
|
if t2 then
|
|
for k,v in pairs(t2) do
|
|
if not t1[k] then
|
|
if type(k) == "number" then
|
|
r[#r + 1] = '[' .. k .. '] = ' .. v
|
|
else
|
|
r[#r + 1] = '["' .. k .. '"] = ' .. v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif t2 then
|
|
for k,v in pairs(t2) do
|
|
if type(k) == "number" then
|
|
r[#r + 1] = '[' .. k .. '] = ' .. v
|
|
else
|
|
r[#r + 1] = '["' .. k .. '"] = ' .. v
|
|
end
|
|
end
|
|
end
|
|
if #r > 0 then
|
|
return concat(r, ",")
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
--- Prepend the indexed table to another table
|
|
--- @param from table
|
|
--- @param to table
|
|
function utils.prepend_table(from, to)
|
|
for i, v in ipairs(from) do
|
|
insert(to, i, v)
|
|
end
|
|
end
|
|
|
|
--- Append the indexed table to another table
|
|
--- @param from table
|
|
--- @param to table
|
|
function utils.append_table(from, to)
|
|
for _, v in ipairs(from) do
|
|
insert(to, v)
|
|
end
|
|
end
|
|
|
|
--- Join elements of the table
|
|
--- @param t table|string
|
|
--- @param delim string
|
|
function utils.join(t, delim)
|
|
if type(t) == "table" then
|
|
return concat(t, delim)
|
|
else
|
|
return tostring(t)
|
|
end
|
|
end
|
|
|
|
--- Outputs values to the stderr. For debug only.
|
|
function utils.var_dump(...)
|
|
io.stderr:write(utils.dump(...) .. "\n" .. debug.traceback() .. "\n")
|
|
end
|
|
|
|
--- Export arguments as string
|
|
--- @return string
|
|
function utils.dump(...)
|
|
local output, n, data = {}, select("#", ...), {...};
|
|
for i = 1, n do
|
|
if type(data[i]) == 'table' then
|
|
insert(output, utils.dump_table(data[i], 0, { [tostring(data[i])] = true}))
|
|
else
|
|
insert(output, tostring(data[i]))
|
|
end
|
|
end
|
|
return concat(output, "\n")
|
|
end
|
|
|
|
--- Serialize the table
|
|
--- @param tbl table
|
|
--- @param indent number отступ в количествах пробелов
|
|
--- @return string
|
|
function utils.dump_table(tbl, indent, tables)
|
|
if not indent then
|
|
indent = 0
|
|
elseif indent > 16 then
|
|
return "*** too deep ***"
|
|
end
|
|
local output = {};
|
|
local mt = getmetatable(tbl)
|
|
local tab = str_rep(" ", indent + 1)
|
|
local iter, ctx, key
|
|
if mt and mt.__pairs then
|
|
iter, ctx, key = mt.__pairs(tbl)
|
|
else
|
|
iter, ctx, key = pairs(tbl)
|
|
end
|
|
for k, v in iter, ctx, key do
|
|
local formatting = tab
|
|
if type(k) == 'string' then
|
|
formatting = formatting .. k .. " = "
|
|
elseif type(k) ~= 'number' then
|
|
formatting = formatting .. "[" .. tostring(k) .. "]" .. " = "
|
|
end
|
|
if type(v) == "table" then
|
|
if tables[v] then
|
|
insert(output, formatting .. "*** recursion ***\n")
|
|
--output = output .. formatting .. "*** recursion ***\n"
|
|
elseif type(k) == "string" and k:sub(1, 1) == "_" then
|
|
insert(output, formatting .. "(table) " .. "*** private field with table ***\n")
|
|
else
|
|
tables[v] = true
|
|
insert(output, formatting .. utils.dump_table(v, indent + 1, tables) .. "\n")
|
|
tables[v] = nil
|
|
end
|
|
elseif type(v) == "userdata" then
|
|
local ok, str = pcall(tostring, v)
|
|
if not ok then
|
|
str = "*** could not convert to string (userdata): " .. tostring(str) .. " ***"
|
|
end
|
|
insert(output, formatting .. "(" .. type(v) .. ") " .. str .. "\n")
|
|
else
|
|
insert(output, formatting .. "(" .. type(v) .. ") " .. tostring(v) .. "\n")
|
|
end
|
|
end
|
|
|
|
if #output > 0 then
|
|
return "{\n" .. concat(output, "") .. str_rep(" ", indent) .. "}"
|
|
else
|
|
return "{}"
|
|
end
|
|
end
|
|
|
|
function utils.table_export(t)
|
|
local output = {}
|
|
for k, v in pairs(t) do
|
|
local key
|
|
if type(k) == 'string' then
|
|
key = "[" .. utils.quote_string(k) .. "] = "
|
|
end
|
|
if type(v) == "table" then
|
|
insert(output, k .. utils.table_export_lua(v))
|
|
elseif type(v) == "number" then
|
|
insert(output, k .. v)
|
|
else
|
|
insert(output, k .. utils.quote_string(tostring(v)))
|
|
end
|
|
end
|
|
return "{" .. concat(output, ", ") .. "}"
|
|
end
|
|
|
|
---
|
|
--- @param s string
|
|
--- @param chrs string|nil
|
|
--- @return string
|
|
function utils.ltrim(s, chrs)
|
|
local i1,i2 = find(s,'^'.. (chrs or "%s")..'*')
|
|
if i2 >= i1 then
|
|
return sub(s,i2+1)
|
|
end
|
|
return s
|
|
end
|
|
|
|
---
|
|
--- @param s string
|
|
--- @param chrs string|nil
|
|
--- @return string
|
|
function utils.rtrim(s, chrs)
|
|
local i1,i2 = find(s, (chrs or "%s")..'*$')
|
|
if i2 >= i1 then
|
|
s = sub(s,1,i1-1)
|
|
end
|
|
return s
|
|
end
|
|
|
|
--- Split string by delimiter
|
|
--- @param str string
|
|
--- @param delim string
|
|
--- @param n number
|
|
--- @return table<string>
|
|
function utils.split(str, delim, n)
|
|
local i1,ls, plain = 1,{}, true
|
|
if not delim then
|
|
delim = '%s+'
|
|
plain = false
|
|
end
|
|
if delim == '' then
|
|
return {str}
|
|
end
|
|
while true do
|
|
local i2,i3 = find(str, delim, i1, plain)
|
|
if not i2 then
|
|
local last = sub(str, i1)
|
|
if last ~= '' then insert(ls, last) end
|
|
if #ls == 1 and ls[1] == '' then
|
|
return {}
|
|
else
|
|
return ls
|
|
end
|
|
end
|
|
insert(ls,sub(str,i1,i2-1))
|
|
if n and #ls == n then
|
|
ls[#ls] = sub(str, i1)
|
|
return ls
|
|
end
|
|
i1 = i3 + 1
|
|
end
|
|
end
|
|
|
|
--- Generate lua code for casting value to another type. Supported types:
|
|
--- number, string, table, iterator, boolean, boolean|any, any
|
|
--- @param value string lua code of the value
|
|
--- @param curr_type string current type of the value
|
|
--- @param to_type string required type
|
|
--- @return string lua code
|
|
function utils.cast_lua(value, curr_type, to_type)
|
|
if curr_type == to_type or to_type == "any" then
|
|
return value
|
|
elseif to_type == "number" then
|
|
return "__.n(" .. value .. ")"
|
|
elseif to_type == "string" then
|
|
return "__.s(" .. value .. ")"
|
|
elseif to_type == "table" then
|
|
if curr_type == "iterator" then
|
|
return value
|
|
else
|
|
return "__.t(" .. value .. ")"
|
|
end
|
|
elseif to_type == "iterator" then
|
|
if curr_type == "table" then
|
|
return value
|
|
else
|
|
return "__.i(" .. value .. ")"
|
|
end
|
|
elseif to_type == "boolean" then
|
|
return "__.b(" .. value .. ")"
|
|
elseif to_type == "boolean|any" then
|
|
if curr_type == "boolean" then
|
|
return value
|
|
else
|
|
return "__.b2(" .. value .. ")"
|
|
end
|
|
else
|
|
return value
|
|
end
|
|
end
|
|
|
|
--- Numerate lines in the text
|
|
--- @param text string
|
|
--- @return string
|
|
function utils.numerate_lines(text, tab)
|
|
tab = tab or ""
|
|
local lines, i = {}, 1
|
|
for _, s in ipairs(utils.split(text, "\n")) do
|
|
insert(lines, tab .. format("%3d", i) .. "| " .. s)
|
|
i = i + 1
|
|
end
|
|
return concat(lines, "\n")
|
|
end
|
|
|
|
if not utils.starts_with then
|
|
--- Return True if input-string starts with prefix, otherwise return False.
|
|
--- @param str string
|
|
--- @param prefix string
|
|
--- @param start number
|
|
function utils.starts_with(str, prefix, start)
|
|
start = start or 1
|
|
return str:sub(start, start + #prefix -1) == prefix
|
|
--return str:sub(start or 1, #prefix) == prefix
|
|
end
|
|
end
|
|
|
|
return utils |