local sanitable = {
from = {
['\xfe'] = '\xf0';
['\1'] = '\xf1';
['\2'] = '\xf2';
['\3'] = '\xf3';
['\0'] = '\xf4'; -- NULs apparently can't be saved in sqlite,
-- or possibly just player metadata
};
to = {
['\xf0'] = '\xfe';
['\xf1'] = '\1';
['\xf2'] = '\2';
['\xf3'] = '\3';
['\xf4'] = '\0';
};
}
local utf8
if _G.core then
if core.global_exists 'utf8' then
utf8 = _G.utf8
end
else
utf8 = _G.utf8
end
if not utf8 then -- sigh
utf8 = {}
local bptns = {
{0x80, 0x00};
{0xE0, 0xC0};
{0xF0, 0xE0};
{0xF8, 0xF0};
}
local function bl(n)
for i = 1,4 do
local mask, ptn = bptns[i][1], bptns[i][2]
if bit.band(mask,n) == ptn then
return i, bit.bnot(mask)
end
end
-- invalid codepoint
end
local function ub(bytes, ofs) ofs = ofs or 0
local function B(n) return string.byte(bytes, n+ofs) end
local eb, m1 = bl(B(1))
if not eb then return -1 end
local val = bit.band(B(1), m1)
for i = 2,eb do
val = bit.bor(bit.lshift(val, 6), bit.band(0x3F, B(i)))
end
return val
end
function utf8.codepoint(str, n) return ub(str, (n and n-1 or nil)) end
local uMatchPtn = "()([^\x80-\xC1\xF5-\xFF][\x80-\xBF]*)"
function utf8.codes(s)
local nx = string.gmatch(s,uMatchPtn)
return function()
local pos,bytes = nx()
if not pos then return nil end
local by = ub(bytes)
return pos, by
end
end
function utf8.len(s)
local i = 0
for _ in utf8.codes(s) do i=i+1 end
return i
end
function utf8.char(s, ...)
local v
if s <= 0x7F then
v = string.char(s)
elseif s <= 0x7FF then
v = string.char(
bit.bor(0xC0, bit.rshift(s,6)),
bit.bor(0x80, bit.band(s, 0x3F))
)
elseif s <= 0xFFFF then
v = string.char(
bit.bor(0xE0, bit.rshift(s,6*2) ),
bit.bor(0x80, bit.band(bit.rshift(s,6), 0x3F)),
bit.bor(0x80, bit.band( s, 0x3F))
)
elseif s <= 0x10FFFF then
v = string.char(
bit.bor(0xF0, bit.rshift(s,6*3) ),
bit.bor(0x80, bit.band(bit.rshift(s,6*2), 0x3F)),
bit.bor(0x80, bit.band(bit.rshift(s,6*1), 0x3F)),
bit.bor(0x80, bit.band( s, 0x3F))
)
else -- invalid byte
v=""
end
if select('#', ...) > 0 then
return v, utf8.char(...)
else return v end
end
end
local function nToStr(n, b, tbl)
local str = {}
local i = 0
if n < 0 then
n = -n
str[1] = tbl['-']
i = 1
end
if n == 0 then return tbl[0] else repeat i = i + 1
local v = n%b
n = math.floor(n / b)
str[i] = assert(tbl[v])
until n == 0 end
return table.concat(str)
end
return {
utf8 = utf8;
capitalize = function(str)
return string.upper(string.sub(str, 1,1)) .. string.sub(str, 2)
end;
beginswith = function(str,pfx)
if #str < #pfx then return false end
if string.sub(str,1,#pfx) == pfx then
return true, string.sub(str,1 + #pfx)
end
end;
endswith = function(str,sfx)
if #str < #sfx then return false end
if string.sub(str,#sfx) == sfx then
return true, string.sub(str,1,#sfx)
end
end;
explode = function(str,delim,pat) -- this is messy as fuck but it works so im keeping it
local i = 1
local tbl = {}
if pat == nil then pat = false end
repeat
local ss = string.sub(str, i)
local d
if pat then
local matches = {string.match(ss, '()' .. delim .. '()')}
if #matches > 0 then
local start,stop = matches[1], matches[#matches]
d = start
i = i + stop - 1
end
else
local dl = string.len(delim)
d = string.find(ss, delim, 1, not pat)
if d then i = i + d + dl - 1 end
end
if not d then
tbl[#tbl+1] = string.sub(ss,1,string.len(ss))
break
else
tbl[#tbl+1] = string.sub(ss,1,d-1)
end
until i > string.len(str)
return tbl
end;
rand = function(rng, min,max)
if not min then min = 16 end
if not max then max = min end
local str = ''
local r_int = 0x39 - 0x30
local r_upper = r_int + (0x5a - 0x41)
local r_lower = r_upper + (0x7a - 0x61)
for i = 1,rng:int(min,max) do
-- 0x30 -- 0x39
-- 0x41 -- 0x5A
-- 0x61 -- 0x71
local codepoint = rng:int(r_lower)
if codepoint > r_upper then
codepoint = (codepoint - r_upper) + 0x61
elseif codepoint > r_int then
codepoint = (codepoint - r_int) + 0x41
else
codepoint = codepoint + 0x30
end
str = str .. string.char(codepoint)
end
return str
end;
htsan = function(str)
return str:gsub('([<\\])', '\\%1')
end;
chop = function(str)
if string.sub(str, 1,1) == ' ' then
str = string.sub(str, 2)
end
if string.sub(str, #str,#str) == ' ' then
str = string.sub(str, 1, #str - 1)
end
return str
end;
meta_armor = function(str,mark_struct)
-- binary values stored in metadata need to be sanitized so
-- they don't contain values that will disrupt parsing of the
-- KV store, as luanti (stupidly) uses in-band signalling
local sanitized = string.gsub(str, '.', function(char)
if sanitable.from[char] then
return '\xfe' .. sanitable.from[char]
else return char end
end)
if sanitized ~= str and mark_struct then
-- use different type code to mark struct headers for
-- back-compat
return string.gsub(sanitized,'^\xfe\xf0\x99','\xfe\x98')
else return sanitized end
end;
meta_dearmor = function(str,cond)
local dearmor = function(s)
return string.gsub(s, '\xfe([\xf0\xf1\xf2\xf3\xf4])', function(char)
return sanitable.to[char]
end)
end
if cond then
if string.sub(str,1,2) == '\xfe\x98' then
return dearmor(string.gsub(str,'^\xfe\x98','\xfe\xf0\x99'))
else return str end
else return dearmor(str) end
end;
nExp = function(n)
return nToStr(n, 10, {
[0]='⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹';
['-'] = '⁻', ['('] = '⁽', [')'] = '⁾';
})
end;
nIdx = function(n)
return nToStr(n, 10, {
[0]='₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉';
['-'] = '₋', ['('] = '₍', [')'] = '₎';
})
end;
}