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