2020-04-17 00:35:00 +00:00
|
|
|
--[[
|
|
|
|
extra string routines
|
|
|
|
]]
|
|
|
|
|
2020-05-19 02:03:45 +00:00
|
|
|
local path = (...):gsub(".stringx", ".")
|
|
|
|
local assert = require(path .. "assert")
|
|
|
|
|
2020-04-17 00:35:00 +00:00
|
|
|
local stringx = setmetatable({}, {
|
|
|
|
__index = string
|
|
|
|
})
|
|
|
|
|
|
|
|
--split a string on a delimiter into an ordered table
|
2020-05-19 02:03:45 +00:00
|
|
|
function stringx.split(self, delim)
|
|
|
|
assert:type(self, "string", "stringx.split - self", 1)
|
|
|
|
assert:type(delim, "string", "stringx.split - delim", 1)
|
|
|
|
|
2020-04-17 00:45:15 +00:00
|
|
|
--we try to create as little garbage as possible!
|
|
|
|
--only one table to contain the result, plus the split strings.
|
|
|
|
--so we do two passes, and work with the bytes underlying the string
|
|
|
|
--partly because string.find is not compiled on older luajit :)
|
|
|
|
local res = {}
|
2020-04-17 00:35:00 +00:00
|
|
|
local length = self:len()
|
|
|
|
--
|
|
|
|
local delim_length = delim:len()
|
2020-04-17 00:45:15 +00:00
|
|
|
--empty delim? split to individual characters
|
|
|
|
if delim_length == 0 then
|
|
|
|
for i = 1, length do
|
|
|
|
table.insert(res, self:sub(i, i))
|
|
|
|
end
|
|
|
|
return res
|
|
|
|
end
|
2020-04-17 00:35:00 +00:00
|
|
|
local delim_start = delim:byte(1)
|
2020-04-17 00:45:15 +00:00
|
|
|
--pass 1
|
|
|
|
--collect split sites
|
2020-04-17 00:35:00 +00:00
|
|
|
local i = 1
|
|
|
|
while i <= length do
|
|
|
|
--scan for delimiter
|
|
|
|
if self:byte(i) == delim_start then
|
|
|
|
local has_whole_delim = true
|
|
|
|
for j = 2, delim_length do
|
|
|
|
if self:byte(i + j - 1) ~= delim:byte(j) then
|
|
|
|
has_whole_delim = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if has_whole_delim then
|
|
|
|
table.insert(res, i)
|
|
|
|
end
|
|
|
|
--iterate forward
|
|
|
|
i = i + delim_length
|
|
|
|
else
|
|
|
|
--iterate forward
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
end
|
2020-04-17 00:45:15 +00:00
|
|
|
--pass 2
|
|
|
|
--collect substrings
|
2020-04-17 00:35:00 +00:00
|
|
|
i = 1
|
|
|
|
for si, j in ipairs(res) do
|
|
|
|
res[si] = self:sub(i, j-1)
|
|
|
|
i = j + delim_length
|
|
|
|
end
|
|
|
|
--add the final section
|
|
|
|
table.insert(res, self:sub(i, -1))
|
|
|
|
--return the collection
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
|
2020-04-17 00:45:15 +00:00
|
|
|
|
|
|
|
--turn input into a vaguely easy to read string
|
|
|
|
--(which is also able to be parsed by lua in many cases)
|
2020-05-19 02:03:45 +00:00
|
|
|
--todo: multi-line/indent for big tables
|
|
|
|
--todo: support cyclic references without crashing :)
|
2020-04-17 00:45:15 +00:00
|
|
|
function stringx.pretty(input)
|
|
|
|
--if the input is not a table, or it has a tostring metamethod
|
|
|
|
--then we can just use tostring
|
|
|
|
local mt = getmetatable(input)
|
|
|
|
if type(input) ~= "table" or mt and mt.__tostring then
|
|
|
|
local s = tostring(input)
|
|
|
|
--quote strings
|
|
|
|
if type(input) == "string" then
|
|
|
|
s = '"' .. s .. '"'
|
|
|
|
end
|
|
|
|
return s
|
|
|
|
end
|
|
|
|
|
|
|
|
--otherwise, we've got to build up a table representation
|
|
|
|
--collate into member chunks
|
|
|
|
local chunks = {}
|
|
|
|
--(tracking for already-seen elements from ipairs)
|
|
|
|
local seen = {}
|
|
|
|
--sequential part first
|
|
|
|
--(in practice, pairs already does this, but the order isn't guaranteed)
|
|
|
|
for i, v in ipairs(input) do
|
|
|
|
seen[i] = true
|
|
|
|
table.insert(chunks, stringx.pretty(v))
|
|
|
|
end
|
|
|
|
--non sequential follows
|
|
|
|
for k, v in pairs(input) do
|
|
|
|
if not seen[k] then
|
|
|
|
--encapsulate anything that's not a string
|
|
|
|
--todo: also keywords
|
|
|
|
if type(k) ~= "string" then
|
|
|
|
k = "[" .. tostring(k) .. "]"
|
|
|
|
end
|
|
|
|
table.insert(chunks, k .. " = " .. stringx.pretty(v))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return "{" .. table.concat(chunks, ", ") .. "}"
|
|
|
|
end
|
|
|
|
|
2020-04-17 00:35:00 +00:00
|
|
|
return stringx
|