MineOS/lib/advancedLua.lua
2017-12-11 21:14:26 +03:00

634 lines
16 KiB
Lua
Executable File

--[[
Advanced Lua Library v1.1 by ECS
This library extends a lot of default Lua methods
and adds some really cool features that haven't been
implemented yet, such as fastest table serialization,
table binary searching, string wrapping, numbers rounding, etc.
]]
local filesystem = require("filesystem")
local unicode = require("unicode")
local bit32 = require("bit32")
-------------------------------------------------- System extensions --------------------------------------------------
function _G.getCurrentScript()
local info
for runLevel = 0, math.huge do
info = debug.getinfo(runLevel)
if info then
if info.what == "main" then
return info.source:sub(2, -1)
end
else
error("Failed to get debug info for runlevel " .. runLevel)
end
end
end
function enum(...)
local args, enums = {...}, {}
for i = 1, #args do
if type(args[i]) ~= "string" then error("Function argument " .. i .. " have non-string type: " .. type(args[i])) end
enums[args[i]] = i
end
return enums
end
function swap(a, b)
return b, a
end
-------------------------------------------------- Bit32 extensions --------------------------------------------------
-- Merge two numbers into one (0xAABB, 0xCCDD -> 0xAABBCCDD)
function bit32.merge(number2, number1)
local cutter = math.ceil(math.log(number1 + 1, 256)) * 8
while number2 > 0 do
number1, number2, cutter = bit32.bor(bit32.lshift(bit32.band(number2, 0xFF), cutter), number1), bit32.rshift(number2, 8), cutter + 8
end
return number1
end
-- Split number to it's own bytes (0xAABBCC -> {0xAA, 0xBB, 0xCC})
function bit32.numberToByteArray(number)
local byteArray = {}
repeat
table.insert(byteArray, 1, bit32.band(number, 0xFF))
number = bit32.rshift(number, 8)
until number <= 0
return byteArray
end
-- Split nubmer to it's own bytes with specified count of bytes (0xAABB, 5 -> {0x00, 0x00, 0x00, 0xAA, 0xBB})
function bit32.numberToFixedSizeByteArray(number, size)
local byteArray, counter = {}, 0
repeat
table.insert(byteArray, 1, bit32.band(number, 0xFF))
number = bit32.rshift(number, 8)
counter = counter + 1
until number <= 0
for i = 1, size - counter do
table.insert(byteArray, 1, 0x0)
end
return byteArray
end
-- Create number from it's own bytes ({0xAA, 0xBB, 0xCC} -> 0xAABBCC)
function bit32.byteArrayToNumber(byteArray)
local result = byteArray[1]
for i = 2, #byteArray do
result = bit32.bor(bit32.lshift(result, 8), byteArray[i])
end
return result
end
-- Create byte from it's bits ({1, 0, 1, 0, 1, 0, 1, 1} -> 0xAB)
function bit32.bitArrayToByte(bitArray)
local number = 0
for i = 1, #bitArray do
number = bit32.bor(bitArray[i], bit32.lshift(number, 1))
end
return number
end
-------------------------------------------------- Math extensions --------------------------------------------------
function math.round(num)
if num >= 0 then
return math.floor(num + 0.5)
else
return math.ceil(num - 0.5)
end
end
function math.roundToDecimalPlaces(num, decimalPlaces)
local mult = 10 ^ (decimalPlaces or 0)
return math.round(num * mult) / mult
end
function math.getDigitCount(num)
return num == 0 and 1 or math.ceil(math.log(num + 1, 10))
end
function math.doubleToString(num, digitCount)
return string.format("%." .. (digitCount or 1) .. "f", num)
end
function math.shortenNumber(number, digitCount)
local shortcuts = {
"K",
"M",
"B",
"T"
}
local index = math.floor(math.log(number, 1000))
if number < 1000 then
return number
elseif index > #shortcuts then
index = #shortcuts
end
return math.roundToDecimalPlaces(number / 1000 ^ index, digitCount) .. shortcuts[index]
end
---------------------------------------------- Filesystem extensions ------------------------------------------------------------------------
-- function filesystem.path(path)
-- return path:match("^(.+%/).") or ""
-- end
-- function filesystem.name(path)
-- return path:match("%/?([^%/]+)%/?$")
-- end
function filesystem.extension(path, lower)
local extension = path:match("[^%/]+(%.[^%/]+)%/?$")
return (lower and extension) and (unicode.lower(extension)) or extension
end
function filesystem.hideExtension(path)
return path:match("(.+)%..+") or path
end
function filesystem.isFileHidden(path)
if path:match("^%..+$") then
return true
end
return false
end
function filesystem.sortedList(path, sortingMethod, showHiddenFiles, filenameMatcher, filenameMatcherCaseSensitive)
if not filesystem.exists(path) then
error("Failed to get file list: directory \"" .. tostring(path) .. "\" doesn't exists")
end
if not filesystem.isDirectory(path) then
error("Failed to get file list: path \"" .. tostring(path) .. "\" is not a directory")
end
local fileList, sortedFileList = {}, {}
for file in filesystem.list(path) do
if not filenameMatcher or string.unicodeFind(filenameMatcherCaseSensitive and file or unicode.lower(file), filenameMatcherCaseSensitive and filenameMatcher or unicode.lower(filenameMatcher)) then
table.insert(fileList, file)
end
end
if #fileList > 0 then
if sortingMethod == "type" then
local extension
for i = 1, #fileList do
extension = filesystem.extension(fileList[i]) or "Script"
if filesystem.isDirectory(path .. fileList[i]) and extension ~= ".app" then
extension = ".01_Folder"
end
fileList[i] = {fileList[i], extension}
end
table.sort(fileList, function(a, b) return unicode.lower(a[2]) < unicode.lower(b[2]) end)
local currentExtensionList, currentExtension = {}, fileList[1][2]
for i = 1, #fileList do
if currentExtension == fileList[i][2] then
table.insert(currentExtensionList, fileList[i][1])
else
table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
for j = 1, #currentExtensionList do
table.insert(sortedFileList, currentExtensionList[j])
end
currentExtensionList, currentExtension = {fileList[i][1]}, fileList[i][2]
end
end
table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
for j = 1, #currentExtensionList do
table.insert(sortedFileList, currentExtensionList[j])
end
elseif sortingMethod == "name" then
sortedFileList = fileList
table.sort(sortedFileList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
elseif sortingMethod == "date" then
for i = 1, #fileList do
fileList[i] = {fileList[i], filesystem.lastModified(path .. fileList[i])}
end
table.sort(fileList, function(a, b) return unicode.lower(a[2]) > unicode.lower(b[2]) end)
for i = 1, #fileList do
table.insert(sortedFileList, fileList[i][1])
end
else
error("Unknown sorting method: " .. tostring(sortingMethod))
end
local i = 1
while i <= #sortedFileList do
if not showHiddenFiles and filesystem.isFileHidden(sortedFileList[i]) then
table.remove(sortedFileList, i)
else
i = i + 1
end
end
end
return sortedFileList
end
function filesystem.directorySize(path)
local size = 0
for file in filesystem.list(path) do
if filesystem.isDirectory(path .. file) then
size = size + filesystem.directorySize(path .. file)
else
size = size + filesystem.size(path .. file)
end
end
return size
end
-------------------------------------------------- Table extensions --------------------------------------------------
local function doSerialize(array, prettyLook, indentationSymbol, indentationSymbolAdder, equalsSymbol, currentRecusrionStack, recursionStackLimit)
local text, indentationSymbolNext, keyType, valueType, stringValue = {"{"}, table.concat({indentationSymbol, indentationSymbolAdder})
if prettyLook then
table.insert(text, "\n")
end
for key, value in pairs(array) do
keyType, valueType, stringValue = type(key), type(value), tostring(value)
if prettyLook then
table.insert(text, indentationSymbolNext)
end
if keyType == "number" then
table.insert(text, "[")
table.insert(text, key)
table.insert(text, "]")
elseif keyType == "string" then
if prettyLook and key:match("^%a") and key:match("^[%w%_]+$") then
table.insert(text, key)
else
table.insert(text, "[\"")
table.insert(text, key)
table.insert(text, "\"]")
end
end
table.insert(text, equalsSymbol)
if valueType == "number" or valueType == "boolean" or valueType == "nil" then
table.insert(text, stringValue)
elseif valueType == "string" or valueType == "function" then
table.insert(text, "\"")
table.insert(text, stringValue)
table.insert(text, "\"")
elseif valueType == "table" then
if currentRecusrionStack < recursionStackLimit then
table.insert(
text,
table.concat(
doSerialize(
value,
prettyLook,
indentationSymbolNext,
indentationSymbolAdder,
equalsSymbol,
currentRecusrionStack + 1,
recursionStackLimit
)
)
)
else
table.insert(text, "\"\"")
end
end
table.insert(text, ",")
if prettyLook then
table.insert(text, "\n")
end
end
-- Удаляем запятую
if prettyLook then
if #text > 2 then
table.remove(text, #text - 1)
end
-- Вставляем заодно уж символ индентации, благо чек на притти лук идет
table.insert(text, indentationSymbol)
else
if #text > 1 then
table.remove(text, #text)
end
end
table.insert(text, "}")
return text
end
function table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit)
checkArg(1, array, "table")
return table.concat(
doSerialize(
array,
prettyLook,
"",
string.rep(indentUsingTabs and " " or " ", indentationWidth or 2),
prettyLook and " = " or "=",
1,
recursionStackLimit or math.huge
)
)
end
function table.unserialize(serializedString)
checkArg(1, serializedString, "string")
local success, result = pcall(load("return " .. serializedString))
if success then
return result
else
return nil, result
end
end
table.toString = table.serialize
table.fromString = table.unserialize
function table.toFile(path, array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit, appendToFile)
checkArg(1, path, "string")
checkArg(2, array, "table")
filesystem.makeDirectory(filesystem.path(path) or "")
local file, reason = io.open(path, appendToFile and "a" or "w")
if file then
file:write(table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit))
file:close()
else
error("Failed to open file for writing: " .. tostring(reason))
end
end
function table.fromFile(path)
checkArg(1, path, "string")
if filesystem.exists(path) then
if filesystem.isDirectory(path) then
error("\"" .. path .. "\" is a directory")
else
local file = io.open(path, "r")
local data = table.unserialize(file:read("*a"))
file:close()
return data
end
else
error("\"" .. path .. "\" doesn't exists")
end
end
local function doTableCopy(source, destination)
for key, value in pairs(source) do
if type(value) == "table" then
destination[key] = {}
doTableCopy(source[key], destination[key])
else
destination[key] = value
end
end
end
function table.copy(tableToCopy)
local tableThatCopied = {}
doTableCopy(tableToCopy, tableThatCopied)
return tableThatCopied
end
function table.binarySearch(t, requestedValue)
local function recursiveSearch(startIndex, endIndex)
local difference = endIndex - startIndex
local centerIndex = math.floor(difference / 2 + startIndex)
if difference > 1 then
if requestedValue >= t[centerIndex] then
return recursiveSearch(centerIndex, endIndex)
else
return recursiveSearch(startIndex, centerIndex)
end
else
if math.abs(requestedValue - t[startIndex]) > math.abs(t[endIndex] - requestedValue) then
return t[endIndex]
else
return t[startIndex]
end
end
end
return recursiveSearch(1, #t)
end
function table.size(t)
local size = 0
for key in pairs(t) do size = size + 1 end
return size
end
function table.contains(t, object)
for _, value in pairs(t) do
if value == object then
return true
end
end
return false
end
function table.indexOf(t, object)
for i = 1, #t do
if t[i] == object then
return i
end
end
end
function table.sortAlphabetically(t)
table.sort(t, function(a, b) return a < b end)
end
-------------------------------------------------- String extensions --------------------------------------------------
function string.brailleChar(a, b, c, d, e, f, g, h)
return unicode.char(10240 + 128*h + 64*g + 32*f + 16*d + 8*b + 4*e + 2*c + a)
end
function string.readUnicodeChar(file)
local byteArray = {string.byte(file:read(1))}
local nullBitPosition = 0
for i = 1, 7 do
if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then
nullBitPosition = i
break
end
end
for i = 1, nullBitPosition - 2 do
table.insert(byteArray, string.byte(file:read(1)))
end
return string.char(table.unpack(byteArray))
end
function string.canonicalPath(str)
return string.gsub("/" .. str, "%/+", "/")
end
function string.optimize(str, indentationWidth)
str = string.gsub(str, "\r\n", "\n")
str = string.gsub(str, " ", string.rep(" ", indentationWidth or 2))
return str
end
function string.optimizeForURLRequests(code)
if code then
code = string.gsub(code, "([^%w ])", function (c)
return string.format("%%%02X", string.byte(c))
end)
code = string.gsub(code, " ", "+")
end
return code
end
function string.unicodeFind(str, pattern, init, plain)
if init then
if init < 0 then
init = -#unicode.sub(str,init)
elseif init > 0 then
init = #unicode.sub(str, 1, init - 1) + 1
end
end
a, b = string.find(str, pattern, init, plain)
if a then
local ap, bp = str:sub(1, a - 1), str:sub(a,b)
a = unicode.len(ap) + 1
b = a + unicode.len(bp) - 1
return a, b
else
return a
end
end
function string.limit(s, limit, mode, noDots)
local length = unicode.len(s)
if length <= limit then return s end
if mode == "left" then
if noDots then
return unicode.sub(s, length - limit + 1, -1)
else
return "" .. unicode.sub(s, length - limit + 2, -1)
end
elseif mode == "center" then
local integer, fractional = math.modf(limit / 2)
if fractional == 0 then
return unicode.sub(s, 1, integer) .. "" .. unicode.sub(s, -integer + 1, -1)
else
return unicode.sub(s, 1, integer) .. "" .. unicode.sub(s, -integer, -1)
end
else
if noDots then
return unicode.sub(s, 1, limit)
else
return unicode.sub(s, 1, limit - 1) .. ""
end
end
end
function string.wrap(data, limit)
if type(data) == "string" then data = {data} end
local wrappedLines, result, preResult, preResultLength = {}
for i = 1, #data do
for subLine in data[i]:gmatch("[^\n]+") do
result = ""
for word in subLine:gmatch("[^%s]+") do
preResult = result .. word
preResultLength = unicode.len(preResult)
if preResultLength > limit then
if unicode.len(word) > limit then
table.insert(wrappedLines, unicode.sub(preResult, 1, limit))
for i = limit + 1, preResultLength, limit do
table.insert(wrappedLines, unicode.sub(preResult, i, i + limit - 1))
end
result = wrappedLines[#wrappedLines] .. " "
wrappedLines[#wrappedLines] = nil
else
result = result:gsub("%s+$", "")
table.insert(wrappedLines, result)
result = word .. " "
end
else
result = preResult .. " "
end
end
result = result:gsub("%s+$", "")
table.insert(wrappedLines, result)
end
end
return wrappedLines
end
-------------------------------------------------- Playground --------------------------------------------------
-- print(table.toString(require("MineOSCore").OSSettings, true, 2, true, 2))
-- local t = {
-- abc = 123,
-- def = {
-- cyka = "pidor",
-- vagina = {
-- chlen = 555,
-- devil = 666,
-- god = 777,
-- serost = {
-- tripleTable = "aefaef",
-- aaa = "bbb",
-- ccc = 123,
-- }
-- }
-- },
-- ghi = "HEHE",
-- emptyTable = {},
-- }
-- print(table.toString(t, true))
------------------------------------------------------------------------------------------------------------------
return {loaded = true}