· 5 years ago · Apr 07, 2020, 08:22 PM
1
2--[[
3
4 Advanced Lua Library v1.1 by ECS
5
6 This library extends a lot of default Lua methods
7 and adds some really cool features that haven't been
8 implemented yet, such as fastest table serialization,
9 table binary searching, string wrapping, numbers rounding, etc.
10
11]]
12
13local filesystem = require("filesystem")
14local unicode = require("unicode")
15local bit32 = require("bit32")
16
17-------------------------------------------------- System extensions --------------------------------------------------
18
19function _G.getCurrentScript()
20 local info
21 for runLevel = 0, math.huge do
22 info = debug.getinfo(runLevel)
23 if info then
24 if info.what == "main" then
25 return info.source:sub(2, -1)
26 end
27 else
28 error("Failed to get debug info for runlevel " .. runLevel)
29 end
30 end
31end
32
33function enum(...)
34 local args, enums = {...}, {}
35 for i = 1, #args do
36 if type(args[i]) ~= "string" then error("Function argument " .. i .. " have non-string type: " .. type(args[i])) end
37 enums[args[i]] = i
38 end
39 return enums
40end
41
42function swap(a, b)
43 return b, a
44end
45
46-------------------------------------------------- Bit32 extensions --------------------------------------------------
47
48-- Merge two numbers into one (0xAABB, 0xCCDD -> 0xAABBCCDD)
49function bit32.merge(number2, number1)
50 local cutter = math.ceil(math.log(number1 + 1, 256)) * 8
51 while number2 > 0 do
52 number1, number2, cutter = bit32.bor(bit32.lshift(bit32.band(number2, 0xFF), cutter), number1), bit32.rshift(number2, 8), cutter + 8
53 end
54
55 return number1
56end
57
58-- Split number to it's own bytes (0xAABBCC -> {0xAA, 0xBB, 0xCC})
59function bit32.numberToByteArray(number)
60 local byteArray = {}
61
62 repeat
63 table.insert(byteArray, 1, bit32.band(number, 0xFF))
64 number = bit32.rshift(number, 8)
65 until number <= 0
66
67 return byteArray
68end
69
70-- Split nubmer to it's own bytes with specified count of bytes (0xAABB, 5 -> {0x00, 0x00, 0x00, 0xAA, 0xBB})
71function bit32.numberToFixedSizeByteArray(number, size)
72 local byteArray, counter = {}, 0
73
74 repeat
75 table.insert(byteArray, 1, bit32.band(number, 0xFF))
76 number = bit32.rshift(number, 8)
77 counter = counter + 1
78 until number <= 0
79
80 for i = 1, size - counter do
81 table.insert(byteArray, 1, 0x0)
82 end
83
84 return byteArray
85end
86
87-- Create number from it's own bytes ({0xAA, 0xBB, 0xCC} -> 0xAABBCC)
88function bit32.byteArrayToNumber(byteArray)
89 local result = byteArray[1]
90 for i = 2, #byteArray do
91 result = bit32.bor(bit32.lshift(result, 8), byteArray[i])
92 end
93
94 return result
95end
96
97-- Create byte from it's bits ({1, 0, 1, 0, 1, 0, 1, 1} -> 0xAB)
98function bit32.bitArrayToByte(bitArray)
99 local number = 0
100 for i = 1, #bitArray do
101 number = bit32.bor(bitArray[i], bit32.lshift(number, 1))
102 end
103 return number
104end
105
106-------------------------------------------------- Math extensions --------------------------------------------------
107
108function math.round(num)
109 if num >= 0 then
110 return math.floor(num + 0.5)
111 else
112 return math.ceil(num - 0.5)
113 end
114end
115
116function math.roundToDecimalPlaces(num, decimalPlaces)
117 local mult = 10 ^ (decimalPlaces or 0)
118 return math.round(num * mult) / mult
119end
120
121function math.getDigitCount(num)
122 return num == 0 and 1 or math.ceil(math.log(num + 1, 10))
123end
124
125function math.doubleToString(num, digitCount)
126 return string.format("%." .. (digitCount or 1) .. "f", num)
127end
128
129function math.shortenNumber(number, digitCount)
130 local shortcuts = {
131 "K",
132 "M",
133 "B",
134 "T"
135 }
136
137 local index = math.floor(math.log(number, 1000))
138 if number < 1000 then
139 return number
140 elseif index > #shortcuts then
141 index = #shortcuts
142 end
143
144 return math.roundToDecimalPlaces(number / 1000 ^ index, digitCount) .. shortcuts[index]
145end
146
147---------------------------------------------- Filesystem extensions ------------------------------------------------------------------------
148
149-- function filesystem.path(path)
150-- return string.match(path, "^(.+%/).") or ""
151-- end
152
153-- function filesystem.name(path)
154-- return string.match(path, "%/?([^%/]+)%/?$")
155-- end
156
157local function getNameAndExtension(path)
158 local fileName, extension = string.match(path, "^(.+)(%.[^%/]+)%/?$")
159 return (fileName or path), extension
160end
161
162function filesystem.extension(path)
163 local fileName, extension = getNameAndExtension(path)
164 return extension
165end
166
167function filesystem.hideExtension(path)
168 local fileName, extension = getNameAndExtension(path)
169 return fileName
170end
171
172function filesystem.isFileHidden(path)
173 if string.match(path, "^%..+$") then return true end
174 return false
175end
176
177function filesystem.sortedList(path, sortingMethod, showHiddenFiles)
178 if not filesystem.exists(path) then
179 error("Failed to get file list: directory \"" .. tostring(path) .. "\" doesn't exists")
180 end
181 if not filesystem.isDirectory(path) then
182 error("Failed to get file list: path \"" .. tostring(path) .. "\" is not a directory")
183 end
184
185 local fileList, sortedFileList = {}, {}
186 for file in filesystem.list(path) do
187 table.insert(fileList, file)
188 end
189
190 if #fileList > 0 then
191 if sortingMethod == "type" then
192 local extension
193 for i = 1, #fileList do
194 extension = filesystem.extension(fileList[i]) or "Script"
195 if filesystem.isDirectory(path .. fileList[i]) and extension ~= ".app" then
196 extension = ".01_Folder"
197 end
198 fileList[i] = {fileList[i], extension}
199 end
200
201 table.sort(fileList, function(a, b) return a[2] < b[2] end)
202
203 local currentExtensionList, currentExtension = {}, fileList[1][2]
204 for i = 1, #fileList do
205 if currentExtension == fileList[i][2] then
206 table.insert(currentExtensionList, fileList[i][1])
207 else
208 table.sort(currentExtensionList, function(a, b) return a < b end)
209 for j = 1, #currentExtensionList do
210 table.insert(sortedFileList, currentExtensionList[j])
211 end
212 currentExtensionList, currentExtension = {fileList[i][1]}, fileList[i][2]
213 end
214 end
215
216 table.sort(currentExtensionList, function(a, b) return a < b end)
217 for j = 1, #currentExtensionList do
218 table.insert(sortedFileList, currentExtensionList[j])
219 end
220 elseif sortingMethod == "name" then
221 sortedFileList = fileList
222 table.sort(sortedFileList, function(a, b) return a < b end)
223 elseif sortingMethod == "date" then
224 for i = 1, #fileList do
225 fileList[i] = {fileList[i], filesystem.lastModified(path .. fileList[i])}
226 end
227
228 table.sort(fileList, function(a, b) return a[2] > b[2] end)
229
230 for i = 1, #fileList do
231 table.insert(sortedFileList, fileList[i][1])
232 end
233 else
234 error("Unknown sorting method: " .. tostring(sortingMethod))
235 end
236
237 local i = 1
238 while i <= #sortedFileList do
239 if not showHiddenFiles and filesystem.isFileHidden(sortedFileList[i]) then
240 table.remove(sortedFileList, i)
241 else
242 i = i + 1
243 end
244 end
245 end
246
247 return sortedFileList
248end
249
250function filesystem.directorySize(path)
251 local size = 0
252 for file in filesystem.list(path) do
253 if filesystem.isDirectory(path .. file) then
254 size = size + filesystem.directorySize(path .. file)
255 else
256 size = size + filesystem.size(path .. file)
257 end
258 end
259
260 return size
261end
262
263-------------------------------------------------- Table extensions --------------------------------------------------
264
265local function doSerialize(array, prettyLook, indentationSymbol, indentationSymbolAdder, equalsSymbol, currentRecusrionStack, recursionStackLimit)
266 local text, keyType, valueType, stringValue = {"{"}
267 table.insert(text, (prettyLook and "\n" or nil))
268
269 for key, value in pairs(array) do
270 keyType, valueType, stringValue = type(key), type(value), tostring(value)
271
272 if keyType == "number" or keyType == "string" then
273 table.insert(text, (prettyLook and table.concat({indentationSymbol, indentationSymbolAdder}) or nil))
274 table.insert(text, "[")
275 table.insert(text, (keyType == "string" and table.concat({"\"", key, "\""}) or key))
276 table.insert(text, "]")
277 table.insert(text, equalsSymbol)
278
279 if valueType == "number" or valueType == "boolean" or valueType == "nil" then
280 table.insert(text, stringValue)
281 elseif valueType == "string" or valueType == "function" then
282 table.insert(text, "\"")
283 table.insert(text, stringValue)
284 table.insert(text, "\"")
285 elseif valueType == "table" then
286 -- Ограничение стека рекурсии
287 if currentRecusrionStack < recursionStackLimit then
288 table.insert(text, table.concat(doSerialize(value, prettyLook, table.concat({indentationSymbol, indentationSymbolAdder}), indentationSymbolAdder, equalsSymbol, currentRecusrionStack + 1, recursionStackLimit)))
289 else
290 table.insert(text, "...")
291 end
292 end
293
294 table.insert(text, ",")
295 table.insert(text, (prettyLook and "\n" or nil))
296 end
297 end
298
299 -- Удаляем запятую
300 if prettyLook then
301 if #text > 2 then
302 table.remove(text, #text - 1)
303 end
304 -- Вставляем заодно уж символ индентации, благо чек на притти лук идет
305 table.insert(text, indentationSymbol)
306 else
307 if #text > 1 then
308 table.remove(text, #text)
309 end
310 end
311
312 table.insert(text, "}")
313
314 return text
315end
316
317function table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit)
318 checkArg(1, array, "table")
319 return table.concat(
320 doSerialize(
321 array,
322 prettyLook,
323 "",
324 string.rep(indentUsingTabs and " " or " ", indentationWidth or 2),
325 prettyLook and " = " or "=",
326 1,
327 recursionStackLimit or math.huge
328 )
329 )
330end
331
332function table.unserialize(serializedString)
333 checkArg(1, serializedString, "string")
334 local success, result = pcall(load("return " .. serializedString))
335 if success then return result else return nil, result end
336end
337
338table.toString = table.serialize
339table.fromString = table.unserialize
340
341function table.toFile(path, array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit, appendToFile)
342 checkArg(1, path, "string")
343 checkArg(2, array, "table")
344 filesystem.makeDirectory(filesystem.path(path) or "")
345 local file = io.open(path, appendToFile and "a" or "w")
346 file:write(table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit))
347 file:close()
348end
349
350function table.fromFile(path)
351 checkArg(1, path, "string")
352 if filesystem.exists(path) then
353 if filesystem.isDirectory(path) then
354 error("\"" .. path .. "\" is a directory")
355 else
356 local file = io.open(path, "r")
357 local data = table.unserialize(file:read("*a"))
358 file:close()
359 return data
360 end
361 else
362 error("\"" .. path .. "\" doesn't exists")
363 end
364end
365
366function table.copy(tableToCopy)
367 local function recursiveCopy(source, destination)
368 for key, value in pairs(source) do
369 if type(value) == "table" then
370 destination[key] = {}
371 recursiveCopy(source[key], destination[key])
372 else
373 destination[key] = value
374 end
375 end
376 end
377
378 local tableThatCopied = {}
379 recursiveCopy(tableToCopy, tableThatCopied)
380
381 return tableThatCopied
382end
383
384function table.binarySearch(t, requestedValue)
385 local function recursiveSearch(startIndex, endIndex)
386 local difference = endIndex - startIndex
387 local centerIndex = math.floor(difference / 2 + startIndex)
388
389 if difference > 1 then
390 if requestedValue >= t[centerIndex] then
391 return recursiveSearch(centerIndex, endIndex)
392 else
393 return recursiveSearch(startIndex, centerIndex)
394 end
395 else
396 if math.abs(requestedValue - t[startIndex]) > math.abs(t[endIndex] - requestedValue) then
397 return t[endIndex]
398 else
399 return t[startIndex]
400 end
401 end
402 end
403
404 return recursiveSearch(1, #t)
405end
406
407function table.size(t)
408 local size = 0
409 for key in pairs(t) do size = size + 1 end
410 return size
411end
412
413function table.contains(t, object)
414 for key in pairs(t) do
415 if t[key] == object then
416 return true
417 end
418 end
419 return false
420end
421
422function table.indexOf(t, object)
423 for i = 1, #t do
424 if t[i] == object then
425 return i
426 end
427 end
428end
429
430-------------------------------------------------- String extensions --------------------------------------------------
431
432function string.readUnicodeChar(file)
433 local byteArray = {string.byte(file:read(1))}
434
435 local nullBitPosition = 0
436 for i = 1, 7 do
437 if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then
438 nullBitPosition = i
439 break
440 end
441 end
442
443 for i = 1, nullBitPosition - 2 do
444 table.insert(byteArray, string.byte(file:read(1)))
445 end
446
447 return string.char(table.unpack(byteArray))
448end
449
450function string.canonicalPath(str)
451 return string.gsub("/" .. str, "%/+", "/")
452end
453
454function string.optimize(str, indentationWidth)
455 str = string.gsub(str, "\r\n", "\n")
456 str = string.gsub(str, " ", string.rep(" ", indentationWidth or 2))
457 return str
458end
459
460function string.optimizeForURLRequests(code)
461 if code then
462 code = string.gsub(code, "([^%w ])", function (c)
463 return string.format("%%%02X", string.byte(c))
464 end)
465 code = string.gsub(code, " ", "+")
466 end
467 return code
468end
469
470function string.unicodeFind(str, pattern, init, plain)
471 if init then
472 if init < 0 then
473 init = -#unicode.sub(str,init)
474 elseif init > 0 then
475 init = #unicode.sub(str, 1, init - 1) + 1
476 end
477 end
478
479 a, b = string.find(str, pattern, init, plain)
480
481 if a then
482 local ap, bp = str:sub(1, a - 1), str:sub(a,b)
483 a = unicode.len(ap) + 1
484 b = a + unicode.len(bp) - 1
485 return a, b
486 else
487 return a
488 end
489end
490
491function string.limit(text, limit, mode, noDots)
492 local length = unicode.len(text)
493 if length <= limit then return text end
494
495 if mode == "left" then
496 if noDots then
497 return unicode.sub(text, length - limit + 1, -1)
498 else
499 return "…" .. unicode.sub(text, length - limit + 2, -1)
500 end
501 elseif mode == "center" then
502 local partSize = math.ceil(limit / 2)
503 return unicode.sub(text, 1, partSize) .. "…" .. unicode.sub(text, -partSize + 1, -1)
504 else
505 if noDots then
506 return unicode.sub(text, 1, limit)
507 else
508 return unicode.sub(text, 1, limit - 1) .. "…"
509 end
510 end
511end
512
513function string.wrap(strings, limit)
514 strings = type(strings) == "string" and {strings} or strings
515
516 local currentString = 1
517 while currentString <= #strings do
518 local words = {}; for word in string.gmatch(tostring(strings[currentString]), "[^%s]+") do table.insert(words, word) end
519
520 local newStringThatFormedFromWords, oldStringThatFormedFromWords = "", ""
521 local word = 1
522 local overflow = false
523 while word <= #words do
524 oldStringThatFormedFromWords = oldStringThatFormedFromWords .. (word > 1 and " " or "") .. words[word]
525 if unicode.len(oldStringThatFormedFromWords) > limit then
526 if unicode.len(words[word]) > limit then
527 local left = unicode.sub(oldStringThatFormedFromWords, 1, limit)
528 local right = unicode.sub(strings[currentString], unicode.len(left) + 1, -1)
529 overflow = true
530 strings[currentString] = left
531 if strings[currentString + 1] then
532 strings[currentString + 1] = right .. " " .. strings[currentString + 1]
533 else
534 strings[currentString + 1] = right
535 end
536 end
537 break
538 else
539 newStringThatFormedFromWords = oldStringThatFormedFromWords
540 end
541 word = word + 1
542 end
543
544 if word <= #words and not overflow then
545 local fuckToAdd = table.concat(words, " ", word, #words)
546 if strings[currentString + 1] then
547 strings[currentString + 1] = fuckToAdd .. " " .. strings[currentString + 1]
548 else
549 strings[currentString + 1] = fuckToAdd
550 end
551 strings[currentString] = newStringThatFormedFromWords
552 end
553
554 currentString = currentString + 1
555 end
556
557 return strings
558end
559
560-------------------------------------------------- Playground --------------------------------------------------
561
562-- local t = {
563-- abc = 123,
564-- def = {
565-- cyka = "pidor",
566-- vagina = {
567-- chlen = 555,
568-- devil = 666,
569-- god = 777,
570-- serost = {
571-- tripleTable = "aefaef",
572-- aaa = "bbb",
573-- ccc = 123,
574-- }
575-- }
576-- },
577-- ghi = "HEHE",
578-- emptyTable = {},
579-- }
580
581-- print(table.toString(t, true))
582
583------------------------------------------------------------------------------------------------------------------
584
585return {loaded = true}