· 8 years ago · Jan 05, 2018, 03:02 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 path:match("^(.+%/).") or ""
151-- end
152
153-- function filesystem.name(path)
154-- return path:match("%/?([^%/]+)%/?$")
155-- end
156
157function filesystem.extension(path, lower)
158 local extension = path:match("[^%/]+(%.[^%/]+)%/?$")
159 return (lower and extension) and (unicode.lower(extension)) or extension
160end
161
162function filesystem.hideExtension(path)
163 return path:match("(.+)%..+") or path
164end
165
166function filesystem.isFileHidden(path)
167 if path:match("^%..+$") then
168 return true
169 end
170 return false
171end
172
173function filesystem.sortedList(path, sortingMethod, showHiddenFiles, filenameMatcher, filenameMatcherCaseSensitive)
174 if not filesystem.exists(path) then
175 error("Failed to get file list: directory \"" .. tostring(path) .. "\" doesn't exists")
176 end
177 if not filesystem.isDirectory(path) then
178 error("Failed to get file list: path \"" .. tostring(path) .. "\" is not a directory")
179 end
180
181 local fileList, sortedFileList = {}, {}
182 for file in filesystem.list(path) do
183 if not filenameMatcher or string.unicodeFind(filenameMatcherCaseSensitive and file or unicode.lower(file), filenameMatcherCaseSensitive and filenameMatcher or unicode.lower(filenameMatcher)) then
184 table.insert(fileList, file)
185 end
186 end
187
188 if #fileList > 0 then
189 if sortingMethod == "type" then
190 local extension
191 for i = 1, #fileList do
192 extension = filesystem.extension(fileList[i]) or "Script"
193 if filesystem.isDirectory(path .. fileList[i]) and extension ~= ".app" then
194 extension = ".01_Folder"
195 end
196 fileList[i] = {fileList[i], extension}
197 end
198
199 table.sort(fileList, function(a, b) return unicode.lower(a[2]) < unicode.lower(b[2]) end)
200
201 local currentExtensionList, currentExtension = {}, fileList[1][2]
202 for i = 1, #fileList do
203 if currentExtension == fileList[i][2] then
204 table.insert(currentExtensionList, fileList[i][1])
205 else
206 table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
207 for j = 1, #currentExtensionList do
208 table.insert(sortedFileList, currentExtensionList[j])
209 end
210 currentExtensionList, currentExtension = {fileList[i][1]}, fileList[i][2]
211 end
212 end
213
214 table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
215 for j = 1, #currentExtensionList do
216 table.insert(sortedFileList, currentExtensionList[j])
217 end
218 elseif sortingMethod == "name" then
219 sortedFileList = fileList
220 table.sort(sortedFileList, function(a, b) return unicode.lower(a) < unicode.lower(b) end)
221 elseif sortingMethod == "date" then
222 for i = 1, #fileList do
223 fileList[i] = {fileList[i], filesystem.lastModified(path .. fileList[i])}
224 end
225
226 table.sort(fileList, function(a, b) return unicode.lower(a[2]) > unicode.lower(b[2]) end)
227
228 for i = 1, #fileList do
229 table.insert(sortedFileList, fileList[i][1])
230 end
231 else
232 error("Unknown sorting method: " .. tostring(sortingMethod))
233 end
234
235 local i = 1
236 while i <= #sortedFileList do
237 if not showHiddenFiles and filesystem.isFileHidden(sortedFileList[i]) then
238 table.remove(sortedFileList, i)
239 else
240 i = i + 1
241 end
242 end
243 end
244
245 return sortedFileList
246end
247
248function filesystem.directorySize(path)
249 local size = 0
250 for file in filesystem.list(path) do
251 if filesystem.isDirectory(path .. file) then
252 size = size + filesystem.directorySize(path .. file)
253 else
254 size = size + filesystem.size(path .. file)
255 end
256 end
257
258 return size
259end
260
261-------------------------------------------------- Table extensions --------------------------------------------------
262
263local function doSerialize(array, prettyLook, indentationSymbol, indentationSymbolAdder, equalsSymbol, currentRecusrionStack, recursionStackLimit)
264 local text, indentationSymbolNext, keyType, valueType, stringValue = {"{"}, table.concat({indentationSymbol, indentationSymbolAdder})
265 if prettyLook then
266 table.insert(text, "\n")
267 end
268
269 for key, value in pairs(array) do
270 keyType, valueType, stringValue = type(key), type(value), tostring(value)
271
272 if prettyLook then
273 table.insert(text, indentationSymbolNext)
274 end
275
276 if keyType == "number" then
277 table.insert(text, "[")
278 table.insert(text, key)
279 table.insert(text, "]")
280 elseif keyType == "string" then
281 if prettyLook and key:match("^%a") and key:match("^[%w%_]+$") then
282 table.insert(text, key)
283 else
284 table.insert(text, "[\"")
285 table.insert(text, key)
286 table.insert(text, "\"]")
287 end
288 end
289
290 table.insert(text, equalsSymbol)
291
292 if valueType == "number" or valueType == "boolean" or valueType == "nil" then
293 table.insert(text, stringValue)
294 elseif valueType == "string" or valueType == "function" then
295 table.insert(text, "\"")
296 table.insert(text, stringValue)
297 table.insert(text, "\"")
298 elseif valueType == "table" then
299 if currentRecusrionStack < recursionStackLimit then
300 table.insert(
301 text,
302 table.concat(
303 doSerialize(
304 value,
305 prettyLook,
306 indentationSymbolNext,
307 indentationSymbolAdder,
308 equalsSymbol,
309 currentRecusrionStack + 1,
310 recursionStackLimit
311 )
312 )
313 )
314 else
315 table.insert(text, "\"…\"")
316 end
317 end
318
319 table.insert(text, ",")
320 if prettyLook then
321 table.insert(text, "\n")
322 end
323 end
324
325 -- УдалÑем запÑтую
326 if prettyLook then
327 if #text > 2 then
328 table.remove(text, #text - 1)
329 end
330 -- Ð’ÑтавлÑем заодно уж Ñимвол индентации, благо чек на притти лук идет
331 table.insert(text, indentationSymbol)
332 else
333 if #text > 1 then
334 table.remove(text, #text)
335 end
336 end
337
338 table.insert(text, "}")
339
340 return text
341end
342
343function table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit)
344 checkArg(1, array, "table")
345
346 return table.concat(
347 doSerialize(
348 array,
349 prettyLook,
350 "",
351 string.rep(indentUsingTabs and " " or " ", indentationWidth or 2),
352 prettyLook and " = " or "=",
353 1,
354 recursionStackLimit or math.huge
355 )
356 )
357end
358
359function table.unserialize(serializedString)
360 checkArg(1, serializedString, "string")
361
362 local success, result = pcall(load("return " .. serializedString))
363 if success then
364 return result
365 else
366 return nil, result
367 end
368end
369
370table.toString = table.serialize
371table.fromString = table.unserialize
372
373function table.toFile(path, array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit, appendToFile)
374 checkArg(1, path, "string")
375 checkArg(2, array, "table")
376
377 filesystem.makeDirectory(filesystem.path(path) or "")
378
379 local file, reason = io.open(path, appendToFile and "a" or "w")
380 if file then
381 file:write(table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit))
382 file:close()
383 else
384 error("Failed to open file for writing: " .. tostring(reason))
385 end
386end
387
388function table.fromFile(path)
389 checkArg(1, path, "string")
390
391 if filesystem.exists(path) then
392 if filesystem.isDirectory(path) then
393 error("\"" .. path .. "\" is a directory")
394 else
395 local file = io.open(path, "r")
396 local data = table.unserialize(file:read("*a"))
397 file:close()
398 return data
399 end
400 else
401 error("\"" .. path .. "\" doesn't exists")
402 end
403end
404
405local function doTableCopy(source, destination)
406 for key, value in pairs(source) do
407 if type(value) == "table" then
408 destination[key] = {}
409 doTableCopy(source[key], destination[key])
410 else
411 destination[key] = value
412 end
413 end
414end
415
416function table.copy(tableToCopy)
417 local tableThatCopied = {}
418 doTableCopy(tableToCopy, tableThatCopied)
419
420 return tableThatCopied
421end
422
423function table.binarySearch(t, requestedValue)
424 local function recursiveSearch(startIndex, endIndex)
425 local difference = endIndex - startIndex
426 local centerIndex = math.floor(difference / 2 + startIndex)
427
428 if difference > 1 then
429 if requestedValue >= t[centerIndex] then
430 return recursiveSearch(centerIndex, endIndex)
431 else
432 return recursiveSearch(startIndex, centerIndex)
433 end
434 else
435 if math.abs(requestedValue - t[startIndex]) > math.abs(t[endIndex] - requestedValue) then
436 return t[endIndex]
437 else
438 return t[startIndex]
439 end
440 end
441 end
442
443 return recursiveSearch(1, #t)
444end
445
446function table.size(t)
447 local size = 0
448 for key in pairs(t) do size = size + 1 end
449 return size
450end
451
452function table.contains(t, object)
453 for _, value in pairs(t) do
454 if value == object then
455 return true
456 end
457 end
458 return false
459end
460
461function table.indexOf(t, object)
462 for i = 1, #t do
463 if t[i] == object then
464 return i
465 end
466 end
467end
468
469function table.sortAlphabetically(t)
470 table.sort(t, function(a, b) return a < b end)
471end
472
473-------------------------------------------------- String extensions --------------------------------------------------
474
475function string.brailleChar(a, b, c, d, e, f, g, h)
476 return unicode.char(10240 + 128*h + 64*g + 32*f + 16*d + 8*b + 4*e + 2*c + a)
477end
478
479function string.readUnicodeChar(file)
480 local byteArray = {string.byte(file:read(1))}
481
482 local nullBitPosition = 0
483 for i = 1, 7 do
484 if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then
485 nullBitPosition = i
486 break
487 end
488 end
489
490 for i = 1, nullBitPosition - 2 do
491 table.insert(byteArray, string.byte(file:read(1)))
492 end
493
494 return string.char(table.unpack(byteArray))
495end
496
497function string.canonicalPath(str)
498 return string.gsub("/" .. str, "%/+", "/")
499end
500
501function string.optimize(str, indentationWidth)
502 str = string.gsub(str, "\r\n", "\n")
503 str = string.gsub(str, " ", string.rep(" ", indentationWidth or 2))
504 return str
505end
506
507function string.optimizeForURLRequests(code)
508 if code then
509 code = string.gsub(code, "([^%w ])", function (c)
510 return string.format("%%%02X", string.byte(c))
511 end)
512 code = string.gsub(code, " ", "+")
513 end
514 return code
515end
516
517function string.unicodeFind(str, pattern, init, plain)
518 if init then
519 if init < 0 then
520 init = -#unicode.sub(str,init)
521 elseif init > 0 then
522 init = #unicode.sub(str, 1, init - 1) + 1
523 end
524 end
525
526 a, b = string.find(str, pattern, init, plain)
527
528 if a then
529 local ap, bp = str:sub(1, a - 1), str:sub(a,b)
530 a = unicode.len(ap) + 1
531 b = a + unicode.len(bp) - 1
532 return a, b
533 else
534 return a
535 end
536end
537
538function string.limit(s, limit, mode, noDots)
539 local length = unicode.len(s)
540 if length <= limit then return s end
541
542 if mode == "left" then
543 if noDots then
544 return unicode.sub(s, length - limit + 1, -1)
545 else
546 return "…" .. unicode.sub(s, length - limit + 2, -1)
547 end
548 elseif mode == "center" then
549 local integer, fractional = math.modf(limit / 2)
550 if fractional == 0 then
551 return unicode.sub(s, 1, integer) .. "…" .. unicode.sub(s, -integer + 1, -1)
552 else
553 return unicode.sub(s, 1, integer) .. "…" .. unicode.sub(s, -integer, -1)
554 end
555 else
556 if noDots then
557 return unicode.sub(s, 1, limit)
558 else
559 return unicode.sub(s, 1, limit - 1) .. "…"
560 end
561 end
562end
563
564function string.wrap(data, limit)
565 if type(data) == "string" then data = {data} end
566
567 local wrappedLines, result, preResult, preResultLength = {}
568 for i = 1, #data do
569 for subLine in data[i]:gmatch("[^\n]+") do
570 result = ""
571
572 for word in subLine:gmatch("[^%s]+") do
573 preResult = result .. word
574 preResultLength = unicode.len(preResult)
575
576 if preResultLength > limit then
577 if unicode.len(word) > limit then
578 table.insert(wrappedLines, unicode.sub(preResult, 1, limit))
579 for i = limit + 1, preResultLength, limit do
580 table.insert(wrappedLines, unicode.sub(preResult, i, i + limit - 1))
581 end
582
583 result = wrappedLines[#wrappedLines] .. " "
584 wrappedLines[#wrappedLines] = nil
585 else
586 result = result:gsub("%s+$", "")
587 table.insert(wrappedLines, result)
588
589 result = word .. " "
590 end
591 else
592 result = preResult .. " "
593 end
594 end
595
596 result = result:gsub("%s+$", "")
597 table.insert(wrappedLines, result)
598 end
599 end
600
601 return wrappedLines
602end
603
604-------------------------------------------------- Playground --------------------------------------------------
605
606-- print(table.toString(require("MineOSCore").OSSettings, true, 2, true, 2))
607
608-- local t = {
609-- abc = 123,
610-- def = {
611-- cyka = "pidor",
612-- vagina = {
613-- chlen = 555,
614-- devil = 666,
615-- god = 777,
616-- serost = {
617-- tripleTable = "aefaef",
618-- aaa = "bbb",
619-- ccc = 123,
620-- }
621-- }
622-- },
623-- ghi = "HEHE",
624-- emptyTable = {},
625-- }
626
627-- print(table.toString(t, true))
628
629------------------------------------------------------------------------------------------------------------------
630
631return {loaded = true}