├── README.MD ├── files ├── Events.lua ├── bit.lua ├── bmp.lua ├── canvas.lua ├── class.lua ├── colors.lua ├── console.lua ├── dialog.lua ├── encryption.lua ├── files.lua ├── graphic.lua ├── http.lua ├── json.lua ├── library.lua ├── libs │ ├── deflate.lua │ ├── log30.lua │ ├── png_decode.lua │ ├── png_encode.lua │ ├── qrcode.lua │ └── stream.lua ├── log.lua ├── lua.lua ├── math.lua ├── number.lua ├── object.lua ├── package.lua ├── path.lua ├── plist.lua ├── point.lua ├── shell.lua ├── storage.lua ├── string.lua ├── table.lua ├── time.lua ├── timer.lua ├── tools.lua └── yaml.lua ├── others ├── graphic.png ├── test.ico ├── test.png ├── test_bmp.bmp ├── test_canvas.png ├── test_dialog.png ├── test_log.png ├── test_object.png ├── test_qrcode.png └── yellow.png ├── test.lua ├── test.txt └── tools.lua /README.MD: -------------------------------------------------------------------------------- 1 | ### Pure Lua Tools 2 | 3 | 0. About 4 | 5 | > there are some useful tools for lua, u can require `tools.lua` file and enjoy the tools ... 6 | 7 | ```lua 8 | require('./tools') 9 | -- do something with tools 10 | ``` 11 | 12 | 1. class 13 | 14 | ```lua 15 | local Animal = class("Animal") 16 | function Animal:__init__() 17 | print('animal init ...') 18 | end 19 | function Animal:eat(something) 20 | print(string.format('eating a poor %s ...', something)) 21 | end 22 | 23 | local Dog, Parent = class("Dog", Animal) 24 | function Dog:__init__() 25 | Parent:__init__(self) 26 | print('dog init ...') 27 | end 28 | function Dog:eat(something) 29 | Parent.eat(self, something) 30 | end 31 | 32 | local dog = Dog() 33 | print(dog) 34 | dog:eat('cat') 35 | 36 | -- animal init ... 37 | -- dog init ... 38 | -- : 01085670 39 | -- eating a poor cat ... 40 | ``` 41 | 42 | 2. events 43 | 44 | ```lua 45 | events = Events() 46 | events:addListener('testEvent', function(argument) 47 | print('callback1 receive: ' .. argument) 48 | end, 1) -- once 49 | events:addListener('testEvent', function(argument) 50 | print('callback2 receive: ' .. argument) 51 | end, 3) -- limeted 52 | events:addListener('testEvent', function(argument) 53 | print('callback3 receive: ' .. argument) 54 | end, 0) -- forever 55 | events:triggerEvent('testEvent', 1) 56 | events:triggerEvent('testEvent', 2) 57 | events:triggerEvent('testEvent', 3) 58 | events:triggerEvent('testEvent', 4) 59 | events:triggerEvent('testEvent', 5) 60 | 61 | -- callback1 receive: 1 62 | -- callback2 receive: 1 63 | -- callback3 receive: 1 64 | -- callback2 receive: 2 65 | -- callback3 receive: 2 66 | -- callback2 receive: 3 67 | -- callback3 receive: 3 68 | -- callback3 receive: 4 69 | -- callback3 receive: 5 70 | ``` 71 | 72 | 3. files 73 | 74 | ```lua 75 | -- write, read 76 | local fileName = "some/file/nam.txt" 77 | files.write(fileName, 'content...') 78 | files.is_file(fileName) 79 | local content = files.read(fileName) 80 | files.delete(fileName) 81 | -- list dir 82 | local dirName = 'some/folder/name/' 83 | files.is_folder(dirName) 84 | local fileNames = files.list(dirName) 85 | -- size 86 | files.size(fileName) 87 | -- watch 88 | files.watch({'test1.txt', 'test1.txt'}, function(path, newTime) 89 | print(path, os.date("modified at: %Y-%m-%d %H:%M:%S", newTime)) 90 | end) 91 | ``` 92 | 93 | 4. json 94 | 95 | ```lua 96 | local test = { 97 | [1] = 'numberKey', 98 | boolenValue = true, 99 | nullValue = null, 100 | objectValue = { 101 | stringValue = "json is ready!", 102 | }, 103 | arrayValue = {"enjoy", "it", "!"}, 104 | } 105 | 106 | -- encode 107 | local s = json.encode(test) 108 | print(s) 109 | -- {"1":"numberKey","objectValue":{"stringValue":"json is ready!"},"nullValue":null,"arrayValue":["enjoy","it","!"],"boolenValue":true} 110 | 111 | -- decode 112 | local t = json.decode(s) 113 | print(t) 114 | -- table: 010A3D40 115 | ``` 116 | 117 | 5. table 118 | 119 | ```lua 120 | -- test table value with some feature 121 | local value = table.new({ 122 | key = "value", 123 | }) 124 | 125 | -- encode, decode 126 | local s = value:encode() -- lua script string of the table value 127 | local t = s:decode() -- table value of the script string 128 | 129 | -- write, read 130 | value:write_to_file('test.txt') 131 | local t = table.read_from_file('test.txt') 132 | 133 | -- keys, values 134 | local keys = value:keys() 135 | local values = value:values() 136 | 137 | -- merge 138 | local t = value:merge({newKey= "newValue"}) 139 | ``` 140 | 141 | 6. timer 142 | 143 | ```lua 144 | -- sync 145 | timer.async(function() 146 | timer.sleep(1) 147 | print('one') 148 | timer.sleep(1) 149 | print('two') 150 | timer.sleep(1) 151 | print('three') 152 | end) 153 | 154 | -- delay 155 | timer.delay(2, function() 156 | print('delay') 157 | end) 158 | 159 | -- start the loop 160 | print("start") 161 | print("end!") 162 | timer.start() 163 | 164 | -- start 165 | -- one 166 | -- delay 167 | -- two 168 | -- three 169 | -- end! 170 | ``` 171 | 172 | 7. string 173 | 174 | ```lua 175 | -- test string value with some feature 176 | local s = string.new('abcd') 177 | 178 | -- fill 179 | local r = s:center(6, ' ') -- ' abcd ' 180 | local r = s:left(6, ' ') -- 'abcd ' 181 | local r = s:right(6, ' ') -- ' abcd' 182 | 183 | -- trim 184 | local s = s:trim() -- 'abcd' 185 | 186 | -- execute 187 | s:execute() 188 | ``` 189 | 190 | 8. bit 191 | 192 | ```lua 193 | r = bit.rshift(2, 1) -- 1 194 | r = bit.lshift(2, 1) -- 4 195 | r = bit.band(1, 3) -- 1 196 | r = bit.bor(1, 2) -- 3 197 | r = bit.bxor(1, 3) -- 2 198 | r = bit.bnot(1) -- -2 199 | ``` 200 | 201 | 9. encryption 202 | ```lua 203 | local text = "hello" 204 | local md5 = encryption.md5(text) 205 | print(md5) 206 | -- 5d41402abc4b2a76b9719d911017c592 207 | local encoded = encryption.base64_encode(text) 208 | print(encoded) 209 | -- aGVsbG8= 210 | local decoded = encryption.base64_decode(encoded) 211 | print(decoded) 212 | -- hello 213 | local key = "key" 214 | local encoded = encryption.bxor_encode(key, text) 215 | local decoded = encryption.bxor_encode(key, encoded) 216 | print(decoded) 217 | -- hello 218 | ``` 219 | 220 | 10. http 221 | 222 | ```lua 223 | -- download 224 | http.download('http://example.xyz/some/file.txt', './file.txt') 225 | 226 | -- request 227 | local isOk, code, content = http.get('http://example.xyz/some/url', {queryKey = "value"}, {headerKey = "value"}) 228 | local isOk, code, content = http.post('http://example.xyz/some/url', {bodyKey = "value"}, {headerKey = "value"}) 229 | assert(isOk and code == 200) 230 | print(content) 231 | ``` 232 | 233 | 11. Object 234 | 235 | ```lua 236 | -- new class 237 | local Human = Object:ext() 238 | function Human:init() 239 | print('human...') 240 | end 241 | -- extended class 242 | local Man = Human:ext() 243 | function Man:init() 244 | Human.init(self) 245 | print('man...') 246 | end 247 | -- a object 248 | local alex = Man:new() 249 | print(alex:is(Man)) 250 | print(alex:is(Human)) 251 | ``` 252 | 253 | ![log_test](./others/test_object.png) 254 | 255 | 12. Log 256 | 257 | ```lua 258 | local log = Log('log.txt', 'TEST', Log.LEVEL.USER, Log.COLOR.TAG_BG_CONTENT_FG) 259 | log:user('time:%d', os.time()) 260 | log:error('error ...') 261 | log:warn('warn ...') 262 | log:info('info ...') 263 | log:debug('debug ...') 264 | ``` 265 | 266 | ![log_test](./others/test_log.png) 267 | 268 | 13. package 269 | 270 | ```lua 271 | -- other.lua 272 | a = 10 273 | b = {} 274 | b.c = 'c...' 275 | print(b) 276 | return b 277 | 278 | -- test.lua 279 | local path = './other.lua' 280 | local m = package.doload(path) 281 | print(m) 282 | print(package.isloaded(path)) 283 | print(a) 284 | print(m.c) 285 | 286 | -- table: 02246FB8 287 | -- table: 02246FB8 288 | -- true 289 | -- 10 290 | -- c... 291 | ``` 292 | 293 | 14. Graphic 294 | 295 | ```lua 296 | local graphic = Graphic() 297 | -- stage 298 | graphic:setXY(250, 250):setWH(495, 495):setClip() 299 | graphic:setXY(250, 250):setWH(500, 500):setColor("#222222"):addRectangle() 300 | -- text 301 | graphic:setXY(125, 265):setColor("#00ff55"):addText("Text...", 48, nil) 302 | -- iamges 303 | graphic:setXY(375, 125):setWH(200, 200):addImage("./others/yellow.png", 75, 75, 350, 350) 304 | graphic:setXY(125, 375):setWH(128, 128):addImage("./others/test.png", 0, 0, 128, 128) 305 | -- lines 306 | graphic:setColor("#2255ff") 307 | graphic:addLine({75, 75}, {175, 175}) 308 | graphic:addCurve({50, 100}, {50, 200}, {150, 200}) 309 | graphic:addBezier({10, 10}, {100, 100}, {200, 10}, {200, 200}) 310 | -- shapes 311 | graphic:setColor("#55ff55ff") 312 | graphic:setXY(425, 325):setWH(50, 50):addEllipse() 313 | graphic:setXY(375, 375):setWH(100, 100):addPie() 314 | graphic:setXY(375, 375):setWH(200, 200):addArc() 315 | graphic:setColor("#5577ffaa") 316 | graphic:addPolygon({75, 125}, {75, 175}, {125, 175}) 317 | -- show or save 318 | graphic:show() 319 | -- graphic:save("./others/graphic.png") 320 | ``` 321 | 322 | ![log_test](./others/graphic.png) 323 | 324 | 15. Dialog 325 | 326 | ```lua 327 | local r = dialog.show_confirm("title...", "confirm...", YesNoCancel) 328 | print("result:", r) 329 | -- result: true 330 | ``` 331 | 332 | ![log_test](./others/test_dialog.png) 333 | 334 | 16. Path 335 | 336 | ```lua 337 | local path = Path() 338 | -- 339 | print(path:set("~"):get()) 340 | -- C:\Users\user 341 | print(path:push("../xyz/abc"):get()) 342 | -- C:\Users\xyz\abc 343 | print(path:relative(Path("~")):get()) 344 | -- ..\..\user 345 | print(Path("~"):relative(path):get()) 346 | -- ..\xyz\abc 347 | ``` 348 | 349 | 17. Time 350 | 351 | ```lua 352 | local time = Time(os.time()) 353 | print(time:getYear()) 354 | -- 2022 355 | local time = Time(60 * 60 * 24 * 10 + 60 * 60 * 5 + 60 * 7 + 9) 356 | print(time:countDay()) 357 | -- 1 3 5 7 9 358 | ``` 359 | 360 | 18. library 361 | 362 | * image 363 | ```lua 364 | -- write png 365 | local img = library.pngEncode(4, 4, "rgba") 366 | while not img.done do 367 | img:write({150, 100, 50, 200}) 368 | end 369 | files.write("./test.png", table.concat(img.output), "wb") 370 | -- read png 371 | local data = files.read("./test.png", "rb") 372 | local img = library.pngDecode(data, printProg) 373 | local pixel = img:getPixel(2, 2) 374 | print("size:", img.width, img.height, "depth:", img.depth, "color:", img.colorType) 375 | print("pixel:", pixel.R, pixel.G, pixel.B, pixel.A) 376 | ``` 377 | 378 | * log30 379 | ```lua 380 | local Parent = library.log30() 381 | function Parent:__init() 382 | print('parent init ...') 383 | end 384 | function Parent:testFunc() 385 | print('test func ...') 386 | end 387 | local Child = Parent:extends() 388 | function Child:__init() 389 | self.super.__init(self) 390 | print('child init ...') 391 | end 392 | local object = Child() 393 | object:testFunc() 394 | -- parent init ... 395 | -- child init ... 396 | -- test func ... 397 | ``` 398 | 399 | * qrcode 400 | ```lua 401 | local isOk, datas = library.qrcode("content...") 402 | assert(isOk == true, 'qrcode generate failed!') 403 | for i,column in ipairs(datas) do 404 | for j,row in ipairs(column) do 405 | -- 406 | end 407 | end 408 | ``` 409 | 410 | 18. Canvas 411 | 412 | ```lua 413 | -- draw canvas 414 | local size = 128 415 | local canvas = Canvas(size, size) 416 | for i=1,size do 417 | for j=1,size do 418 | local c = (i + j - 1) % 255 419 | canvas:setPixel(i, j, colors.rgb_to_hex({c, c, c})) 420 | end 421 | end 422 | local c = math.round(size / 2) 423 | local d = math.round(c * 0.25) 424 | canvas:drawLine(c + d * 3, c + d * 3, c - d * 3, c - d * 3, 0xff0000) 425 | canvas:drawRect(c - d * 2, c + d * 2, c + d * 2, c - d * 2, false, 0x22ffff) 426 | canvas:drawCircle(c, c, c - d, false, 0xffff44) 427 | canvas:drawEllipse(c, c, c - d * 2, c - d * 3, 0x22ff88) 428 | -- write to file 429 | local img = library.pngEncode(size, size, "rgba") 430 | for y=size,1,-1 do 431 | for x=1,size do 432 | local px = canvas:getPixel(x, y) 433 | local rgba = table.insert(colors.hex_to_rgb(px), 255) 434 | assert(#rgba == 4) 435 | img:write(rgba) 436 | end 437 | end 438 | assert(img.done == true) 439 | files.write("./others/test_canvas.png", table.concat(img.output), "wb") 440 | ``` 441 | 442 | ![log_test](./others/test_canvas.png) 443 | 444 | 19. colors 445 | 446 | ```lua 447 | local rand = colors.get_random_rgb() 448 | local grey = colors.rgb_get_grayscale(rand) 449 | local hex = colors.rgb_to_hex(grey) 450 | ``` 451 | 452 | 20. bmp 453 | 454 | ```lua 455 | local size = 128 456 | local pixels = {} 457 | for y=size,1,-1 do 458 | pixels[y] = {} 459 | for x=1,size do 460 | local r = math.floor((x / size * 255)) % 256 461 | local g = math.floor((y / size * 255)) % 256 462 | local b = math.floor(((x + y) / size * 255)) % 256 463 | pixels[y][x] = {r, g, b} 464 | end 465 | end 466 | local path = "./others/test_bmp.bmp" 467 | bmp.write(path, size, size, pixels) 468 | local w, h, pxs = bmp.read(path) 469 | assert(w == size and h == size) 470 | ``` 471 | 472 | ![log_test](./others/test_bmp.bmp) 473 | 474 | 21. Point 475 | 476 | ```lua 477 | local p = Point(10, 10):rotate(-90) 478 | print(p.x, p.y) 479 | -- 10 -10 480 | ``` 481 | 482 | 22. yaml 483 | 484 | ```lua 485 | local text = [[ 486 | time: 2023-09-12_21:30:00+08 487 | one:&anchor1 488 | a:'aaa' 489 | b:"bbb" # comment2... 490 | two: 491 | c:ccc # comment3... 492 | <<:*anchor1 493 | d:0xff 494 | three: 495 | - &anchor2 xxx 496 | - yyy 497 | # comment1... 498 | - zzz 499 | - *anchor2 500 | ]] 501 | local data = yaml.decode(text) 502 | print(data) 503 | -- { 504 | -- [time] = 1694496600, 505 | -- [one] = { 506 | -- [a] = "aaa", 507 | -- [b] = "bbb" 508 | -- }, 509 | -- [two] = { 510 | -- [c] = "ccc", 511 | -- [a] = "aaa", 512 | -- [d] = 255, 513 | -- [b] = "bbb" 514 | -- } 515 | -- [three] = { 516 | -- (1) = "xxx", 517 | -- (2) = "yyy", 518 | -- (3) = "zzz", 519 | -- (4) = "xxx" 520 | -- }, 521 | -- } 522 | ``` 523 | 524 | 22. plist 525 | 526 | ```lua 527 | local text = [[ 528 | 529 | 530 | 531 | 532 | a 533 | 534 | 1 535 | 2 536 | 537 | s 538 | str... 539 | b 540 | 541 | 542 | 543 | ]] 544 | local t = plist.decode(text) 545 | print(t) 546 | -- { 547 | -- [a] = { 548 | -- (1) = 1, 549 | -- (2) = 2 550 | -- }, 551 | -- [s] = "str...", 552 | -- [b] = true 553 | -- } 554 | ``` 555 | 556 | 23. decorator 557 | 558 | ```lua 559 | -- test func 560 | local tFunc = function(arg1, arg2) 561 | return arg1 + arg2 562 | end 563 | -- executed befor test func 564 | local bFunc = function(arg1, arg2) 565 | if arg2 == 40 then 566 | return arg1, 50 -- change the second argument 567 | elseif foo then 568 | return nil, arg2 -- set first argument as nil 569 | elseif bar then 570 | return nil -- set all arguments as nil 571 | else 572 | -- use given arguments 573 | end 574 | end 575 | -- executed after test func 576 | local aFunc = function(result) 577 | if result == 70 then 578 | return 80 -- change the result 579 | elseif foo then 580 | return nil -- set result as nil 581 | else 582 | -- use returend result 583 | end 584 | end 585 | -- executed when occurs exception 586 | local eFunc = function(err) 587 | return 0 -- fix the result 588 | end 589 | -- use decorator 590 | local addFunc = lua_new_decorator(tFunc):before(bFunc):after(aFunc):error(eFunc) 591 | r = addFunc:call(10, 20) 592 | print("10 + 20 = " .. r) 593 | r = addFunc:call(10, 40) 594 | print("10 + 40 = " .. r) 595 | r = addFunc:call(10, 60) 596 | print("10 + 60 = " .. r) 597 | r = addFunc:call(10, nil) 598 | print("10 + nil = " .. r) 599 | -- 10 + 20 = 30 600 | -- 10 + 40 = 60 601 | -- 10 + 60 = 80 602 | -- 10 + nil = 0 603 | ``` 604 | 605 | 24. console 606 | 607 | ```lua 608 | local confirmed = console.print_confirm() 609 | print("confirmed:", confirmed) 610 | -- ---------------------confirm---------------------- 611 | -- | Yes or No ? | 612 | -- -------------------------------------------------- 613 | -- > n 614 | -- * confirmed! 615 | -- confirmed: false 616 | console.print_progress(75) 617 | -- [ ========================================= 100% ] 618 | console.print_qrcode("hello...") 619 | -- ----------------------qrcode---------------------- 620 | -- | qr code body with white and black blocks 621 | -- -------------------------------------------------- 622 | local selection = console.print_select({'lua', 'c', 'c++'}) 623 | print('selection:', selection) 624 | -- ----------------------select---------------------- 625 | -- | 1 . lua | 626 | -- | 2 . c | 627 | -- -------------------------------------------------- 628 | -- > 1 629 | -- * selected! 630 | -- selection: lua 631 | local input = console.print_enter(true) 632 | print('input:', input) 633 | -- ----------------------enter----------------------- 634 | -- | Enter a password ? | 635 | -- -------------------------------------------------- 636 | -- > ****** 637 | -- * entered! 638 | -- input: 123456 639 | local content = console.print_edit('origin content ...') 640 | print('content:', content) 641 | -- -----------------------edit----------------------- 642 | -- | e:Edit s:Save p:Print r:Revert q:Quit | 643 | -- -------------------------------------------------- 644 | -- * editing: 645 | -- * edited! 646 | -- | 1 edited content ... 647 | -- * printed! 648 | -- * quitted! 649 | -- content: edited content ... 650 | ``` 651 | ![log_test](./others/test_qrcode.png) 652 | 653 | 25. shell 654 | 655 | ```lua 656 | local isOk, output = shell.whoami() 657 | print(isOk, output) 658 | -- true win\administrator 659 | ``` 660 | 661 | --- 662 | 663 | > for more feture plz read the source code ... 664 | -------------------------------------------------------------------------------- /files/Events.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Events 3 | ]] 4 | 5 | assert(Events == nil) 6 | Events = class('Events') 7 | 8 | function Events:__init__() 9 | self._eventsMap = {} 10 | end 11 | 12 | function Events:triggerEvent(name, ...) 13 | assert(type(name) == 'string', 'event name should be function') 14 | self._eventsMap[name] = self._eventsMap[name] or {} 15 | local args = {...} 16 | for callback,times in pairs(self._eventsMap[name]) do 17 | xpcall(function() 18 | callback(unpack(args)) 19 | end, function(error) 20 | print('event trigger error:', error) 21 | end) 22 | if not self._eventsMap[name][callback] then 23 | -- listener was removed in callback 24 | elseif times <= 0 then 25 | -- continue listening until removed 26 | elseif times > 1 then 27 | self._eventsMap[name][callback] = times - 1 28 | elseif times == 1 then 29 | self._eventsMap[name][callback] = nil 30 | else 31 | error('not expected') 32 | end 33 | end 34 | end 35 | 36 | function Events:addListener(name, listener, times) 37 | assert(type(name) == 'string', 'event name should be function') 38 | assert(type(listener) == 'function', 'event listener should be function') 39 | self._eventsMap[name] = self._eventsMap[name] or {} 40 | if times == nil then 41 | times = 1 42 | elseif times == false then 43 | times = 1 44 | elseif times == true then 45 | times = 0 46 | elseif type(times) == 'number' then 47 | times = math.max(times, 0) 48 | else 49 | error('event times should be number') 50 | end 51 | self._eventsMap[name][listener] = times 52 | end 53 | 54 | function Events:removeListener(name, listener) 55 | assert(type(name) == 'string', 'event name should be function') 56 | assert(type(listener) == 'function', 'event listener should be function') 57 | self._eventsMap[name] = self._eventsMap[name] or {} 58 | self._eventsMap[name][listener] = nil 59 | end 60 | 61 | function Events:removeListeners(name) 62 | assert(type(name) == 'string', 'event name should be function') 63 | self._eventsMap[name] = {} 64 | end 65 | 66 | function Events:hasListener(name, listener) 67 | assert(type(name) == 'string', 'event name should be function') 68 | assert(type(listener) == 'function', 'event listener should be function') 69 | return self._eventsMap[name] ~= nil and self._eventsMap[name][listener] ~= nil 70 | end 71 | -------------------------------------------------------------------------------- /files/bit.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | bit 3 | ]] 4 | 5 | bit = bit or {} 6 | bit.WEIGHTS = {} 7 | bit.DIGIT = 32 8 | 9 | for i = 1, bit.DIGIT do 10 | bit.WEIGHTS[i] = 2 ^ (32 - i) 11 | end 12 | 13 | function bit.table2number(tb) 14 | local negative = tb[1] == 1 15 | local nr = 0 16 | for i = 1, bit.DIGIT do 17 | local v = nil 18 | if negative then 19 | v = tb[i] == 1 and 0 or 1 20 | else 21 | v = tb[i] 22 | end 23 | if v == 1 then 24 | nr = nr + bit.WEIGHTS[i] 25 | end 26 | end 27 | if negative then 28 | nr = nr + 1 29 | nr = -nr 30 | end 31 | return nr 32 | end 33 | 34 | function bit.number2table(nm) 35 | nm = nm >= 0 and nm or (0xFFFFFFFF + nm + 1) 36 | local tb = {} 37 | for i = 1, bit.DIGIT do 38 | if nm >= bit.WEIGHTS[i] then 39 | tb[i] = 1 40 | nm = nm - bit.WEIGHTS[i] 41 | else 42 | tb[i] = 0 43 | end 44 | end 45 | return tb 46 | end 47 | 48 | -- logic 49 | function bit.rshift(a, n) 50 | local tb = bit.number2table(a) 51 | n = math.max(0, math.min(bit.DIGIT, n)) 52 | for i = bit.DIGIT, 1, -1 do 53 | tb[i] = tb[i - n] or 0 54 | end 55 | return bit.table2number(tb) 56 | end 57 | 58 | -- arithmetic 59 | function bit.arshift(a, n) 60 | local tb = bit.number2table(a) 61 | n = math.max(0, math.min(bit.DIGIT, n)) 62 | local fill = a < 0 and 1 or 0 63 | for i = bit.DIGIT, 1, -1 do 64 | tb[i] = tb[i - n] or fill 65 | end 66 | return bit.table2number(tb) 67 | end 68 | 69 | function bit.lshift(a, n) 70 | local tb = bit.number2table(a) 71 | n = math.max(0, math.min(bit.DIGIT, n)) 72 | for i = 1, bit.DIGIT do 73 | tb[i] = tb[i + n] or 0 74 | end 75 | return bit.table2number(tb) 76 | end 77 | 78 | function bit.band(a, b) 79 | local tb1 = bit.number2table(a) 80 | local tb2 = bit.number2table(b) 81 | local r = {} 82 | for i = 1, bit.DIGIT do 83 | r[i] = (tb1[i] == 1 and tb2[i] == 1) and 1 or 0 84 | end 85 | return bit.table2number(r) 86 | end 87 | 88 | function bit.bor(a, b) 89 | local tb1 = bit.number2table(a) 90 | local tb2 = bit.number2table(b) 91 | local r = {} 92 | for i = 1, bit.DIGIT do 93 | r[i] = (tb1[i] == 1 or tb2[i] == 1) and 1 or 0 94 | end 95 | return bit.table2number(r) 96 | end 97 | 98 | function bit.bxor(a, b) 99 | local tb1 = bit.number2table(a) 100 | local tb2 = bit.number2table(b) 101 | local r = {} 102 | for i = 1, bit.DIGIT do 103 | r[i] = tb1[i] ~= tb2[i] and 1 or 0 104 | end 105 | return bit.table2number(r) 106 | end 107 | 108 | function bit.bnot(a) 109 | local tb = bit.number2table(a) 110 | local r = {} 111 | for i = 1, bit.DIGIT do 112 | r[i] = tb[i] == 1 and 0 or 1 113 | end 114 | return bit.table2number(r) 115 | end 116 | -------------------------------------------------------------------------------- /files/bmp.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | bmp 3 | ]] 4 | 5 | bmp = bmp or {} 6 | 7 | function bmp.write(filename, width, height, pixels) 8 | local file = assert(io.open(filename, "wb")) 9 | assert(width % 4 == 0, "Invalid 4-byte alignment for width") 10 | assert(height % 4 == 0, "Invalid 4-byte alignment for height") 11 | assert(file ~= nil, 'open file failed:' .. tostring(filename)) 12 | -- BMP文件头 13 | local fileheader = string.char(0x42, 0x4D) -- 文件类型,BM 14 | local filesize = 54 + 3 * width * height -- 文件大小 15 | fileheader = fileheader .. string.char( 16 | filesize % 256, 17 | math.floor(filesize / 256) % 256, 18 | math.floor(filesize / 65536) % 256, 19 | math.floor(filesize / 16777216) % 256 20 | ) -- 文件大小 21 | fileheader = fileheader .. string.rep(string.char(0), 4) -- 保留字段 22 | fileheader = fileheader .. string.char(54, 0, 0, 0) -- 数据起始位置 23 | -- BMP信息头 24 | local infoheader = string.char(40, 0, 0, 0) -- 信息头大小 25 | infoheader = infoheader .. string.char( 26 | width % 256, 27 | math.floor(width / 256) % 256, 28 | math.floor(width / 65536) % 256, 29 | math.floor(width / 16777216) % 256 30 | ) -- 图像宽度 31 | infoheader = infoheader .. string.char( 32 | height % 256, 33 | math.floor(height / 256) % 256, 34 | math.floor(height / 65536) % 256, 35 | math.floor(height / 16777216) % 256 36 | ) -- 图像高度 37 | infoheader = infoheader .. string.char(1, 0) -- 颜色平面数,必须为1 38 | infoheader = infoheader .. string.char(24, 0) -- 每个像素的位数,24位 39 | infoheader = infoheader .. string.rep(string.char(0), 4) -- 压缩方式,0表示不压缩 40 | local imagesize = 3 * width * height 41 | infoheader = infoheader .. string.char( 42 | imagesize % 256, 43 | math.floor(imagesize / 256) % 256, 44 | math.floor(imagesize / 65536) % 256, 45 | math.floor(imagesize / 16777216) % 256 46 | ) -- 图像数据大小 47 | infoheader = infoheader .. string.rep(string.char(0), 16) -- 其他信息 48 | -- 写入文件头和信息头 49 | file:write(fileheader) 50 | file:write(infoheader) 51 | -- 写入像素数据 52 | for y = height, 1, -1 do 53 | for x = 1, width do 54 | local pixel = pixels[y][x] 55 | file:write(string.char(pixel[3], pixel[2], pixel[1])) 56 | end 57 | end 58 | file:close() 59 | end 60 | 61 | function bmp.read(filename) 62 | local file = assert(io.open(filename, "rb")) 63 | assert(file ~= nil, 'open file failed:' .. tostring(filename)) 64 | -- BMP文件头 65 | local fileheader = file:read(14) 66 | local filetype = fileheader:sub(1,2) 67 | assert(filetype == "BM", "Not a BMP file") 68 | local filesize = fileheader:byte(3) + 69 | fileheader:byte(4) * 256 + 70 | fileheader:byte(5) * 65536 + 71 | fileheader:byte(6) * 16777216 72 | local datastart = fileheader:byte(11) + 73 | fileheader:byte(12) * 256 + 74 | fileheader:byte(13) * 65536 + 75 | fileheader:byte(14) * 16777216 76 | 77 | -- BMP信息头 78 | local infoheader = file:read(40) 79 | local width = infoheader:byte(5) + 80 | infoheader:byte(6) * 256 + 81 | infoheader:byte(7) * 65536 + 82 | infoheader:byte(8) * 16777216 83 | local height = infoheader:byte(9) + 84 | infoheader:byte(10) * 256 + 85 | infoheader:byte(11) * 65536 + 86 | infoheader:byte(12) * 16777216 87 | local bitsperpixel = infoheader:byte(15) + 88 | infoheader:byte(16) * 256 89 | assert(width % 4 == 0, "Invalid 4-byte alignment for width") 90 | assert(height % 4 == 0, "Invalid 4-byte alignment for height") 91 | assert(bitsperpixel == 24, "Only 24-bit BMP files are supported") 92 | local compression = infoheader:byte(17) + 93 | infoheader:byte(18) * 256 + 94 | infoheader:byte(19) * 65536 + 95 | infoheader:byte(20) * 16777216 96 | assert(compression == 0, "Compressed BMP files are not supported") 97 | -- 跳过可能存在的调色板数据 98 | local palette = file:read(datastart - 54) 99 | -- 读取像素数据 100 | local pixels = {} 101 | for y = height, 1, -1 do 102 | pixels[y] = {} 103 | for x = 1, width do 104 | local b = file:read(1):byte() 105 | local g = file:read(1):byte() 106 | local r = file:read(1):byte() 107 | pixels[y][x] = {r, g, b} 108 | end 109 | -- 跳过每行可能存在的填充字节 110 | file:read((4 - (width * 3) % 4) % 4) 111 | end 112 | file:close() 113 | return width, height, pixels 114 | end -------------------------------------------------------------------------------- /files/canvas.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Canvas 3 | ]] 4 | 5 | assert(Canvas == nil) 6 | Canvas = class("Canvas") 7 | 8 | function Canvas:__init__(w, h) 9 | self._width = w 10 | self._height = h 11 | self._empty = 0x000000 12 | self._pixels = {} 13 | end 14 | 15 | function Canvas:setPixel(x, y, pixel) 16 | local xi, xf = math.modf(x) 17 | local yi, yf = math.modf(y) 18 | if xf == 0.5 and yf == 0.5 then 19 | return 20 | elseif xf == 0.5 then 21 | self:setPixel(xi, y, pixel) 22 | self:setPixel(xi + 1, y, pixel) 23 | return 24 | elseif yf == 0.5 then 25 | self:setPixel(x, yi, pixel) 26 | self:setPixel(x, yi + 1, pixel) 27 | return 28 | end 29 | x = math.round(x) 30 | y = math.round(y) 31 | if not self._pixels[x] then 32 | self._pixels[x] = {} 33 | end 34 | self._pixels[x][y] = pixel 35 | return self 36 | end 37 | 38 | function Canvas:getPixel(x, y) 39 | return self._pixels[y] and self._pixels[y][x] or self._empty 40 | end 41 | 42 | function Canvas:getPixels(x, y, w, h) 43 | x = x or 1 44 | y = y or 1 45 | w = w or self._width 46 | h = h or self._height 47 | local r = {} 48 | for i=y,h do 49 | for j=x,w do 50 | table.insert(r, self:getPixel(j, i)) 51 | end 52 | end 53 | return r 54 | end 55 | 56 | function Canvas:drawLine(fromX, fromY, toX, toY, pixel) 57 | local dx = toX >= fromX and 1 or -1 58 | local dy = toY >= fromY and 1 or -1 59 | local kx = (toY - fromY) / (toX - fromX) 60 | local ky = (toX - fromX) / (toY - fromY) 61 | local bx = fromY - kx * fromX 62 | local by = fromX - ky * fromY 63 | if kx ~= math.huge and kx ~= -math.huge and kx == kx then 64 | for x=fromX,toX,dx do 65 | local y = kx * x + bx 66 | self:setPixel(x, y, pixel) 67 | end 68 | end 69 | if ky ~= math.huge and ky ~= -math.huge and ky == ky then 70 | for y=fromY,toY,dy do 71 | local x = ky * y + by 72 | self:setPixel(x, y, pixel) 73 | end 74 | end 75 | return self 76 | end 77 | 78 | function Canvas:drawRect(fromX, fromY, toX, toY, isFill, pixel) 79 | local d = toX >= fromX and 1 or -1 80 | for x=fromX,toX,d do 81 | if isFill or x == fromX or x == toX then 82 | self:drawLine(x, fromY, x, toY, pixel) 83 | else 84 | self:setPixel(x, fromY, pixel) 85 | self:setPixel(x, toY, pixel) 86 | end 87 | end 88 | return self 89 | end 90 | 91 | function Canvas:drawCircle(cx, cy, r, isFill, pixel) 92 | for x=-r,r do 93 | y = math.round(math.sqrt(r * r - x * x)) 94 | if isFill then 95 | self:drawLine(cx + x, cy + y, cx + x, cy - y, pixel) 96 | else 97 | self:setPixel(cx + x, cy + y, pixel) 98 | self:setPixel(cx + x, cy - y, pixel) 99 | end 100 | end 101 | for y=-r,r do 102 | x = math.round(math.sqrt(r * r - y * y)) 103 | if isFill then 104 | self:drawLine(cx + x, cy + y, cx - x, cy + y, pixel) 105 | else 106 | self:setPixel(cx + x, cy + y, pixel) 107 | self:setPixel(cx - x, cy + y, pixel) 108 | end 109 | end 110 | return self 111 | end 112 | 113 | function Canvas:drawEllipse(cx, cy, w, h, pixel) 114 | for y=cy-h,cy+h do 115 | for x=cx-w,cx+w do 116 | local tx = (x - cx) / (w * 2) 117 | local ty = (y - cy) / (h * 2) 118 | if tx * tx + ty * ty <= 0.25 then 119 | self:setPixel(x, y, pixel) 120 | end 121 | end 122 | end 123 | return self 124 | end 125 | -------------------------------------------------------------------------------- /files/class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | class 3 | ]] 4 | 5 | local classMap = {} 6 | 7 | -- create object 8 | local function new(Class, ...) 9 | -- asset values 10 | assert(is_class(Class), "invalid class table") 11 | -- object table 12 | local object = { 13 | __type__ = "object", 14 | __name__ = Class.__name__, 15 | __class__ = Class 16 | } 17 | -- object meta 18 | local objectMeta = { 19 | __index = Class, 20 | __tostring = function(object) 21 | return string.format("", object.__name__, lua_get_pointer(object), object.__path__) 22 | end 23 | } 24 | local object = setmetatable(object, objectMeta) 25 | -- init object 26 | assert(object.__init__, string.format("not constructor for class: %s", object.__class__.__name__)) 27 | object.__init__(object, ...) 28 | -- return object 29 | return object 30 | end 31 | 32 | -- create class 33 | assert(class == nil) 34 | function class(name, Base) 35 | -- assert values 36 | assert(is_string(name), "invalid class name") 37 | assert(is_nil(Base) or is_class(Base), "invalid class base") 38 | assert(is_nil(classMap[name]), "multiple class name") 39 | -- class table 40 | local Class = { 41 | __type__ = "class", 42 | __name__ = name, 43 | __super__ = Base, 44 | __path__ = lua_script_path(1), 45 | } 46 | -- class meta 47 | local ClassMeta = { 48 | __tostring = function(Class) 49 | return string.format("", Class.__name__, lua_get_pointer(Class), Class.__path__) 50 | end, 51 | __call = function(Class, ...) 52 | return new(Class, ...) 53 | end 54 | } 55 | if Class.__super__ then 56 | ClassMeta.__index = Class.__super__ 57 | end 58 | setmetatable(Class, ClassMeta) 59 | -- return class 60 | return Class, Base 61 | end 62 | -------------------------------------------------------------------------------- /files/colors.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | colors 3 | ]] 4 | 5 | colors = colors or {} 6 | 7 | function colors.rgb_to_hex(rgb) 8 | return bit.lshift(rgb[1], 16) + bit.lshift(rgb[2], 8) + rgb[3] 9 | end 10 | 11 | function colors.hex_to_rgb(hex) 12 | local r = bit.band(bit.rshift(hex, 16), 0xFF) 13 | local g = bit.band(bit.rshift(hex, 8), 0xFF) 14 | local b = bit.band(hex, 0xFF) 15 | return {r, g, b} 16 | end 17 | 18 | function colors.rgb_to_cmyk(rgb) 19 | local cyan = 255 - rgb[1] 20 | local magenta = 255 - rgb[2] 21 | local yellow = 255 - rgb[3] 22 | local black = math.min(cyan, magenta, yellow) 23 | local cyan = ((cyan - black) / (255 - black)) 24 | local magenta = ((magenta - black) / (255 - black)) 25 | local yellow = ((yellow - black) / (255 - black)) 26 | return {cyan, magenta, yellow, black / 255} 27 | end 28 | 29 | function colors.cmyk_to_rgb(cmyk) 30 | local k = cmyk[4] 31 | local R = cmyk[1] * (1.0 - k) + k 32 | local G = cmyk[2] * (1.0 - k) + k 33 | local B = cmyk[3] * (1.0 - k) + k 34 | R = math.floor((1.0 - R) * 255.0 + 0.5) 35 | G = math.floor((1.0 - G) * 255.0 + 0.5) 36 | B = math.floor((1.0 - B) * 255.0 + 0.5) 37 | return {R, G, B} 38 | end 39 | 40 | function colors.rgb_to_str(rgb) 41 | local r_hex = string.format("%02X", rgb[1]) 42 | local g_hex = string.format("%02X", rgb[2]) 43 | local b_hex = string.format("%02X", rgb[3]) 44 | return "#" .. r_hex .. g_hex .. b_hex 45 | end 46 | 47 | function colors.str_to_rgb(str) 48 | if string.sub(str, 1, 1) == "#" then 49 | str = string.sub(str, 2, -1) 50 | end 51 | local r_hex = string.sub(str, 1, 2) 52 | local g_hex = string.sub(str, 3, 4) 53 | local b_hex = string.sub(str, 5, 6) 54 | return {tonumber(r_hex, 16), tonumber(g_hex, 16), tonumber(b_hex, 16)} 55 | end 56 | 57 | function colors.rgb_mix_colors(color, ...) 58 | local r = color[1] 59 | local g = color[2] 60 | local b = color[3] 61 | local t = {...} 62 | for i,v in ipairs(t) do 63 | r = r + v[1] 64 | g = g + v[2] 65 | b = b + v[3] 66 | end 67 | local c = #t + 1 68 | r = math.floor(r / c) 69 | g = math.floor(g / c) 70 | b = math.floor(b / c) 71 | return {r, g, b} 72 | end 73 | 74 | function colors.rgb_adjust_brightness(rgb, percent) 75 | local factor = (100 + percent) / 100 76 | local r = math.min(255, math.max(0, math.floor(rgb[1] * factor))) 77 | local g = math.min(255, math.max(0, math.floor(rgb[2] * factor))) 78 | local b = math.min(255, math.max(0, math.floor(rgb[3] * factor))) 79 | return {r, g, b} 80 | end 81 | 82 | function colors.rgb_get_brightness(rgb) 83 | return (rgb[1] * 299 + rgb[2] * 587 + rgb[3] * 114) / 1000 84 | end 85 | 86 | function colors.rgb_get_grayscale(rgb) 87 | local gray = (rgb[1] + rgb[2] + rgb[3]) / 3 88 | gray = math.round(gray) 89 | return {gray, gray, gray} 90 | end 91 | 92 | function colors.rgb_get_complementary(rgb) 93 | local r = 255 - rgb[1] 94 | local g = 255 - rgb[2] 95 | local b = 255 - rgb[3] 96 | return {r, g, b, a} 97 | end 98 | 99 | function colors.get_random_rgb() 100 | return {math.random(1, 255), math.random(1, 255), math.random(1, 255)} 101 | end 102 | -------------------------------------------------------------------------------- /files/console.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | console 3 | ]] 4 | 5 | console = console or {} 6 | 7 | local LINE_LENGTH = 50 8 | 9 | local style_flag = nil 10 | local STYLE_MAP = { 11 | RESET = 0, 12 | BOLD = 1, 13 | UNDERLINE = 4, 14 | INVERSE = 7, 15 | } 16 | local COLOR_MAP = { 17 | BLACK = {90, 40}, 18 | RED = {91, 41}, 19 | GREEN = {92, 42}, 20 | YELLOW = {93, 43}, 21 | BLUE = {94, 44}, 22 | MAGENTA = {95, 45}, 23 | CYAN = {96, 46}, 24 | WHITE = {97, 47}, 25 | } 26 | 27 | local function _console_print_format(format, ...) 28 | local args = {...} 29 | if not style_flag then 30 | style_flag = true 31 | os.execute('cd > nul 2>&1') 32 | end 33 | io.write(format) 34 | for i,v in ipairs(args) do 35 | io.write(i == 1 and "" or " ", v) 36 | end 37 | io.write('\27[0m') 38 | end 39 | 40 | function console.print_colorful_no_wrap(fgName, bgName, ...) 41 | local fgInfo = COLOR_MAP[fgName] or COLOR_MAP.WHITE 42 | local bgInfo = COLOR_MAP[bgName] or COLOR_MAP.BLACK 43 | local fgColor = fgInfo[1] 44 | local bgColor = bgInfo[2] 45 | local format = string.format('\27[%d;%dm', bgColor, fgColor) 46 | _console_print_format(format, ...) 47 | end 48 | 49 | function console.print_colorful_with_wrap(fgName, bgName, ...) 50 | console.print_colorful_no_wrap(fgName, bgName, ...) 51 | io.write('\n') 52 | end 53 | 54 | function console.print_colorful(fgName, bgName, ...) 55 | print_colorful_with_wrap(fgName, bgName, ...) 56 | end 57 | 58 | function console.print_styled_no_wrap(name, ...) 59 | name = name and string.upper(name) or "RESET" 60 | local style = STYLE_MAP[name] or STYLE_MAP.RESETd 61 | local format = string.format('\27[%dm', style) 62 | _console_print_format(format, ...) 63 | end 64 | 65 | function console.print_styled_with_wrap(name, ...) 66 | console.print_styled_no_wrap(name, ...) 67 | io.write('\n') 68 | end 69 | 70 | function console.print_styled(name, ...) 71 | print_styled_with_wrap(name, ...) 72 | end 73 | 74 | function console.print_inform() 75 | print(string.center("inform", LINE_LENGTH, "-")) 76 | print("|" .. string.center("Yes ?", LINE_LENGTH - 2, " ") .. "|") 77 | print(string.rep("-", LINE_LENGTH)) 78 | while true do 79 | io.write("> ") 80 | local input = string.upper(io.read()) 81 | if input == "TRUE" or input == "YES" or input == "Y" then 82 | print('* informed!') 83 | return true 84 | else 85 | console.delete_line(1) 86 | print('* inform:') 87 | end 88 | end 89 | end 90 | 91 | function console.print_confirm() 92 | print(string.center("confirm", LINE_LENGTH, "-")) 93 | print("|" .. string.center("Yes or No ?", LINE_LENGTH - 2, " ") .. "|") 94 | print(string.rep("-", LINE_LENGTH)) 95 | while true do 96 | io.write("> ") 97 | local input = string.upper(io.read()) 98 | if input == "FALSE" or input == "NO" or input == "N" then 99 | print('* confirmed!') 100 | return false 101 | elseif input == "TRUE" or input == "YES" or input == "Y" then 102 | print('* confirmed!') 103 | return true 104 | else 105 | console.delete_line(1) 106 | print('* confirm:') 107 | end 108 | end 109 | end 110 | 111 | function console.print_progress(rate, isReplace, charLeft, charMiddle, charRight) 112 | charLeft = charLeft ~= nil and charLeft:sub(1, 1) or "=" 113 | charMiddle = charMiddle ~= nil and charMiddle:sub(1, 1) or ">" 114 | charRight = charRight ~= nil and charRight:sub(1, 1) or "-" 115 | local size = LINE_LENGTH - 9 116 | local format = "[ %s %s ]\n" 117 | local progress = math.max(0, math.min(1, rate)) 118 | local bar = "" 119 | local isLeft = false 120 | local isMiddle = false 121 | local isRight = false 122 | for i=1,size do 123 | local v = i / size 124 | local isSmall = v < progress 125 | local isBig = v > progress 126 | if not isLeft and not isMiddle and not isRight then 127 | isLeft = isSmall 128 | elseif isLeft and not isMiddle and isBig then 129 | isLeft = false 130 | isMiddle = true 131 | elseif isMiddle and not isRight and isBig then 132 | isMiddle = false 133 | isRight = true 134 | end 135 | local char = charRight 136 | if isLeft then 137 | char = charLeft 138 | elseif isMiddle then 139 | char = charMiddle 140 | end 141 | bar = bar .. char 142 | end 143 | local percent = string.center(string.format("%d%%", progress * 100), 4, " ") 144 | local text = string.format(format, bar, percent) 145 | console.delete_line(isReplace and 1 or 0, text, true) 146 | end 147 | 148 | function console.print_qrcode(content) 149 | print(string.center("qrcode", LINE_LENGTH, "-")) 150 | print("|") 151 | local isOk, datas = library.qrcode(content) 152 | assert(isOk == true, 'qrcode generate failed!') 153 | for i,column in ipairs(datas) do 154 | if i ~= 1 then 155 | io.write('\n') 156 | end 157 | for j,row in ipairs(column) do 158 | if j == 1 then 159 | io.write('| ') 160 | end 161 | io.write(row > 0 and "\27[47m \27[0m" or " ") 162 | if j == #column then 163 | io.write(' |') 164 | end 165 | end 166 | end 167 | io.write('\n') 168 | print("|") 169 | print(string.rep("-", LINE_LENGTH)) 170 | end 171 | 172 | function console.print_select(selections) 173 | selections = selections or {} 174 | local TEXT_LENGTH = LINE_LENGTH - 9 175 | -- 176 | if #selections <= 0 then 177 | return nil, -1 178 | end 179 | -- 180 | local lenLine = 0 181 | local _texts = {} 182 | 183 | for i,text in ipairs(selections) do 184 | local head = string.center(tostring(i), 3, " ") 185 | local body = nil 186 | if #text <= TEXT_LENGTH then 187 | body = string.left(text, TEXT_LENGTH, " ") 188 | else 189 | body = string.sub(text, 1, TEXT_LENGTH - 3) .. "..." 190 | end 191 | local line = string.format("| %s. %s |", head, body) 192 | _texts[i] = line 193 | lenLine = math.max(lenLine, #line) 194 | end 195 | -- 196 | print(string.center("select", lenLine, "-")) 197 | for i,text in ipairs(_texts) do 198 | print(text) 199 | end 200 | for i=0,#_texts do 201 | end 202 | print(string.rep("-", lenLine)) 203 | -- 204 | while true do 205 | io.write("> ") 206 | local input = io.read() 207 | local index = tonumber(input) 208 | if index and selections[index] then 209 | print('* selected!') 210 | return selections[index], index 211 | else 212 | console.delete_line(1) 213 | print('* select:') 214 | end 215 | end 216 | end 217 | 218 | function console.print_enter(isPassword, isNumber, checkFunc) 219 | local tip = "text" 220 | if isPassword then tip = "password" end 221 | if isNumber then tip = "number" end 222 | local title = string.format("Enter a %s ?", tip) 223 | print(string.center("enter", LINE_LENGTH, "-")) 224 | print("|" .. string.center(title, LINE_LENGTH - 2, " ") .. "|") 225 | print(string.rep("-", LINE_LENGTH)) 226 | while true do 227 | io.write("> ") 228 | local input = io.read() 229 | local skip = false 230 | if #input > 0 then 231 | if isNumber and tonumber(input) == nil then 232 | print("* invalid number!") 233 | print('* enter:') 234 | skip = true 235 | end 236 | if checkFunc then 237 | local isValid, errorMsg = checkFunc(input) 238 | if not isValid then 239 | if isPassword then 240 | console.delete_line(1, "> " .. string.rep("*", #input), false) 241 | end 242 | print("* " .. (errorMsg or "invalid format!")) 243 | print('* enter:') 244 | skip = true 245 | end 246 | end 247 | if not skip then 248 | if isPassword then 249 | console.delete_line(1, "> " .. string.rep("*", #input), false) 250 | end 251 | print('* entered!') 252 | return input 253 | end 254 | else 255 | console.delete_line(1, "* enter:", false) 256 | end 257 | end 258 | end 259 | 260 | function console.print_edit(_content) 261 | _content = _content or "" 262 | local content = _content 263 | print(string.center("edit", LINE_LENGTH, "-")) 264 | print("|" .. string.center("e:Edit s:Save p:Print r:Revert q:Quit", LINE_LENGTH - 2, " ") .. "|") 265 | print(string.rep("-", LINE_LENGTH)) 266 | while true do 267 | io.write("> ") 268 | local input = string.upper(io.read()) 269 | if input == "E" or input == "EDIT" then 270 | console.delete_line(1) 271 | print('* editing:') 272 | local path = files.temp_file() 273 | files.write(path, content) 274 | tools.edit_file(path) 275 | content = files.read(path) or content 276 | files.delete(path) 277 | print('* edited!') 278 | elseif input == "P" or input == "PRINT" then 279 | console.delete_line(1) 280 | local lines = {} 281 | for line in content:gmatch("[^\r\n]+") do 282 | table.insert(lines, line) 283 | end 284 | for i,v in ipairs(lines) do 285 | print("|" .. string.right(tostring(i), 3, " "), v) 286 | end 287 | print('* printed!') 288 | elseif input == "S" or input == "SAVE" then 289 | local path = dialog.select_save(title, filter, folder) 290 | console.delete_line(1) 291 | if path then 292 | files.write(path, content) 293 | print('* saved!') 294 | end 295 | elseif input == "R" or input == "RESET" then 296 | content = _content 297 | console.delete_line(1) 298 | print('* reverted!') 299 | elseif input == "Q" or input == "QUIT" then 300 | console.delete_line(1) 301 | print('* quitted!') 302 | break 303 | else 304 | console.delete_line(1) 305 | print('* edit:') 306 | end 307 | end 308 | return content 309 | end 310 | 311 | function console.delete_line(count, replacement, noWrap) 312 | local line = math.max(0, count or 1) 313 | local text = replacement or "" 314 | if noWrap == nil then noWrap = #text == 0 end 315 | text = text .. (noWrap and "" or "\n") 316 | text = line <= 0 and text or string.format("\027[%dF\027[0J", line) .. text 317 | io.write(text) 318 | end 319 | 320 | function console.clean_screen(replacement, noWrap) 321 | local text = replacement or "" 322 | if noWrap == nil then noWrap = #text == 0 end 323 | text = text .. (noWrap and "" or "\n") 324 | text = "\027[2J\027[1;1H" .. text 325 | io.write(text) 326 | end 327 | -------------------------------------------------------------------------------- /files/dialog.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | dialog 3 | ]] 4 | 5 | dialog = dialog or {} 6 | 7 | local POWERSHELL = [[ 8 | 9 | param( 10 | [string]$funcName, 11 | [string]$arg1, 12 | [string]$arg2, 13 | [string]$arg3, 14 | [string]$arg4, 15 | [string]$arg5 16 | ) 17 | 18 | Function return_result([string]$result) { 19 | Write-Host "[result[$result]result]" 20 | } 21 | 22 | Function select_file($windowTitle, $filterDesc, $startFolder) { 23 | Add-Type -AssemblyName System.Windows.Forms 24 | $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog 25 | $OpenFileDialog.Title = $windowTitle 26 | $OpenFileDialog.InitialDirectory = $startFolder 27 | $OpenFileDialog.filter = $filterDesc 28 | If ($OpenFileDialog.ShowDialog() -eq "Cancel") { 29 | return_result "" 30 | } Else { 31 | return_result $OpenFileDialog.FileName 32 | } 33 | } 34 | 35 | function select_save($windowTitle, $filterDesc, $startFolder) { 36 | [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null 37 | $OpenFileDialog = New-Object System.Windows.Forms.SaveFileDialog 38 | $OpenFileDialog.Title = $windowTitle 39 | $OpenFileDialog.initialDirectory = $startFolder 40 | $OpenFileDialog.filter = $filterDesc 41 | $OpenFileDialog.ShowDialog() | Out-Null 42 | return_result $OpenFileDialog.filename 43 | } 44 | 45 | Function select_folder($windowTitle, $startFolder) { 46 | [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null 47 | $foldername = New-Object System.Windows.Forms.FolderBrowserDialog 48 | $foldername.Description = $windowTitle 49 | $foldername.rootfolder = "MyComputer" 50 | $foldername.SelectedPath = $startFolder 51 | If ($foldername.ShowDialog() -eq "OK") { 52 | return_result $foldername.SelectedPath 53 | } else { 54 | return_result "" 55 | } 56 | } 57 | 58 | function select_color() { 59 | [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null 60 | $dialog = New-Object System.Windows.Forms.ColorDialog 61 | $dialog.AnyColor = $true 62 | if ($dialog.ShowDialog() -eq "OK") { 63 | return_result "$($dialog.Color.R),$($dialog.Color.G),$($dialog.Color.B)" 64 | } Else { 65 | return_result "" 66 | } 67 | } 68 | 69 | Function show_confirm($title, $message, $flag) { 70 | Add-Type -AssemblyName Microsoft.VisualBasic 71 | $result = [Microsoft.VisualBasic.Interaction]::MsgBox($message, $flag, $title) 72 | return_result $result 73 | } 74 | 75 | Function show_input($title, $message, $default) { 76 | Add-Type -AssemblyName Microsoft.VisualBasic 77 | $result = [Microsoft.VisualBasic.Interaction]::InputBox($message, $title, $default) 78 | return_result $result 79 | } 80 | 81 | & $funcName $arg1 $arg2 $arg3 $arg4 $arg5 82 | 83 | ]] 84 | 85 | local function dialog_execute_powershell(func, ...) 86 | assert(tools.is_windows(), 'platform not supported!') 87 | files.write("./running.ps1", POWERSHELL) 88 | local cmd = func 89 | local agrs = {...} 90 | for i,v in ipairs(agrs) do 91 | cmd = cmd .. [[ "]] .. tostring(v) .. [["]] 92 | end 93 | local isOk, r = tools.execute([[ powershell.exe -file ./running.ps1 ]] .. cmd) 94 | files.delete("running.ps1") 95 | assert(isOk, 'powershell execute failed:' .. cmd) 96 | return r:match(".*%[result%[(.*)%]result%].*") 97 | end 98 | 99 | local function dialog_validate_folder(folder) 100 | folder = folder:gsub('/', '\\') 101 | if folder:sub(-1, -1) == '\\' then 102 | folder = folder:sub(1, -2) 103 | end 104 | folder = folder:gsub('\\\\', '\\') 105 | return folder 106 | end 107 | 108 | function dialog.select_file(title, filter, folder) 109 | title = title or "please select a file ..." 110 | filter = filter or "All files (*.*)|*.*" 111 | folder = folder or "" 112 | print(dialog_validate_folder(folder)) 113 | local path = dialog_execute_powershell("select_file", title, filter, dialog_validate_folder(folder)) 114 | if string.valid(path) then 115 | return path 116 | end 117 | end 118 | 119 | function dialog.select_folder(title, folder) 120 | title = title or "please select a folder ..." 121 | folder = folder or "" 122 | local path = dialog_execute_powershell("select_folder", title, dialog_validate_folder(folder)) 123 | if string.valid(path) then 124 | return path 125 | end 126 | end 127 | 128 | function dialog.select_save(title, filter, folder) 129 | title = title or "please save a file ..." 130 | filter = filter or "All files (*.*)|*.*" 131 | folder = folder or "" 132 | local path = dialog_execute_powershell("select_save", title, filter, dialog_validate_folder(folder)) 133 | if string.valid(path) then 134 | return path 135 | end 136 | end 137 | 138 | function dialog.select_color() 139 | local color = dialog_execute_powershell("select_color") 140 | if string.valid(color) then 141 | local t = string.explode(color, ",") 142 | local r, g, b = tonumber(t[1]), tonumber(t[2]), tonumber(t[3]) 143 | return r, g, b 144 | end 145 | end 146 | 147 | function dialog.show_confirm(title, message, flag) 148 | title = title or "title..." 149 | message = message or "confirm..." 150 | flag = flag or "YesNoCancel" -- YesNoCancel, YesNo, OkCancel, OKOnly, Critical, Question, Exclamation, Information 151 | local r = dialog_execute_powershell("show_confirm", title, message, flag) 152 | if r == "Yes" or r == "Ok" then return true end 153 | if r == "No" then return false end 154 | return nil 155 | end 156 | 157 | function dialog.show_input(title, message, default) 158 | title = title or "title..." 159 | message = message or "input..." 160 | default = default or "" 161 | local result = dialog_execute_powershell("show_input", title, message, default) 162 | if string.valid(result) then 163 | return result 164 | end 165 | end 166 | 167 | function dialog.open_path(path) 168 | if tools.is_windows() then 169 | path = dialog_validate_folder(path) 170 | return tools.execute([[start %windir%\explorer.exe "]] .. path .. [["]]) 171 | else 172 | assert('open path not implemented on this platform') 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /files/encryption.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | encrypt 3 | ]] 4 | 5 | encryption = encryption or {} 6 | 7 | --[[ 8 | Add integers, wrapping at 2^32. This uses 16-bit operations internally 9 | to work around bugs in some JS interpreters. 10 | --]] 11 | local function safeAdd(x, y) 12 | if x == nil then 13 | x = 0 14 | end 15 | if y == nil then 16 | y = 0 17 | end 18 | local lsw = bit.band(x, 0xffff) + bit.band(y, 0xffff) 19 | local msw = bit.arshift(x, 16) + bit.arshift(y, 16) + bit.arshift(lsw, 16) 20 | return bit.bor(bit.lshift(msw, 16), bit.band(lsw, 0xffff)) 21 | end 22 | 23 | --[[ 24 | Bitwise rotate a 32-bit number to the left. 25 | --]] 26 | local function bitRotateLeft(num, cnt) 27 | return bit.bor(bit.lshift(num, cnt), bit.rshift(num, (32 - cnt))) 28 | end 29 | 30 | --[[ 31 | These local functions implement the four basic operations the algorithm uses. 32 | --]] 33 | local function md5cmn(q, a, b, x, s, t) 34 | return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) 35 | end 36 | local function md5ff(a, b, c, d, x, s, t) 37 | return md5cmn(bit.bor(bit.band(b, c), bit.band(bit.bnot(b), d)), a, b, x, s, t) 38 | end 39 | local function md5gg(a, b, c, d, x, s, t) 40 | return md5cmn(bit.bor(bit.band(b, d), bit.band(c, bit.bnot(d))), a, b, x, s, t) 41 | end 42 | local function md5hh(a, b, c, d, x, s, t) 43 | return md5cmn(bit.bxor(b, bit.bxor(c, d)), a, b, x, s, t) 44 | end 45 | local function md5ii(a, b, c, d, x, s, t) 46 | return md5cmn(bit.bxor(c, bit.bor(b, bit.bnot(d))), a, b, x, s, t) 47 | end 48 | 49 | --[[ 50 | Calculate the MD5 of an array of little-endian words, and a bit length. 51 | --]] 52 | local function binlMD5(x, len) 53 | -- append padding 54 | x[1 + bit.arshift(len, 5)] = bit.bor(x[1 + bit.arshift(len, 5)], bit.lshift(0x80, (len % 32))) 55 | x[1 + bit.lshift(bit.rshift(len + 64, 9), 4) + 14] = len 56 | 57 | local i 58 | local olda 59 | local oldb 60 | local oldc 61 | local oldd 62 | local a = 1732584193 63 | local b = -271733879 64 | local c = -1732584194 65 | local d = 271733878 66 | 67 | for i = 1, #x, 16 do 68 | olda = a 69 | oldb = b 70 | oldc = c 71 | oldd = d 72 | 73 | a = md5ff(a, b, c, d, x[i], 7, -680876936) 74 | d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) 75 | c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) 76 | b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) 77 | a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) 78 | d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) 79 | c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) 80 | b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) 81 | a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) 82 | d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) 83 | c = md5ff(c, d, a, b, x[i + 10], 17, -42063) 84 | b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) 85 | a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) 86 | d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) 87 | c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) 88 | b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) 89 | 90 | a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) 91 | d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) 92 | c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) 93 | b = md5gg(b, c, d, a, x[i], 20, -373897302) 94 | a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) 95 | d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) 96 | c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) 97 | b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) 98 | a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) 99 | d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) 100 | c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) 101 | b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) 102 | a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) 103 | d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) 104 | c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) 105 | b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) 106 | 107 | a = md5hh(a, b, c, d, x[i + 5], 4, -378558) 108 | d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) 109 | c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) 110 | b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) 111 | a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) 112 | d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) 113 | c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) 114 | b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) 115 | a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) 116 | d = md5hh(d, a, b, c, x[i], 11, -358537222) 117 | c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) 118 | b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) 119 | a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) 120 | d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) 121 | c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) 122 | b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) 123 | 124 | a = md5ii(a, b, c, d, x[i], 6, -198630844) 125 | d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) 126 | c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) 127 | b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) 128 | a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) 129 | d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) 130 | c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) 131 | b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) 132 | a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) 133 | d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) 134 | c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) 135 | b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) 136 | a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) 137 | d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) 138 | c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) 139 | b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) 140 | 141 | a = safeAdd(a, olda) 142 | b = safeAdd(b, oldb) 143 | c = safeAdd(c, oldc) 144 | d = safeAdd(d, oldd) 145 | end 146 | return {a, b, c, d} 147 | end 148 | 149 | --[[ 150 | Convert an array of little-endian words to a string 151 | --]] 152 | local function binl2rstr(input) 153 | local i 154 | local output = {} 155 | local length32 = #input * 32 156 | for i = 0, length32 - 1, 8 do 157 | table.insert(output, string.char(bit.band(bit.rshift(input[1 + bit.arshift(i, 5)], i % 32), 0xff))) 158 | end 159 | return table.concat(output, '') 160 | end 161 | 162 | --[[ 163 | Convert a raw string to an array of little-endian words 164 | --]] 165 | local function rstr2binl(input) 166 | local output = {} 167 | for i = 1, bit.arshift(string.len(input), 2) do 168 | output[i] = 0 169 | end 170 | local length8 = string.len(input) * 8 171 | for i = 0, length8 - 1, 8 do 172 | local p = 1 + bit.arshift(i, 5); 173 | if output[p] == nil then 174 | output[p] = 0 175 | end 176 | output[p] = bit.bor(output[p], bit.lshift(bit.band(input:byte((i / 8) + 1), 0xff), (i % 32))) 177 | end 178 | return output 179 | end 180 | 181 | local function rstrMD5(s) 182 | return binl2rstr(binlMD5(rstr2binl(s), string.len(s) * 8)) 183 | end 184 | 185 | local function charAt(str, n) 186 | return string.sub(str, n, n) 187 | end 188 | 189 | local function rstr2hex(input) 190 | local hexTab = '0123456789abcdef' 191 | local output = {} 192 | for i = 1, string.len(input) do 193 | local x = input:byte(i) 194 | table.insert(output, charAt(hexTab, 1 + bit.band(bit.rshift(x, 4), 0x0f))) 195 | table.insert(output, charAt(hexTab, 1 + bit.band(x, 0x0f))) 196 | end 197 | return table.concat(output, '') 198 | end 199 | 200 | function encryption.md5(str) 201 | return rstr2hex(rstrMD5(str)) 202 | end 203 | 204 | -- http://lua-users.org/wiki/BaseSixtyFour 205 | local b ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 206 | 207 | function encryption.base64_encode(data) 208 | return ((data:gsub('.', function(x) 209 | local r,b='',x:byte() 210 | for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end 211 | return r; 212 | end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) 213 | if (#x < 6) then return '' end 214 | local c=0 215 | for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end 216 | return b:sub(c+1,c+1) 217 | end)..({ '', '==', '=' })[#data%3+1]) 218 | end 219 | 220 | function encryption.base64_decode(data) 221 | data = string.gsub(data, '[^'..b..'=]', '') 222 | return (data:gsub('.', function(x) 223 | if (x == '=') then return '' end 224 | local r,f='',(b:find(x)-1) 225 | for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end 226 | return r; 227 | end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) 228 | if (#x ~= 8) then return '' end 229 | local c=0 230 | for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end 231 | return string.char(c) 232 | end)) 233 | end 234 | 235 | local function bxor_transform(key, text) 236 | assert(string.valid(key)) 237 | assert(is_string(key)) 238 | local encrypted = "" 239 | local keyLength = #key 240 | for i = 1, #text do 241 | local origin = string.byte(text, i) 242 | local salt = string.byte(key, (i - 1) % keyLength + 1) 243 | local encoded = bit.bxor(origin, salt) 244 | encrypted = encrypted .. string.char(encoded) 245 | end 246 | return encrypted 247 | end 248 | 249 | function encryption.bxor_encode(key, text) 250 | return bxor_transform(key, text) 251 | end 252 | 253 | function encryption.bxor_decode(key, text) 254 | return bxor_transform(key, text) 255 | end 256 | -------------------------------------------------------------------------------- /files/files.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | file 3 | ]] 4 | 5 | files = files or {} 6 | 7 | local delimiter = nil 8 | function files.delimiter() 9 | if delimiter then return delimiter end 10 | delimiter = string.find(os.tmpname(""), "\\") and "\\" or "/" 11 | return delimiter 12 | end 13 | 14 | function files.unixify(path) 15 | return path:gsub("\\+", "/"):gsub("/+", "/"):trim() 16 | end 17 | 18 | function files.home() 19 | local home = os.getenv('HOME') or os.getenv('USERPROFILE') 20 | return files.unixify(home) 21 | end 22 | 23 | function files.root() 24 | local cwd = files.cwd() 25 | return files.unixify(cwd):explode("/")[1] 26 | end 27 | 28 | function files.user() 29 | local path = string.format("%s/.%s/", files.home(), lua_get_user()) 30 | files.mk_folder(path) 31 | return path 32 | end 33 | 34 | function files.temp() 35 | local path = files.user() .. "/my-lua-tmp/" 36 | files.mk_folder(path) 37 | return path 38 | end 39 | 40 | function files.temp_file(name, ext) 41 | name = name or "unknown" 42 | ext = ext or "txt" 43 | local dateText = os.date("%Y-%m-%d_%H-%M-%S", os.time()) 44 | local tempName = os.tmpname():sub(2, -1) 45 | local tempFldr = files.temp() 46 | files.mk_folder(tempFldr) 47 | if string.ends(tempName, ".") then 48 | tempName = tempName .. "0" 49 | end 50 | return string.format("%s/%s_%s_%s.%s", tempFldr, name, dateText, tempName, ext) 51 | end 52 | 53 | function files.temp_clear(name) 54 | local tempFldr = files.temp() 55 | local list = files.list(tempFldr) 56 | local count = 0 57 | for i,path in ipairs(list) do 58 | if string.find(path, name) then 59 | files.delete(tempFldr .. "/" .. path) 60 | count = count + 1 61 | end 62 | end 63 | return count > 0 64 | end 65 | 66 | -- current working directory 67 | local cwd = nil 68 | function files.cwd() 69 | if cwd then return cwd end 70 | local isOk, output = nil, nil 71 | if tools.is_windows() then 72 | isOk, output = tools.execute("cd") 73 | else 74 | isOk, output = tools.execute("pwd") 75 | end 76 | assert(isOk and output ~= nil) 77 | cwd = output:trim():slash() .. '/' 78 | return files.unixify(cwd) 79 | end 80 | 81 | -- current script directory 82 | function files.csd(thread) 83 | local info = debug.getinfo(thread or 2) 84 | if not info then return end 85 | local path = info.source:sub(2, -1) 86 | assert(path ~= nil) 87 | path = path:trim():slash() 88 | local folder = files.get_folder(path) 89 | local csd = files.absolute(folder) 90 | return files.unixify(csd) 91 | end 92 | 93 | function files.absolute(this) 94 | if string.match(this, "^/") or string.match(this, "^%a:") then 95 | return this 96 | end 97 | return files.cwd() .. this .. "/" 98 | end 99 | 100 | function files.relative(this) 101 | return this:gsub(files.cwd(), '') 102 | end 103 | 104 | function files.write(path, content, mode) 105 | local f = io.open(path, mode or "w") 106 | if not f then return false end 107 | f:write(content) 108 | f:close() 109 | return true 110 | end 111 | 112 | function files.read(path, mode) 113 | local f = io.open(path, mode or "r") 114 | if not f then return end 115 | local content = f:read("*a") 116 | f:close() 117 | return content 118 | end 119 | 120 | function files.size(path) 121 | f = io.open(path, "rb") 122 | if not f then return 0 end 123 | local size = f:seek("end") 124 | f:close() 125 | return size 126 | end 127 | 128 | function files.delete(path) 129 | return os.remove(path) 130 | end 131 | 132 | function files.is_file(path) 133 | local f = io.open(path, "rb") 134 | if f then f:close() end 135 | return f ~= nil 136 | end 137 | 138 | function files.print(path) 139 | print("[file:" .. path .. "]") 140 | print("[[[[[[[") 141 | local lines = files.is_file(path) and io.lines(path) or ipairs({}) 142 | local num = 0 143 | for line in lines do 144 | num = num + 1 145 | print(" " .. tostring(num):right(7, "0") .. " " .. line) 146 | end 147 | print("]]]]]]]") 148 | end 149 | 150 | function files.copy(from, to) 151 | local f1 = io.open(from, 'rb') 152 | local f2 = io.open(to, 'wb') 153 | if not f1 or not f2 then return end 154 | f2:write(f1:read('*a')) 155 | f1:close() 156 | f2:close() 157 | return true 158 | end 159 | 160 | function files.sync(from, to) 161 | assert(files.is_folder(from), 'sync from path is invalid') 162 | files.mk_folder(to) 163 | local t = files.list(from) 164 | for i,v in ipairs(t) do 165 | local fromPath = from .."/" .. v 166 | local toPath = to .."/" .. v 167 | if files.is_file(fromPath) then 168 | files.copy(fromPath, toPath) 169 | elseif files.is_folder(fromPath) then 170 | files.sync(fromPath, toPath) 171 | end 172 | end 173 | end 174 | 175 | function files.is_folder(path) 176 | if not path then return false end 177 | local isOk, _ = tools.execute("cd " .. path) 178 | return isOk == true 179 | end 180 | 181 | function files.mk_folder(path) 182 | if files.is_folder(path) then return end 183 | local isOk 184 | if tools.is_windows() then 185 | isOk = tools.execute(string.format([[mkdir "%s"]], path)) 186 | else 187 | isOk = tools.execute(string.format([[mkdir -p "%s"]], path)) 188 | end 189 | return isOk == true 190 | end 191 | 192 | function files.list(path) 193 | local r = table.new() 194 | if not files.is_folder(path) then return r end 195 | local isOk, out 196 | if tools.is_windows() then 197 | isOk, out = tools.execute(string.format([[dir /b "%s"]], path)) 198 | else 199 | isOk, out = tools.execute(string.format([[ls "%s"]], path)) 200 | end 201 | t = out:explode('\n') 202 | for i,v in ipairs(t) do 203 | if is_string(v) and #v > 0 then 204 | table.insert(r, v) 205 | end 206 | end 207 | return r 208 | end 209 | 210 | function files.get_folder(filePath) 211 | return string.gsub(filePath, "[^\\/]+%.[^\\/]+", "") 212 | end 213 | 214 | function files.modified(path) 215 | local stamp = nil 216 | xpcall(function() 217 | local isOk, result = tools.execute("stat -f %m " .. path) -- mac 218 | if isOk then stamp = result end 219 | local isOk, result = tools.execute("stat -c %Y " .. path) -- linux 220 | if isOk then stamp = result end 221 | assert(stamp ~= nil, 'get modified stamp failed') 222 | end, function(err) 223 | print(err) 224 | end) 225 | if not stamp then 226 | return -1 227 | end 228 | local modified = tonumber(stamp) or -1 229 | return modified 230 | end 231 | 232 | function files.watch(paths, callback, triggerDelay) 233 | if is_string(paths) then paths = {paths} end 234 | assert(#paths >= 1, 'the paths to watch should not be empty') 235 | assert(is_function(callback), 'the last argument should be a callback func') 236 | for i, path in ipairs(paths) do 237 | assert(files.is_file(path) or files.is_folder(path), 'path not found in watch:' .. tostring(path)) 238 | end 239 | triggerDelay = triggerDelay or 1 240 | local modifiedMap = {} 241 | local function check(path) 242 | local modifiedTime = files.modified(path) 243 | if not modifiedMap[path] then 244 | callback(path, modifiedTime, true) 245 | modifiedMap[path] = modifiedTime 246 | elseif modifiedTime - modifiedMap[path] >= triggerDelay then 247 | callback(path, modifiedTime, false) 248 | modifiedMap[path] = modifiedTime 249 | end 250 | end 251 | while true do 252 | for i,v in ipairs(paths) do check(v) end 253 | end 254 | end 255 | -------------------------------------------------------------------------------- /files/graphic.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | graphic 3 | ]] 4 | 5 | assert(Graphic == nil) 6 | Graphic = class("Graphic") 7 | 8 | local HIDE_CONSOLE = [[ 9 | Add-Type -Name Window -Namespace Console -MemberDefinition ' 10 | [DllImport("Kernel32.dll")] 11 | public static extern IntPtr GetConsoleWindow(); 12 | [DllImport("user32.dll")] 13 | public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow); 14 | ' 15 | $consolePtr = [Console.Window]::GetConsoleWindow() 16 | [Console.Window]::ShowWindow($consolePtr, 0) # hide:0, show:5 17 | ]] 18 | 19 | local COMMON_HEADER = [[ 20 | [reflection.assembly]::LoadWithPartialName( "System.Drawing"); 21 | $brush = new-object Drawing.SolidBrush "#22ffcc" 22 | $pen = new-object Drawing.Pen "#22ffcc" 23 | $pen.width = 10 24 | $x = 0 25 | $y = 0 26 | $w = 250 27 | $h = 250 28 | $ax = 0.5 29 | $ay = 0.5 30 | ]] 31 | 32 | local FORM_CREATE = [[ 33 | [reflection.assembly]::LoadWithPartialName( "System.Windows.Forms"); 34 | [System.Windows.Forms.Application]::EnableVisualStyles(); 35 | $form = New-Object Windows.Forms.Form 36 | $form.ClientSize = '%d,%d' 37 | $form.StartPosition = 'CenterScreen' 38 | $graphics = $form.createGraphics() 39 | $form.add_paint({ 40 | ]] 41 | 42 | local FORM_SHOW = [[ 43 | }) 44 | $icon = New-Object system.drawing.icon ("%s") 45 | $form.Icon = $icon 46 | $form.text = "%s" 47 | $form.ShowDialog(); 48 | $graphics.Dispose() 49 | ]] 50 | 51 | local IMAGE_CREATE = [[ 52 | $bitmap = new-object System.Drawing.Bitmap 500,500 53 | $graphics = [System.Drawing.Graphics]::FromImage($bitmap) 54 | ]] 55 | 56 | local IMAGE_SAVE = [[ 57 | $bitmap.Save("%s") 58 | $graphics.Dispose() 59 | ]] 60 | 61 | local GRAPHIC_COLOR = [[ 62 | $brush.color = "%s" 63 | $pen.color = "%s" 64 | ]] 65 | 66 | local GRAPHIC_SIZE = [[ 67 | $pen.width = %d 68 | ]] 69 | 70 | local GRAPHIC_POSITION = [[ 71 | $x = %d 72 | $y = %d 73 | ]] 74 | 75 | local GRAPHIC_SIZE = [[ 76 | $w = %d 77 | $h = %d 78 | ]] 79 | 80 | local GRAPHIC_SCREEN = [[ 81 | $size = new-object System.Drawing.Size $w, $h 82 | $graphics.CopyFromScreen(%d, %d, $x, $y, $size); 83 | ]] 84 | 85 | local GRAPHIC_CLIP = [[ 86 | $rect = new-object Drawing.Rectangle ($x - $ax * $w), ($y - $ay * $h), $w, $h 87 | $graphics.SetClip($rect) 88 | ]] 89 | 90 | local DIALOG_TEXT = [[ 91 | $font = new-object System.Drawing.Font "%s",%d 92 | $string = '%s' 93 | $size = $graphics.MeasureString($string, $font); 94 | $graphics.DrawString($string, $font, $brush, ($x - $ax * $size.Width), ($y - $ay * $size.Height)); 95 | ]] 96 | 97 | local DIALOG_IMAGE = [[ 98 | $file = (get-item '%s') 99 | $img = [System.Drawing.Image]::Fromfile($file); 100 | $units = [System.Drawing.GraphicsUnit]::Pixel 101 | $dest = new-object Drawing.Rectangle ($x - $ax * $w), ($y - $ay * $h), $w, $h 102 | $src = new-object Drawing.Rectangle %d, %d, %d, %d 103 | $graphics.DrawImage($img, $dest, $src, $units); 104 | ]] 105 | 106 | local DIALOG_ELLIPSE = [[ 107 | $rect = new-object Drawing.Rectangle ($x - $ax * $w), ($y - $ay * $h), $w, $h 108 | $graphics.%sEllipse(%s, $rect); 109 | ]] 110 | 111 | local DIALOG_RECTANGLE = [[ 112 | $rect = new-object Drawing.Rectangle ($x - $ax * $w), ($y - $ay * $h), $w, $h 113 | $graphics.%sRectangle(%s, $rect); 114 | ]] 115 | 116 | local DIALOG_LINE = [[ 117 | %s 118 | $points = %s 119 | $graphics.DrawLines($pen, $points); 120 | ]] 121 | 122 | local DIALOG_CURVE = [[ 123 | %s 124 | $points = %s 125 | $graphics.DrawCurve($pen, $points); 126 | ]] 127 | 128 | local DIALOG_BEZIER = [[ 129 | %s 130 | $points = %s 131 | $graphics.DrawBeziers($pen, $points); 132 | ]] 133 | 134 | local DIALOG_PIE = [[ 135 | $graphics.%sPie(%s, ($x - $ax * $w), ($y - $ay * $h), $w, $h, %d, %d); 136 | ]] 137 | 138 | local DIALOG_ARC = [[ 139 | $graphics.DrawArc($pen, ($x - $ax * $w), ($y - $ay * $h), $w, $h, %d, %d); 140 | ]] 141 | 142 | local DIALOG_POLYGON = [[ 143 | %s 144 | $points = %s 145 | $graphics.%sPolygon(%s, $points); 146 | ]] 147 | 148 | function Graphic:__init__(w, h) 149 | assert(tools.is_windows(), 'platform not supported!') 150 | self._w = w or 500 151 | self._h = h or 500 152 | self._children = {} 153 | self._code = "" 154 | end 155 | 156 | function Graphic:setColor(color) 157 | color = color or "#eeeeee" 158 | self._code = self._code .. string.format(GRAPHIC_COLOR, color, color) 159 | return self 160 | end 161 | 162 | function Graphic:setSize(size) 163 | size = size or 10 164 | self._code = self._code .. string.format(GRAPHIC_SIZE, size) 165 | return self 166 | end 167 | 168 | function Graphic:setXY(x, y) 169 | x = x or 0 170 | y = y or 0 171 | self._code = self._code .. string.format(GRAPHIC_POSITION, x, y) 172 | return self 173 | end 174 | 175 | function Graphic:setWH(w, h) 176 | w = w or 0 177 | h = h or 0 178 | self._code = self._code .. string.format(GRAPHIC_SIZE, w, h) 179 | return self 180 | end 181 | 182 | function Graphic:copyScreen(fromX, fromY) 183 | fromX = fromX or 0 184 | fromY = fromY or 0 185 | self._code = self._code .. string.format(GRAPHIC_SCREEN, fromX, fromY) 186 | return self 187 | end 188 | 189 | function Graphic:setClip() 190 | self._code = self._code .. string.format(GRAPHIC_CLIP) 191 | return self 192 | end 193 | 194 | function Graphic:addText(text, size, font) 195 | text = text or "Text..." 196 | size = size or 13 197 | font = font or "Microsoft Sans Serif" 198 | self._code = self._code .. string.format(DIALOG_TEXT, font, size, text) 199 | return self 200 | end 201 | 202 | function Graphic:addImage(path, fromX, fromY, fromW, fromH) 203 | path = path or "" 204 | fromX = fromX or 0 205 | fromY = fromY or 0 206 | fromW = fromW or 250 207 | fromH = fromH or 250 208 | self._code = self._code .. string.format(DIALOG_IMAGE, path, fromX, fromY, fromW, fromH) 209 | return self 210 | end 211 | 212 | function Graphic:addEllipse(isFill) 213 | local mode, tool = self:_formatMode(isFill ~= false) 214 | self._code = self._code .. string.format(DIALOG_ELLIPSE, mode, tool) 215 | return self 216 | end 217 | 218 | function Graphic:addRectangle(isFill) 219 | local mode, tool = self:_formatMode(isFill ~= false) 220 | self._code = self._code .. string.format(DIALOG_RECTANGLE, mode, tool) 221 | return self 222 | end 223 | 224 | function Graphic:addLine(point1, point2, ...) 225 | local points = {point1, point2, ...} 226 | points = #points > 0 and points or {} 227 | local names, bodies = self:_formatPoints(points) 228 | self._code = self._code .. string.format(DIALOG_LINE, bodies, names) 229 | return self._children[#self._children] 230 | end 231 | 232 | function Graphic:addCurve(point1, point2, ...) 233 | local points = {point1, point2, ...} 234 | points = #points > 0 and points or {} 235 | local names, bodies = self:_formatPoints(points) 236 | self._code = self._code .. string.format(DIALOG_CURVE, bodies, names) 237 | return self._children[#self._children] 238 | end 239 | 240 | function Graphic:addBezier(start, cPointA1, cPointB1, end1, ...) 241 | local points = {start, cPointA1, cPointB1, end1, ...} 242 | points = #points > 0 and points or {} 243 | local names, bodies = self:_formatPoints(points) 244 | self._code = self._code .. string.format(DIALOG_BEZIER, bodies, names) 245 | return self._children[#self._children] 246 | end 247 | 248 | function Graphic:addPie(fromR, toR, isFill) 249 | fromR = fromR or 0 250 | toR = toR or 270 251 | local mode, tool = self:_formatMode(isFill ~= false) 252 | self._code = self._code .. string.format(DIALOG_PIE, mode, tool, fromR, toR) 253 | return self 254 | end 255 | 256 | function Graphic:addArc(fromR, toR) 257 | fromR = fromR or 0 258 | toR = toR or 270 259 | self._code = self._code .. string.format(DIALOG_ARC, fromR, toR) 260 | return self._children[#self._children] 261 | end 262 | 263 | function Graphic:addPolygon(point1, point2, ...) 264 | local points = {point1, point2, ...} 265 | points = #points > 0 and points or {} 266 | local names, bodies = self:_formatPoints(points) 267 | local mode, tool = self:_formatMode(false) 268 | self._code = self._code .. string.format(DIALOG_POLYGON, bodies, names, mode, tool) 269 | 270 | return self._children[#self._children] 271 | end 272 | 273 | function Graphic:_formatMode(isFill) 274 | if isFill then 275 | return "Fill", "$brush" 276 | else 277 | return "Draw", "$pen" 278 | end 279 | end 280 | 281 | function Graphic:_formatPoints(points) 282 | local names, bodies = "", "" 283 | for index,item in ipairs(points) do 284 | names = names .. string.format("$p%d", index) .. (index ~= #points and "," or "") 285 | bodies = bodies .. string.format("$p%s = new-object Drawing.Point %d, %d;", index, item[1], item[2]) 286 | end 287 | return names, bodies 288 | end 289 | 290 | function Graphic:_runScript() 291 | files.write("running.ps1", self._code) 292 | local isOk, r = tools.execute([[ powershell.exe -file ./running.ps1]]) 293 | assert(isOk, 'powershell execute failed:' .. r) 294 | files.delete("running.ps1") 295 | end 296 | 297 | function Graphic:show(title, icon) 298 | title = title or "Title..." 299 | icon = icon or "./others/test.ico" 300 | self._code = COMMON_HEADER .. string.format(FORM_CREATE, self._w, self._h) .. self._code -- HIDE_CONSOLE 301 | self._code = self._code .. string.format(FORM_SHOW, icon, title) 302 | self:_runScript() 303 | end 304 | 305 | function Graphic:save(path) 306 | path = path or "./graphic.png" 307 | self._code = COMMON_HEADER .. string.format(IMAGE_CREATE, self._w, self._h) .. self._code 308 | self._code = self._code .. string.format(IMAGE_SAVE, path) 309 | self:_runScript() 310 | end 311 | -------------------------------------------------------------------------------- /files/http.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | http 3 | ]] 4 | 5 | http = http or {} 6 | 7 | function http.download(url, path, tp) 8 | assert(string.valid(url)) 9 | assert(string.valid(path)) 10 | tp = tp or 'wget' 11 | local folder = files.get_folder(path) 12 | files.mk_folder(folder) 13 | local cmd = nil 14 | local isOk = false 15 | if tp == 'curl' then 16 | cmd = [[curl -L "%s" -o "%s" --max-redirs 3]] 17 | elseif tp == 'wget' then 18 | cmd = [[wget "%s" -O "%s"]] 19 | end 20 | cmd = string.format(cmd, url, path) 21 | local isOk, output = tools.execute(cmd) 22 | return isOk, output, cmd 23 | end 24 | 25 | local function curl_request(url, method, params, headers) 26 | -- 27 | local httpContentFile = "./.lua.http.log" 28 | files.delete(httpContentFile) 29 | -- 30 | local h = "" 31 | for k,v in pairs(headers) do 32 | assert(is_string(k)) 33 | assert(is_string(v) or is_number(v)) 34 | if h ~= "" then 35 | h = h .. ";" 36 | end 37 | h = h .. "-H '" .. tostring(k) .. ":" .. tostring(v) .. "'" 38 | end 39 | -- 40 | local b = "" 41 | if method == "GET" then 42 | for k,v in pairs(params) do 43 | if not string.find(url, "?") then url = url .. "?" end 44 | assert(is_string(k)) 45 | assert(is_string(v) or is_number(v)) 46 | url = url .. tostring(k) .. "=" .. tostring(v) 47 | end 48 | elseif method == "POST" then 49 | b = string.format("-d '%s'", json.encode(params)) 50 | end 51 | -- 52 | local cmd = [[curl "%s" -i --silent -o "%s" -X %s "%s" -d "%s"]] 53 | cmd = string.format(cmd, url, httpContentFile, method, h, b) 54 | local isOk, output = tools.execute(cmd) 55 | local content = files.read(httpContentFile) or "" 56 | files.delete(httpContentFile) 57 | local contents = string.explode(content, "\n%s*\n", 1) 58 | local head = contents[1] or "" 59 | local body = contents[2] or "" 60 | local from, to = string.find(head, 'HTTP.*%s%d%d%d') 61 | local code = (from and to) and tonumber(string.sub(head, to - 3, to) or "") or -1 62 | if not isOk or code < 0 then 63 | return -1, output 64 | else 65 | return code, body 66 | end 67 | end 68 | 69 | local function http_request(url, method, params, headers) 70 | assert(string.valid(url)) 71 | local m = string.upper(method) 72 | assert(m == "POST" or m == "GET") 73 | params = params or {} 74 | headers = headers or {} 75 | local code, content = curl_request(url, method, params, headers) 76 | return code == 200, code, content 77 | end 78 | 79 | function http.get(url, params, headers) 80 | return http_request(url, 'GET', params, headers) 81 | end 82 | 83 | function http.post(url, params, headers) 84 | return http_request(url, 'POST', params, headers) 85 | end 86 | -------------------------------------------------------------------------------- /files/json.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | json 3 | ]] 4 | 5 | json = json or {} 6 | 7 | function json.encodable(o) 8 | local t = type(o) 9 | return (t == 'string' or t == 'boolean' or t == 'number' or t == 'nil' or t == 'table') or (t == 'function' and o == null) 10 | end 11 | 12 | function json._encodeString(s) 13 | s = string.gsub(s, '\\', '\\\\') 14 | s = string.gsub(s, '"', '\\"') 15 | s = string.gsub(s, "'", "\\'") 16 | s = string.gsub(s, '\n', '\\n') 17 | s = string.gsub(s, '\t', '\\t') 18 | return s 19 | end 20 | 21 | function json._encode(v) 22 | if is_nil(v) or v == null then return "null" end 23 | if is_boolean(v) or is_number(v) then return tostring(v) end 24 | if is_string(v) then return '"' .. json._encodeString(v) .. '"' end 25 | local rval = {} 26 | if table.is_array(v) then 27 | for i = 1, #v do 28 | table.insert(rval, json._encode(v[i])) 29 | end 30 | return '[' .. table.concat(rval, ',') .. ']' 31 | end 32 | if is_table(v) then 33 | for i, j in pairs(v) do 34 | if json.encodable(i) and json.encodable(j) then 35 | table.insert(rval, '"' .. json._encodeString(i) .. '":' .. json._encode(j)) 36 | end 37 | end 38 | return '{' .. table.concat(rval, ',') .. '}' 39 | end 40 | assert(false, 'type not supported:' .. type(v)) 41 | end 42 | 43 | function json._decode_scanWhitespace(s, startPos) 44 | local stringLen = string.len(s) 45 | while (string.find(" \n\r\t", string.sub(s, startPos, startPos), 1, true) and startPos <= stringLen) do 46 | startPos = startPos + 1 47 | end 48 | return startPos 49 | end 50 | 51 | function json._decode_scanObject(s, startPos) 52 | local object = {} 53 | local stringLen = string.len(s) 54 | local key, value 55 | startPos = startPos + 1 56 | repeat 57 | startPos = json._decode_scanWhitespace(s, startPos) 58 | assert(startPos <= stringLen, 'JSON string ended unexpectedly while scanning object.') 59 | local curChar = string.sub(s, startPos, startPos) 60 | if (curChar == '}') then return object, startPos + 1 end 61 | if (curChar == ',') then startPos = json._decode_scanWhitespace(s, startPos + 1) end 62 | assert(startPos <= stringLen, 'JSON string ended unexpectedly scanning object.') 63 | -- Scan the key 64 | key, startPos = json._decode(s, startPos) 65 | assert(startPos <= stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) 66 | startPos = json._decode_scanWhitespace(s, startPos) 67 | assert(startPos <= stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) 68 | assert(string.sub(s, startPos, startPos) == ':', 'JSON string ended unexpectedly searching for assignment at ' .. startPos) 69 | startPos = json._decode_scanWhitespace(s, startPos + 1) 70 | assert(startPos <= stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) 71 | value, startPos = json._decode(s, startPos) 72 | object[key] = value 73 | until false 74 | end 75 | 76 | function json._decode_scanArray(s, startPos) 77 | local array = {} 78 | local stringLen = string.len(s) 79 | startPos = startPos + 1 80 | repeat 81 | startPos = json._decode_scanWhitespace(s, startPos) 82 | assert(startPos <= stringLen, 'JSON String ended unexpectedly scanning array.') 83 | local curChar = string.sub(s, startPos, startPos) 84 | if (curChar == ']') then return array, startPos + 1 end 85 | if (curChar == ',') then startPos = json._decode_scanWhitespace(s, startPos + 1) end 86 | assert(startPos <= stringLen, 'JSON String ended unexpectedly scanning array.') 87 | object, startPos = json._decode(s, startPos) 88 | table.insert(array, object) 89 | until false 90 | end 91 | 92 | function json._decode_scanNumber(s, startPos) 93 | local endPos = startPos + 1 94 | local stringLen = string.len(s) 95 | while (string.find("+-0123456789.e", string.sub(s, endPos, endPos), 1, true) and endPos <= stringLen) do 96 | endPos = endPos + 1 97 | end 98 | local stringValue = string.sub(s, startPos, endPos - 1) 99 | local numberValue = tonumber(stringValue) 100 | assert(numberValue ~= nil, 'invalid number [ ' .. stringValue .. '] at:' .. startPos .. ' : ' .. endPos) 101 | return stringEval(), endPos 102 | end 103 | 104 | function json._decode_scanString(s, startPos) 105 | local startChar = string.sub(s, startPos, startPos) 106 | local escaped = false 107 | local endPos = startPos + 1 108 | local bEnded = false 109 | local stringLen = string.len(s) 110 | repeat 111 | local curChar = string.sub(s, endPos, endPos) 112 | if not escaped then 113 | if curChar == [[\]] then 114 | escaped = true 115 | else 116 | bEnded = curChar == startChar 117 | end 118 | else 119 | escaped = false 120 | end 121 | endPos = endPos + 1 122 | assert(endPos <= stringLen + 1, 'JSON string ended unexpectedly scanning string.') 123 | until bEnded 124 | local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1) 125 | local stringEval = loadstring(stringValue) 126 | assert(stringEval, 'invalid string [ ' .. stringValue .. '] at ' .. startPos .. ' : ' .. endPos) 127 | return stringEval(), endPos 128 | end 129 | 130 | function json._decode_scanComment(s, startPos) 131 | local endPos = string.find(s, '*/', startPos + 2) 132 | assert(endPos ~= nil, "invalid comment tag!") 133 | return json._decode(s, endPos + 2) 134 | end 135 | 136 | function json._decode_scanConstants(s, startPos) 137 | local constValues = {true, false, nil} 138 | local constNames = {"true", "false", "null"} 139 | for _, k in pairs(constNames) do 140 | if string.sub(s, startPos, startPos + string.len(k) - 1) == k then 141 | return constValues[k], startPos + string.len(k) 142 | end 143 | end 144 | assert(false, 'invalid json value at:' .. startPos) 145 | end 146 | 147 | function json._decode(s, startPos) 148 | startPos = startPos and startPos or 1 149 | startPos = json._decode_scanWhitespace(s, startPos) 150 | assert(startPos <= string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') 151 | local curChar = string.sub(s, startPos, startPos) 152 | -- 153 | if string.sub(s, startPos, startPos + 1) == '/*' then return json._decode_scanComment(s, startPos) end 154 | if curChar == '{' then return json._decode_scanObject(s, startPos) end 155 | if curChar == '[' then return json._decode_scanArray(s, startPos) end 156 | if curChar == [["]] or curChar == [[']] then return json._decode_scanString(s, startPos) end 157 | if string.find("+-0123456789.e", curChar, 1, true) then return json._decode_scanNumber(s, startPos) end 158 | -- 159 | return json._decode_scanConstants(s, startPos) 160 | end 161 | 162 | function json.encode(v) 163 | local isOk, r = xpcall(function() return json._encode(v) end, function(err) return err end) 164 | if isOk then return r, nil end 165 | return nil, r 166 | end 167 | 168 | function json.decode(s) 169 | local isOk, r = xpcall(function() return json._decode(s) end, function(err) return err end) 170 | if isOk then return r, nil end 171 | return nil, r 172 | end 173 | -------------------------------------------------------------------------------- /files/library.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | library 3 | ]] 4 | 5 | library = library or {} 6 | 7 | library.log30 = log30_wrapper() 8 | library.deflate = deflate_wrapper() 9 | library.Stream = stream_wrapper() 10 | library.qrcode = qrcode_wrapper() 11 | library.pngEncode = png_encode_wrapper() 12 | library.pngDecode = png_decode_wrapper() 13 | -------------------------------------------------------------------------------- /files/libs/deflate.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | deflate https://github.com/DelusionalLogic/pngLua 3 | ]] 4 | 5 | function deflate_wrapper() 6 | --[[ 7 | 8 | LUA MODULE 9 | 10 | compress.deflatelua - deflate (and gunzip/zlib) implemented in Lua. 11 | 12 | SYNOPSIS 13 | local DEFLATE = require 'compress.deflatelua' 14 | -- uncompress gzip file 15 | local fh = assert(io.open'foo.txt.gz', 'rb') 16 | local ofh = assert(io.open'foo.txt', 'wb') 17 | DEFLATE.gunzip {input=fh, output=ofh} 18 | fh:close(); ofh:close() 19 | -- can also uncompress from string including zlib and raw DEFLATE formats. 20 | DESCRIPTION 21 | This is a pure Lua implementation of decompressing the DEFLATE format, 22 | including the related zlib and gzip formats. 23 | Note: This library only supports decompression. 24 | Compression is not currently implemented. 25 | 26 | API 27 | 28 | Note: in the following functions, input stream `fh` may be 29 | a file handle, string, or an iterator function that returns strings. 30 | Output stream `ofh` may be a file handle or a function that 31 | consumes one byte (number 0..255) per call. 32 | 33 | DEFLATE.inflate {input=fh, output=ofh} 34 | 35 | Decompresses input stream `fh` in the DEFLATE format 36 | while writing to output stream `ofh`. 37 | DEFLATE is detailed in http://tools.ietf.org/html/rfc1951 . 38 | DEFLATE.gunzip {input=fh, output=ofh, disable_crc=disable_crc} 39 | Decompresses input stream `fh` with the gzip format 40 | while writing to output stream `ofh`. 41 | `disable_crc` (defaults to `false`) will disable CRC-32 checking 42 | to increase speed. 43 | gzip is detailed in http://tools.ietf.org/html/rfc1952 . 44 | 45 | DEFLATE.inflate_zlib {input=fh, output=ofh, disable_crc=disable_crc} 46 | Decompresses input stream `fh` with the zlib format 47 | while writing to output stream `ofh`. 48 | `disable_crc` (defaults to `false`) will disable CRC-32 checking 49 | to increase speed. 50 | zlib is detailed in http://tools.ietf.org/html/rfc1950 . 51 | 52 | DEFLATE.adler32(byte, crc) --> rcrc 53 | Returns adler32 checksum of byte `byte` (number 0..255) appended 54 | to string with adler32 checksum `crc`. This is internally used by 55 | `inflate_zlib`. 56 | ADLER32 in detailed in http://tools.ietf.org/html/rfc1950 . 57 | 58 | COMMAND LINE UTILITY 59 | 60 | A `gunziplua` command line utility (in folder `bin`) is also provided. 61 | This mimicks the *nix `gunzip` utility but is a pure Lua implementation 62 | that invokes this library. For help do 63 | gunziplua -h 64 | DEPENDENCIES 65 | 66 | Requires 'digest.crc32lua' (used for optional CRC-32 checksum checks). 67 | https://github.com/davidm/lua-digest-crc32lua 68 | 69 | Will use a bit library ('bit', 'bit32', 'bit.numberlua') if available. This 70 | is not that critical for this library but is required by digest.crc32lua. 71 | 72 | 'pythonic.optparse' is only required by the optional `gunziplua` 73 | command-line utilty for command line parsing. 74 | https://github.com/davidm/lua-pythonic-optparse 75 | 76 | INSTALLATION 77 | 78 | Copy the `compress` directory into your LUA_PATH. 79 | REFERENCES 80 | 81 | [1] DEFLATE Compressed Data Format Specification version 1.3 82 | http://tools.ietf.org/html/rfc1951 83 | [2] GZIP file format specification version 4.3 84 | http://tools.ietf.org/html/rfc1952 85 | [3] http://en.wikipedia.org/wiki/DEFLATE 86 | [4] pyflate, by Paul Sladen 87 | http://www.paul.sladen.org/projects/pyflate/ 88 | [5] Compress::Zlib::Perl - partial pure Perl implementation of 89 | Compress::Zlib 90 | http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm 91 | 92 | LICENSE 93 | 94 | (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). 95 | 96 | Permission is hereby granted, free of charge, to any person obtaining a copy 97 | of this software and associated documentation files (the "Software"), to deal 98 | in the Software without restriction, including without limitation the rights 99 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 100 | copies of the Software, and to permit persons to whom the Software is 101 | furnished to do so, subject to the following conditions: 102 | 103 | The above copyright notice and this permission notice shall be included in 104 | all copies or substantial portions of the Software. 105 | 106 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 107 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 108 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 109 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 110 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 111 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 112 | THE SOFTWARE. 113 | (end license) 114 | --]] 115 | 116 | local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'} 117 | 118 | local assert = assert 119 | local error = error 120 | local ipairs = ipairs 121 | local pairs = pairs 122 | local print = print 123 | local require = require 124 | local tostring = tostring 125 | local type = type 126 | local setmetatable = setmetatable 127 | local io = io 128 | local math = math 129 | local table_sort = table.sort 130 | local math_max = math.max 131 | local string_char = string.char 132 | 133 | --[[ 134 | Requires the first module listed that exists, else raises like `require`. 135 | If a non-string is encountered, it is returned. 136 | Second return value is module name loaded (or ''). 137 | --]] 138 | local function requireany(...) 139 | local errs = {} 140 | for i = 1, select('#', ...) do local name = select(i, ...) 141 | if type(name) ~= 'string' then return name, '' end 142 | local ok, mod = pcall(require, name) 143 | if ok then return mod, name end 144 | errs[#errs+1] = mod 145 | end 146 | error(table.concat(errs, '\n'), 2) 147 | end 148 | 149 | 150 | --local crc32 = require "digest.crc32lua" . crc32_byte 151 | local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', nil) 152 | 153 | local DEBUG = false 154 | 155 | -- Whether to use `bit` library functions in current module. 156 | -- Unlike the crc32 library, it doesn't make much difference in this module. 157 | local NATIVE_BITOPS = (bit ~= nil) 158 | 159 | local band, lshift, rshift 160 | if NATIVE_BITOPS then 161 | band = bit.band 162 | lshift = bit.lshift 163 | rshift = bit.rshift 164 | end 165 | 166 | 167 | local function warn(s) 168 | io.stderr:write(s, '\n') 169 | end 170 | 171 | 172 | local function debug(...) 173 | print('DEBUG', ...) 174 | end 175 | 176 | 177 | local function runtime_error(s, level) 178 | level = level or 1 179 | error({s}, level+1) 180 | end 181 | 182 | 183 | local function make_outstate(outbs) 184 | local outstate = {} 185 | outstate.outbs = outbs 186 | outstate.window = {} 187 | outstate.window_pos = 1 188 | return outstate 189 | end 190 | 191 | 192 | local function output(outstate, byte) 193 | -- debug('OUTPUT:', s) 194 | local window_pos = outstate.window_pos 195 | outstate.outbs(byte) 196 | outstate.window[window_pos] = byte 197 | outstate.window_pos = window_pos % 32768 + 1 -- 32K 198 | end 199 | 200 | 201 | local function noeof(val) 202 | return assert(val, 'unexpected end of file') 203 | end 204 | 205 | 206 | local function hasbit(bits, bit) 207 | return bits % (bit + bit) >= bit 208 | end 209 | 210 | 211 | local function memoize(f) 212 | local mt = {} 213 | local t = setmetatable({}, mt) 214 | function mt:__index(k) 215 | local v = f(k) 216 | t[k] = v 217 | return v 218 | end 219 | return t 220 | end 221 | 222 | 223 | -- small optimization (lookup table for powers of 2) 224 | local pow2 = memoize(function(n) return 2^n end) 225 | 226 | --local tbits = memoize( 227 | -- function(bits) 228 | -- return memoize( function(bit) return getbit(bits, bit) end ) 229 | -- end ) 230 | 231 | 232 | -- weak metatable marking objects as bitstream type 233 | local is_bitstream = setmetatable({}, {__mode='k'}) 234 | 235 | 236 | -- DEBUG 237 | -- prints LSB first 238 | --[[ 239 | local function bits_tostring(bits, nbits) 240 | local s = '' 241 | local tmp = bits 242 | local function f() 243 | local b = tmp % 2 == 1 and 1 or 0 244 | s = s .. b 245 | tmp = (tmp - b) / 2 246 | end 247 | if nbits then 248 | for i=1,nbits do f() end 249 | else 250 | while tmp ~= 0 do f() end 251 | end 252 | 253 | return s 254 | end 255 | --]] 256 | 257 | local function bytestream_from_file(fh) 258 | local o = {} 259 | function o:read() 260 | local sb = fh:read(1) 261 | if sb then return sb:byte() end 262 | end 263 | return o 264 | end 265 | 266 | 267 | local function bytestream_from_string(s) 268 | local i = 1 269 | local o = {} 270 | function o:read() 271 | local by 272 | if i <= #s then 273 | by = s:byte(i) 274 | i = i + 1 275 | end 276 | return by 277 | end 278 | return o 279 | end 280 | 281 | 282 | local function bytestream_from_function(f) 283 | local i = 0 284 | local buffer = '' 285 | local o = {} 286 | function o:read() 287 | i = i + 1 288 | if i > #buffer then 289 | buffer = f() 290 | if not buffer then return end 291 | i = 1 292 | end 293 | return buffer:byte(i,i) 294 | end 295 | return o 296 | end 297 | 298 | 299 | local function bitstream_from_bytestream(bys) 300 | local buf_byte = 0 301 | local buf_nbit = 0 302 | local o = {} 303 | 304 | function o:nbits_left_in_byte() 305 | return buf_nbit 306 | end 307 | 308 | if NATIVE_BITOPS then 309 | function o:read(nbits) 310 | nbits = nbits or 1 311 | while buf_nbit < nbits do 312 | local byte = bys:read() 313 | if not byte then return end -- note: more calls also return nil 314 | buf_byte = buf_byte + lshift(byte, buf_nbit) 315 | buf_nbit = buf_nbit + 8 316 | end 317 | local bits 318 | if nbits == 0 then 319 | bits = 0 320 | elseif nbits == 32 then 321 | bits = buf_byte 322 | buf_byte = 0 323 | else 324 | bits = band(buf_byte, rshift(0xffffffff, 32 - nbits)) 325 | buf_byte = rshift(buf_byte, nbits) 326 | end 327 | buf_nbit = buf_nbit - nbits 328 | return bits 329 | end 330 | else 331 | function o:read(nbits) 332 | nbits = nbits or 1 333 | while buf_nbit < nbits do 334 | local byte = bys:read() 335 | if not byte then return end -- note: more calls also return nil 336 | buf_byte = buf_byte + pow2[buf_nbit] * byte 337 | buf_nbit = buf_nbit + 8 338 | end 339 | local m = pow2[nbits] 340 | local bits = buf_byte % m 341 | buf_byte = (buf_byte - bits) / m 342 | buf_nbit = buf_nbit - nbits 343 | return bits 344 | end 345 | end 346 | 347 | is_bitstream[o] = true 348 | 349 | return o 350 | end 351 | 352 | 353 | local function get_bitstream(o) 354 | local bs 355 | if is_bitstream[o] then 356 | return o 357 | elseif io.type(o) == 'file' then 358 | bs = bitstream_from_bytestream(bytestream_from_file(o)) 359 | elseif type(o) == 'string' then 360 | bs = bitstream_from_bytestream(bytestream_from_string(o)) 361 | elseif type(o) == 'function' then 362 | bs = bitstream_from_bytestream(bytestream_from_function(o)) 363 | else 364 | runtime_error 'unrecognized type' 365 | end 366 | return bs 367 | end 368 | 369 | 370 | local function get_obytestream(o) 371 | local bs 372 | if io.type(o) == 'file' then 373 | bs = function(sbyte) o:write(string_char(sbyte)) end 374 | elseif type(o) == 'function' then 375 | bs = o 376 | else 377 | runtime_error('unrecognized type: ' .. tostring(o)) 378 | end 379 | return bs 380 | end 381 | 382 | 383 | local function HuffmanTable(init, is_full) 384 | local t = {} 385 | if is_full then 386 | for val,nbits in pairs(init) do 387 | if nbits ~= 0 then 388 | t[#t+1] = {val=val, nbits=nbits} 389 | --debug('*',val,nbits) 390 | end 391 | end 392 | else 393 | for i=1,#init-2,2 do 394 | local firstval, nbits, nextval = init[i], init[i+1], init[i+2] 395 | --debug(val, nextval, nbits) 396 | if nbits ~= 0 then 397 | for val=firstval,nextval-1 do 398 | t[#t+1] = {val=val, nbits=nbits} 399 | end 400 | end 401 | end 402 | end 403 | table_sort(t, function(a,b) 404 | return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits 405 | end) 406 | 407 | -- assign codes 408 | local code = 1 -- leading 1 marker 409 | local nbits = 0 410 | for i,s in ipairs(t) do 411 | if s.nbits ~= nbits then 412 | code = code * pow2[s.nbits - nbits] 413 | nbits = s.nbits 414 | end 415 | s.code = code 416 | --debug('huffman code:', i, s.nbits, s.val, code, bits_tostring(code)) 417 | code = code + 1 418 | end 419 | 420 | local minbits = math.huge 421 | local look = {} 422 | for i,s in ipairs(t) do 423 | minbits = math.min(minbits, s.nbits) 424 | look[s.code] = s.val 425 | end 426 | 427 | --for _,o in ipairs(t) do 428 | -- debug(':', o.nbits, o.val) 429 | --end 430 | 431 | -- function t:lookup(bits) return look[bits] end 432 | 433 | local msb = NATIVE_BITOPS and function(bits, nbits) 434 | local res = 0 435 | for i=1,nbits do 436 | res = lshift(res, 1) + band(bits, 1) 437 | bits = rshift(bits, 1) 438 | end 439 | return res 440 | end or function(bits, nbits) 441 | local res = 0 442 | for i=1,nbits do 443 | local b = bits % 2 444 | bits = (bits - b) / 2 445 | res = res * 2 + b 446 | end 447 | return res 448 | end 449 | 450 | local tfirstcode = memoize( 451 | function(bits) return pow2[minbits] + msb(bits, minbits) end) 452 | 453 | function t:read(bs) 454 | local code = 1 -- leading 1 marker 455 | local nbits = 0 456 | while 1 do 457 | if nbits == 0 then -- small optimization (optional) 458 | code = tfirstcode[noeof(bs:read(minbits))] 459 | nbits = nbits + minbits 460 | else 461 | local b = noeof(bs:read()) 462 | nbits = nbits + 1 463 | code = code * 2 + b -- MSB first 464 | --[[NATIVE_BITOPS 465 | code = lshift(code, 1) + b -- MSB first 466 | --]] 467 | end 468 | --debug('code?', code, bits_tostring(code)) 469 | local val = look[code] 470 | if val then 471 | --debug('FOUND', val) 472 | return val 473 | end 474 | end 475 | end 476 | 477 | return t 478 | end 479 | 480 | 481 | local function parse_gzip_header(bs) 482 | -- local FLG_FTEXT = 2^0 483 | local FLG_FHCRC = 2^1 484 | local FLG_FEXTRA = 2^2 485 | local FLG_FNAME = 2^3 486 | local FLG_FCOMMENT = 2^4 487 | 488 | local id1 = bs:read(8) 489 | local id2 = bs:read(8) 490 | if id1 ~= 31 or id2 ~= 139 then 491 | runtime_error 'not in gzip format' 492 | end 493 | local cm = bs:read(8) -- compression method 494 | local flg = bs:read(8) -- FLaGs 495 | local mtime = bs:read(32) -- Modification TIME 496 | local xfl = bs:read(8) -- eXtra FLags 497 | local os = bs:read(8) -- Operating System 498 | 499 | if DEBUG then 500 | debug("CM=", cm) 501 | debug("FLG=", flg) 502 | debug("MTIME=", mtime) 503 | -- debug("MTIME_str=",os.date("%Y-%m-%d %H:%M:%S",mtime)) -- non-portable 504 | debug("XFL=", xfl) 505 | debug("OS=", os) 506 | end 507 | 508 | if not os then runtime_error 'invalid header' end 509 | 510 | if hasbit(flg, FLG_FEXTRA) then 511 | local xlen = bs:read(16) 512 | local extra = 0 513 | for i=1,xlen do 514 | extra = bs:read(8) 515 | end 516 | if not extra then runtime_error 'invalid header' end 517 | end 518 | 519 | local function parse_zstring(bs) 520 | repeat 521 | local by = bs:read(8) 522 | if not by then runtime_error 'invalid header' end 523 | until by == 0 524 | end 525 | 526 | if hasbit(flg, FLG_FNAME) then 527 | parse_zstring(bs) 528 | end 529 | 530 | if hasbit(flg, FLG_FCOMMENT) then 531 | parse_zstring(bs) 532 | end 533 | 534 | if hasbit(flg, FLG_FHCRC) then 535 | local crc16 = bs:read(16) 536 | if not crc16 then runtime_error 'invalid header' end 537 | -- IMPROVE: check CRC. where is an example .gz file that 538 | -- has this set? 539 | if DEBUG then 540 | debug("CRC16=", crc16) 541 | end 542 | end 543 | end 544 | 545 | local function parse_zlib_header(bs) 546 | local cm = bs:read(4) -- Compression Method 547 | local cinfo = bs:read(4) -- Compression info 548 | local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG) 549 | local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary) 550 | local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level) 551 | local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags) 552 | local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs 553 | 554 | if cm ~= 8 then -- not "deflate" 555 | runtime_error("unrecognized zlib compression method: " .. cm) 556 | end 557 | if cinfo > 7 then 558 | runtime_error("invalid zlib window size: cinfo=" .. cinfo) 559 | end 560 | local window_size = 2^(cinfo + 8) 561 | 562 | if (cmf*256 + flg) % 31 ~= 0 then 563 | runtime_error("invalid zlib header (bad fcheck sum)") 564 | end 565 | 566 | if fdict == 1 then 567 | runtime_error("FIX:TODO - FDICT not currently implemented") 568 | local dictid_ = bs:read(32) 569 | end 570 | 571 | return window_size 572 | end 573 | 574 | local function parse_huffmantables(bs) 575 | local hlit = bs:read(5) -- # of literal/length codes - 257 576 | local hdist = bs:read(5) -- # of distance codes - 1 577 | local hclen = noeof(bs:read(4)) -- # of code length codes - 4 578 | 579 | local ncodelen_codes = hclen + 4 580 | local codelen_init = {} 581 | local codelen_vals = { 582 | 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} 583 | for i=1,ncodelen_codes do 584 | local nbits = bs:read(3) 585 | local val = codelen_vals[i] 586 | codelen_init[val] = nbits 587 | end 588 | local codelentable = HuffmanTable(codelen_init, true) 589 | 590 | local function decode(ncodes) 591 | local init = {} 592 | local nbits 593 | local val = 0 594 | while val < ncodes do 595 | local codelen = codelentable:read(bs) 596 | --FIX:check nil? 597 | local nrepeat 598 | if codelen <= 15 then 599 | nrepeat = 1 600 | nbits = codelen 601 | --debug('w', nbits) 602 | elseif codelen == 16 then 603 | nrepeat = 3 + noeof(bs:read(2)) 604 | -- nbits unchanged 605 | elseif codelen == 17 then 606 | nrepeat = 3 + noeof(bs:read(3)) 607 | nbits = 0 608 | elseif codelen == 18 then 609 | nrepeat = 11 + noeof(bs:read(7)) 610 | nbits = 0 611 | else 612 | error 'ASSERT' 613 | end 614 | for i=1,nrepeat do 615 | init[val] = nbits 616 | val = val + 1 617 | end 618 | end 619 | local huffmantable = HuffmanTable(init, true) 620 | return huffmantable 621 | end 622 | 623 | local nlit_codes = hlit + 257 624 | local ndist_codes = hdist + 1 625 | 626 | local littable = decode(nlit_codes) 627 | local disttable = decode(ndist_codes) 628 | 629 | return littable, disttable 630 | end 631 | 632 | 633 | local tdecode_len_base 634 | local tdecode_len_nextrabits 635 | local tdecode_dist_base 636 | local tdecode_dist_nextrabits 637 | local function parse_compressed_item(bs, outstate, littable, disttable) 638 | local val = littable:read(bs) 639 | --debug(val, val < 256 and string_char(val)) 640 | if val < 256 then -- literal 641 | output(outstate, val) 642 | elseif val == 256 then -- end of block 643 | return true 644 | else 645 | if not tdecode_len_base then 646 | local t = {[257]=3} 647 | local skip = 1 648 | for i=258,285,4 do 649 | for j=i,i+3 do t[j] = t[j-1] + skip end 650 | if i ~= 258 then skip = skip * 2 end 651 | end 652 | t[285] = 258 653 | tdecode_len_base = t 654 | --for i=257,285 do debug('T1',i,t[i]) end 655 | end 656 | if not tdecode_len_nextrabits then 657 | local t = {} 658 | if NATIVE_BITOPS then 659 | for i=257,285 do 660 | local j = math_max(i - 261, 0) 661 | t[i] = rshift(j, 2) 662 | end 663 | else 664 | for i=257,285 do 665 | local j = math_max(i - 261, 0) 666 | t[i] = (j - (j % 4)) / 4 667 | end 668 | end 669 | t[285] = 0 670 | tdecode_len_nextrabits = t 671 | --for i=257,285 do debug('T2',i,t[i]) end 672 | end 673 | local len_base = tdecode_len_base[val] 674 | local nextrabits = tdecode_len_nextrabits[val] 675 | local extrabits = bs:read(nextrabits) 676 | local len = len_base + extrabits 677 | 678 | if not tdecode_dist_base then 679 | local t = {[0]=1} 680 | local skip = 1 681 | for i=1,29,2 do 682 | for j=i,i+1 do t[j] = t[j-1] + skip end 683 | if i ~= 1 then skip = skip * 2 end 684 | end 685 | tdecode_dist_base = t 686 | --for i=0,29 do debug('T3',i,t[i]) end 687 | end 688 | if not tdecode_dist_nextrabits then 689 | local t = {} 690 | if NATIVE_BITOPS then 691 | for i=0,29 do 692 | local j = math_max(i - 2, 0) 693 | t[i] = rshift(j, 1) 694 | end 695 | else 696 | for i=0,29 do 697 | local j = math_max(i - 2, 0) 698 | t[i] = (j - (j % 2)) / 2 699 | end 700 | end 701 | tdecode_dist_nextrabits = t 702 | --for i=0,29 do debug('T4',i,t[i]) end 703 | end 704 | local dist_val = disttable:read(bs) 705 | local dist_base = tdecode_dist_base[dist_val] 706 | local dist_nextrabits = tdecode_dist_nextrabits[dist_val] 707 | local dist_extrabits = bs:read(dist_nextrabits) 708 | local dist = dist_base + dist_extrabits 709 | 710 | --debug('BACK', len, dist) 711 | for i=1,len do 712 | local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K 713 | output(outstate, assert(outstate.window[pos], 'invalid distance')) 714 | end 715 | end 716 | return false 717 | end 718 | 719 | 720 | local function parse_block(bs, outstate) 721 | local bfinal = bs:read(1) 722 | local btype = bs:read(2) 723 | 724 | local BTYPE_NO_COMPRESSION = 0 725 | local BTYPE_FIXED_HUFFMAN = 1 726 | local BTYPE_DYNAMIC_HUFFMAN = 2 727 | local BTYPE_RESERVED_ = 3 728 | 729 | if DEBUG then 730 | debug('bfinal=', bfinal) 731 | debug('btype=', btype) 732 | end 733 | 734 | if btype == BTYPE_NO_COMPRESSION then 735 | bs:read(bs:nbits_left_in_byte()) 736 | local len = bs:read(16) 737 | local nlen_ = noeof(bs:read(16)) 738 | 739 | for i=1,len do 740 | local by = noeof(bs:read(8)) 741 | output(outstate, by) 742 | end 743 | elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then 744 | local littable, disttable 745 | if btype == BTYPE_DYNAMIC_HUFFMAN then 746 | littable, disttable = parse_huffmantables(bs) 747 | else 748 | littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil} 749 | disttable = HuffmanTable {0,5, 32,nil} 750 | end 751 | 752 | repeat 753 | local is_done = parse_compressed_item( 754 | bs, outstate, littable, disttable) 755 | until is_done 756 | else 757 | runtime_error 'unrecognized compression type' 758 | end 759 | 760 | return bfinal ~= 0 761 | end 762 | 763 | 764 | function M.inflate(t) 765 | local bs = get_bitstream(t.input) 766 | local outbs = get_obytestream(t.output) 767 | local outstate = make_outstate(outbs) 768 | 769 | repeat 770 | local is_final = parse_block(bs, outstate) 771 | until is_final 772 | end 773 | local inflate = M.inflate 774 | 775 | 776 | function M.gunzip(t) 777 | local bs = get_bitstream(t.input) 778 | local outbs = get_obytestream(t.output) 779 | local disable_crc = t.disable_crc 780 | if disable_crc == nil then disable_crc = false end 781 | 782 | parse_gzip_header(bs) 783 | 784 | local data_crc32 = 0 785 | 786 | inflate{input=bs, output= 787 | disable_crc and outbs or 788 | function(byte) 789 | data_crc32 = crc32(byte, data_crc32) 790 | outbs(byte) 791 | end 792 | } 793 | 794 | bs:read(bs:nbits_left_in_byte()) 795 | 796 | local expected_crc32 = bs:read(32) 797 | local isize = bs:read(32) -- ignored 798 | if DEBUG then 799 | debug('crc32=', expected_crc32) 800 | debug('isize=', isize) 801 | end 802 | if not disable_crc and data_crc32 then 803 | if data_crc32 ~= expected_crc32 then 804 | runtime_error('invalid compressed data--crc error') 805 | end 806 | end 807 | if bs:read() then 808 | warn 'trailing garbage ignored' 809 | end 810 | end 811 | 812 | 813 | function M.adler32(byte, crc) 814 | local s1 = crc % 65536 815 | local s2 = (crc - s1) / 65536 816 | s1 = (s1 + byte) % 65521 817 | s2 = (s2 + s1) % 65521 818 | return s2*65536 + s1 819 | end -- 65521 is the largest prime smaller than 2^16 820 | 821 | 822 | function M.inflate_zlib(t) 823 | local bs = get_bitstream(t.input) 824 | local outbs = get_obytestream(t.output) 825 | local disable_crc = t.disable_crc 826 | if disable_crc == nil then disable_crc = false end 827 | 828 | local window_size_ = parse_zlib_header(bs) 829 | 830 | local data_adler32 = 1 831 | 832 | inflate{input=bs, output= 833 | disable_crc and outbs or 834 | function(byte) 835 | data_adler32 = M.adler32(byte, data_adler32) 836 | outbs(byte) 837 | end 838 | } 839 | 840 | bs:read(bs:nbits_left_in_byte()) 841 | 842 | local b3 = bs:read(8) 843 | local b2 = bs:read(8) 844 | local b1 = bs:read(8) 845 | local b0 = bs:read(8) 846 | local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0 847 | if DEBUG then 848 | debug('alder32=', expected_adler32) 849 | end 850 | if not disable_crc then 851 | if data_adler32 ~= expected_adler32 then 852 | runtime_error('invalid compressed data--crc error') 853 | end 854 | end 855 | if bs:read() then 856 | warn 'trailing garbage ignored' 857 | end 858 | end 859 | 860 | --------------------------------------------end------------------------------------------- 861 | return M 862 | end 863 | -------------------------------------------------------------------------------- /files/libs/log30.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 30log https://github.com/DelusionalLogic/pngLua 3 | ]] 4 | 5 | function log30_wrapper() 6 | --------------------------------------------begin------------------------------------------- 7 | 8 | local class 9 | local assert, pairs, type, tostring, baseMt, _instances, _classes, class = assert, pairs, type, tostring, {}, {}, {} 10 | local function deep_copy(t, dest, aType) 11 | local t, r = t or {}, dest or {} 12 | for k,v in pairs(t) do 13 | if aType and type(v)==aType then r[k] = v elseif not aType then 14 | if type(v) == 'table' and k ~= "__index" then r[k] = deep_copy(v) else r[k] = v end 15 | end 16 | end; return r 17 | end 18 | local function instantiate(self,...) 19 | local instance = deep_copy(self) ; _instances[instance] = tostring(instance); setmetatable(instance,self) 20 | if self.__init then 21 | if type(self.__init) == 'table' then deep_copy(self.__init, instance) else self.__init(instance, ...) end 22 | end 23 | return instance 24 | end 25 | local function extends(self,extra_params) 26 | local heirClass = deep_copy(self, class(extra_params)); heirClass.__index, heirClass.super = heirClass, self 27 | return setmetatable(heirClass,self) 28 | end 29 | local baseMt = { __call = function (self,...) return self:new(...) end, 30 | __tostring = function(self,...) 31 | if _instances[self] then return ('object (of %s): <%s>'):format((rawget(getmetatable(self),'__name') or 'Unnamed'), _instances[self]) end 32 | return _classes[self] and ('class (%s): <%s>'):format((rawget(self,'__name') or 'Unnamed'),_classes[self]) or self 33 | end} 34 | class = function(attr) 35 | local c = deep_copy(attr) ; _classes[c] = tostring(c); 36 | c.with = function(self,include) assert(_classes[self], 'Mixins can only be used on classes') return deep_copy(include, self, 'function') end 37 | c.new, c.extends, c.__index, c.__call, c.__tostring = instantiate, extends, c, baseMt.__call, baseMt.__tostring; return setmetatable(c,baseMt) 38 | end; 39 | 40 | --------------------------------------------end------------------------------------------- 41 | return class 42 | end -------------------------------------------------------------------------------- /files/libs/png_decode.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | png decode https://github.com/DelusionalLogic/pngLua 3 | ]] 4 | 5 | function png_decode_wrapper() 6 | local deflate = library and library.deflate or lib_deflate_wrapper() 7 | local class = library and library.log30 or log30_wrapper() 8 | local Stream = library and library.stream or stream_wrapper() 9 | --------------------------------------------begin------------------------------------------- 10 | 11 | local Chunk = class() 12 | Chunk.__name = "Chunk" 13 | Chunk.length = 0 14 | Chunk.name = "" 15 | Chunk.data = "" 16 | Chunk.crc = "" 17 | 18 | function Chunk:__init(stream) 19 | if stream.__name == "Chunk" then 20 | self.length = stream.length 21 | self.name = stream.name 22 | self.data = stream.data 23 | self.crc = stream.crc 24 | else 25 | self.length = stream:readInt() 26 | self.name = stream:readChars(4) 27 | self.data = stream:readChars(self.length) 28 | self.crc = stream:readChars(4) 29 | end 30 | end 31 | 32 | function Chunk:getDataStream() 33 | return Stream({input = self.data}) 34 | end 35 | 36 | local IHDR = Chunk:extends() 37 | IHDR.__name = "IHDR" 38 | IHDR.width = 0 39 | IHDR.height = 0 40 | IHDR.bitDepth = 0 41 | IHDR.colorType = 0 42 | IHDR.compression = 0 43 | IHDR.filter = 0 44 | IHDR.interlace = 0 45 | 46 | function IHDR:__init(chunk) 47 | self.super.__init(self, chunk) 48 | local stream = chunk:getDataStream() 49 | self.width = stream:readInt() 50 | self.height = stream:readInt() 51 | self.bitDepth = stream:readByte() 52 | self.colorType = stream:readByte() 53 | self.compression = stream:readByte() 54 | self.filter = stream:readByte() 55 | self.interlace = stream:readByte() 56 | end 57 | 58 | local IDAT = Chunk:extends() 59 | IDAT.__name = "IDAT" 60 | 61 | function IDAT:__init(chunk) 62 | self.super.__init(self, chunk) 63 | end 64 | 65 | local PLTE = Chunk:extends() 66 | PLTE.__name = "PLTE" 67 | PLTE.numColors = 0 68 | PLTE.colors = {} 69 | 70 | function PLTE:__init(chunk) 71 | self.super.__init(self, chunk) 72 | self.numColors = math.floor(chunk.length/3) 73 | local stream = chunk:getDataStream() 74 | for i = 1, self.numColors do 75 | self.colors[i] = { 76 | R = stream:readByte(), 77 | G = stream:readByte(), 78 | B = stream:readByte(), 79 | } 80 | end 81 | end 82 | 83 | function PLTE:getColor(index) 84 | return self.colors[index] 85 | end 86 | 87 | local Pixel = class() 88 | Pixel.__name = "Pixel" 89 | Pixel.R = 0 90 | Pixel.G = 0 91 | Pixel.B = 0 92 | Pixel.A = 0 93 | 94 | function Pixel:__init(stream, depth, colorType, palette) 95 | local bps = math.floor(depth/8) 96 | if colorType == 0 then 97 | local grey = stream:readInt(bps) 98 | self.R = grey 99 | self.G = grey 100 | self.B = grey 101 | self.A = 255 102 | end 103 | if colorType == 2 then 104 | self.R = stream:readInt(bps) 105 | self.G = stream:readInt(bps) 106 | self.B = stream:readInt(bps) 107 | self.A = 255 108 | end 109 | if colorType == 3 then 110 | local index = stream:readInt(bps)+1 111 | local color = palette:getColor(index) 112 | self.R = color.R 113 | self.G = color.G 114 | self.B = color.B 115 | self.A = 255 116 | end 117 | if colorType == 4 then 118 | local grey = stream:readInt(bps) 119 | self.R = grey 120 | self.G = grey 121 | self.B = grey 122 | self.A = stream:readInt(bps) 123 | end 124 | if colorType == 6 then 125 | self.R = stream:readInt(bps) 126 | self.G = stream:readInt(bps) 127 | self.B = stream:readInt(bps) 128 | self.A = stream:readInt(bps) 129 | end 130 | end 131 | 132 | function Pixel:format() 133 | return string.format("R: %d, G: %d, B: %d, A: %d", self.R, self.G, self.B, self.A) 134 | end 135 | 136 | local ScanLine = class() 137 | ScanLine.__name = "ScanLine" 138 | ScanLine.pixels = {} 139 | ScanLine.filterType = 0 140 | 141 | function ScanLine:__init(stream, depth, colorType, palette, length) 142 | bpp = math.floor(depth/8) * self:bitFromColorType(colorType) 143 | bpl = bpp*length 144 | self.filterType = stream:readByte() 145 | stream:seek(-1) 146 | stream:writeByte(0) 147 | local startLoc = stream.position 148 | if self.filterType == 0 then 149 | for i = 1, length do 150 | self.pixels[i] = Pixel(stream, depth, colorType, palette) 151 | end 152 | end 153 | if self.filterType == 1 then 154 | for i = 1, length do 155 | for j = 1, bpp do 156 | local curByte = stream:readByte() 157 | stream:seek(-(bpp+1)) 158 | local lastByte = 0 159 | if stream.position >= startLoc then lastByte = stream:readByte() or 0 else stream:readByte() end 160 | stream:seek(bpp-1) 161 | stream:writeByte((curByte + lastByte) % 256) 162 | end 163 | stream:seek(-bpp) 164 | self.pixels[i] = Pixel(stream, depth, colorType, palette) 165 | end 166 | end 167 | if self.filterType == 2 then 168 | for i = 1, length do 169 | for j = 1, bpp do 170 | local curByte = stream:readByte() 171 | stream:seek(-(bpl+2)) 172 | local lastByte = stream:readByte() or 0 173 | stream:seek(bpl) 174 | stream:writeByte((curByte + lastByte) % 256) 175 | end 176 | stream:seek(-bpp) 177 | self.pixels[i] = Pixel(stream, depth, colorType, palette) 178 | end 179 | end 180 | if self.filterType == 3 then 181 | for i = 1, length do 182 | for j = 1, bpp do 183 | local curByte = stream:readByte() 184 | stream:seek(-(bpp+1)) 185 | local lastByte = 0 186 | if stream.position >= startLoc then lastByte = stream:readByte() or 0 else stream:readByte() end 187 | stream:seek(-(bpl)+bpp-2) 188 | local priByte = stream:readByte() or 0 189 | stream:seek(bpl) 190 | stream:writeByte((curByte + math.floor((lastByte+priByte)/2)) % 256) 191 | end 192 | stream:seek(-bpp) 193 | self.pixels[i] = Pixel(stream, depth, colorType, palette) 194 | end 195 | end 196 | if self.filterType == 4 then 197 | for i = 1, length do 198 | for j = 1, bpp do 199 | local curByte = stream:readByte() 200 | stream:seek(-(bpp+1)) 201 | local lastByte = 0 202 | if stream.position >= startLoc then lastByte = stream:readByte() or 0 else stream:readByte() end 203 | stream:seek(-(bpl + 2 - bpp)) 204 | local priByte = stream:readByte() or 0 205 | stream:seek(-(bpp+1)) 206 | local lastPriByte = 0 207 | if stream.position >= startLoc - (length * bpp + 1) then lastPriByte = stream:readByte() or 0 else stream:readByte() end 208 | stream:seek(bpl + bpp) 209 | stream:writeByte((curByte + self:_PaethPredict(lastByte, priByte, lastPriByte)) % 256) 210 | end 211 | stream:seek(-bpp) 212 | self.pixels[i] = Pixel(stream, depth, colorType, palette) 213 | end 214 | end 215 | end 216 | 217 | function ScanLine:bitFromColorType(colorType) 218 | if colorType == 0 then return 1 end 219 | if colorType == 2 then return 3 end 220 | if colorType == 3 then return 1 end 221 | if colorType == 4 then return 2 end 222 | if colorType == 6 then return 4 end 223 | error 'Invalid colortype' 224 | end 225 | 226 | function ScanLine:getPixel(pixel) 227 | return self.pixels[pixel] 228 | end 229 | 230 | --Stolen right from w3. 231 | function ScanLine:_PaethPredict(a, b, c) 232 | local p = a + b - c 233 | local varA = math.abs(p - a) 234 | local varB = math.abs(p - b) 235 | local varC = math.abs(p - c) 236 | if varA <= varB and varA <= varC then return a end 237 | if varB <= varC then return b end 238 | return c 239 | end 240 | 241 | local pngImage = class() 242 | pngImage.__name = "PNG" 243 | pngImage.width = 0 244 | pngImage.height = 0 245 | pngImage.depth = 0 246 | pngImage.colorType = 0 247 | pngImage.scanLines = {} 248 | 249 | function pngImage:__init(data, progCallback) 250 | local str = Stream({input = data}) 251 | if str:readChars(8) ~= "\137\080\078\071\013\010\026\010" then error 'Not a PNG' end 252 | local ihdr = {} 253 | local plte = {} 254 | local idat = {} 255 | local num = 1 256 | while true do 257 | ch = Chunk(str) 258 | if ch.name == "IHDR" then ihdr = IHDR(ch) end 259 | if ch.name == "PLTE" then plte = PLTE(ch) end 260 | if ch.name == "IDAT" then idat[num] = IDAT(ch) num = num+1 end 261 | if ch.name == "IEND" then break end 262 | end 263 | self.width = ihdr.width 264 | self.height = ihdr.height 265 | self.depth = ihdr.bitDepth 266 | self.colorType = ihdr.colorType 267 | 268 | local dataStr = "" 269 | for k,v in pairs(idat) do dataStr = dataStr .. v.data end 270 | local output = {} 271 | deflate.inflate_zlib {input = dataStr, output = function(byte) output[#output+1] = string.char(byte) end, disable_crc = true} 272 | imStr = Stream({input = table.concat(output)}) 273 | 274 | for i = 1, self.height do 275 | self.scanLines[i] = ScanLine(imStr, self.depth, self.colorType, plte, self.width) 276 | if progCallback ~= nil then progCallback(i, self.height) end 277 | end 278 | end 279 | 280 | function pngImage:getPixel(x, y) 281 | local pixel = self.scanLines[y].pixels[x] 282 | return pixel 283 | end 284 | 285 | --------------------------------------------end------------------------------------------- 286 | return pngImage 287 | end 288 | -------------------------------------------------------------------------------- /files/libs/png_encode.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | png encode https://github.com/DelusionalLogic/pngLua 3 | ]] 4 | 5 | function png_encode_wrapper() 6 | --------------------------------------------begin------------------------------------------- 7 | 8 | local Png = {} 9 | Png.__index = Png 10 | 11 | local DEFLATE_MAX_BLOCK_SIZE = 65535 12 | 13 | local function putBigUint32(val, tbl, index) 14 | for i=0,3 do 15 | tbl[index + i] = bit.band(bit.rshift(val, (3 - i) * 8), 0xFF) 16 | end 17 | end 18 | 19 | function Png:writeBytes(data, index, len) 20 | index = index or 1 21 | len = len or #data 22 | for i=index,index+len-1 do 23 | table.insert(self.output, string.char(data[i])) 24 | end 25 | end 26 | 27 | function Png:write(pixels) 28 | local count = #pixels -- Byte count 29 | local pixelPointer = 1 30 | while count > 0 do 31 | if self.positionY >= self.height then 32 | error("All image pixels already written") 33 | end 34 | 35 | if self.deflateFilled == 0 then -- Start DEFLATE block 36 | local size = DEFLATE_MAX_BLOCK_SIZE; 37 | if (self.uncompRemain < size) then 38 | size = self.uncompRemain 39 | end 40 | local header = { -- 5 bytes long 41 | bit.band((self.uncompRemain <= DEFLATE_MAX_BLOCK_SIZE and 1 or 0), 0xFF), 42 | bit.band(bit.rshift(size, 0), 0xFF), 43 | bit.band(bit.rshift(size, 8), 0xFF), 44 | bit.band(bit.bxor(bit.rshift(size, 0), 0xFF), 0xFF), 45 | bit.band(bit.bxor(bit.rshift(size, 8), 0xFF), 0xFF), 46 | } 47 | self:writeBytes(header) 48 | self:crc32(header, 1, #header) 49 | end 50 | assert(self.positionX < self.lineSize and self.deflateFilled < DEFLATE_MAX_BLOCK_SIZE); 51 | 52 | if (self.positionX == 0) then -- Beginning of line - write filter method byte 53 | local b = {0} 54 | self:writeBytes(b) 55 | self:crc32(b, 1, 1) 56 | self:adler32(b, 1, 1) 57 | self.positionX = self.positionX + 1 58 | self.uncompRemain = self.uncompRemain - 1 59 | self.deflateFilled = self.deflateFilled + 1 60 | else -- Write some pixel bytes for current line 61 | local n = DEFLATE_MAX_BLOCK_SIZE - self.deflateFilled; 62 | if (self.lineSize - self.positionX < n) then 63 | n = self.lineSize - self.positionX 64 | end 65 | if (count < n) then 66 | n = count; 67 | end 68 | assert(n > 0); 69 | 70 | self:writeBytes(pixels, pixelPointer, n) 71 | 72 | -- Update checksums 73 | self:crc32(pixels, pixelPointer, n); 74 | self:adler32(pixels, pixelPointer, n); 75 | 76 | -- Increment positions 77 | count = count - n; 78 | pixelPointer = pixelPointer + n; 79 | self.positionX = self.positionX + n; 80 | self.uncompRemain = self.uncompRemain - n; 81 | self.deflateFilled = self.deflateFilled + n; 82 | end 83 | 84 | if (self.deflateFilled >= DEFLATE_MAX_BLOCK_SIZE) then 85 | self.deflateFilled = 0; -- End current block 86 | end 87 | 88 | if (self.positionX == self.lineSize) then -- Increment line 89 | self.positionX = 0; 90 | self.positionY = self.positionY + 1; 91 | if (self.positionY == self.height) then -- Reached end of pixels 92 | local footer = { -- 20 bytes long 93 | 0, 0, 0, 0, -- DEFLATE Adler-32 placeholder 94 | 0, 0, 0, 0, -- IDAT CRC-32 placeholder 95 | -- IEND chunk 96 | 0x00, 0x00, 0x00, 0x00, 97 | 0x49, 0x45, 0x4E, 0x44, 98 | 0xAE, 0x42, 0x60, 0x82, 99 | } 100 | putBigUint32(self.adler, footer, 1) 101 | self:crc32(footer, 1, 4) 102 | putBigUint32(self.crc, footer, 5) 103 | self:writeBytes(footer) 104 | self.done = true 105 | end 106 | end 107 | end 108 | end 109 | 110 | function Png:crc32(data, index, len) 111 | self.crc = bit.bnot(self.crc) 112 | for i=index,index+len-1 do 113 | local byte = data[i] 114 | for j=0,7 do -- Inefficient bitwise implementation, instead of table-based 115 | local nbit = bit.band(bit.bxor(self.crc, bit.rshift(byte, j)), 1); 116 | self.crc = bit.bxor(bit.rshift(self.crc, 1), bit.band((-nbit), 0xEDB88320)); 117 | end 118 | end 119 | self.crc = bit.bnot(self.crc) 120 | end 121 | function Png:adler32(data, index, len) 122 | local s1 = bit.band(self.adler, 0xFFFF) 123 | local s2 = bit.rshift(self.adler, 16) 124 | for i=index,index+len-1 do 125 | s1 = (s1 + data[i]) % 65521 126 | s2 = (s2 + s1) % 65521 127 | end 128 | self.adler = bit.bor(bit.lshift(s2, 16), s1) 129 | end 130 | 131 | local function begin(width, height, colorMode) 132 | -- Default to rgb 133 | colorMode = colorMode or "rgb" 134 | 135 | -- Determine bytes per pixel and the PNG internal color type 136 | local bytesPerPixel, colorType 137 | if colorMode == "rgb" then 138 | bytesPerPixel, colorType = 3, 2 139 | elseif colorMode == "rgba" then 140 | bytesPerPixel, colorType = 4, 6 141 | else 142 | error("Invalid colorMode") 143 | end 144 | 145 | local state = setmetatable({ width = width, height = height, done = false, output = {} }, Png) 146 | 147 | -- Compute and check data siezs 148 | state.lineSize = width * bytesPerPixel + 1 149 | -- TODO: check if lineSize too big 150 | 151 | state.uncompRemain = state.lineSize * height 152 | 153 | local numBlocks = math.ceil(state.uncompRemain / DEFLATE_MAX_BLOCK_SIZE) 154 | 155 | -- 5 bytes per DEFLATE uncompressed block header, 2 bytes for zlib header, 4 bytes for zlib Adler-32 footer 156 | local idatSize = numBlocks * 5 + 6 157 | idatSize = idatSize + state.uncompRemain; 158 | 159 | -- TODO check if idatSize too big 160 | 161 | local header = { -- 43 bytes long 162 | -- PNG header 163 | 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 164 | -- IHDR chunk 165 | 0x00, 0x00, 0x00, 0x0D, 166 | 0x49, 0x48, 0x44, 0x52, 167 | 0, 0, 0, 0, -- 'width' placeholder 168 | 0, 0, 0, 0, -- 'height' placeholder 169 | 0x08, colorType, 0x00, 0x00, 0x00, 170 | 0, 0, 0, 0, -- IHDR CRC-32 placeholder 171 | -- IDAT chunk 172 | 0, 0, 0, 0, -- 'idatSize' placeholder 173 | 0x49, 0x44, 0x41, 0x54, 174 | -- DEFLATE data 175 | 0x08, 0x1D, 176 | } 177 | putBigUint32(width, header, 17) 178 | putBigUint32(height, header, 21) 179 | putBigUint32(idatSize, header, 34) 180 | 181 | state.crc = 0 182 | state:crc32(header, 13, 17) 183 | putBigUint32(state.crc, header, 30) 184 | state:writeBytes(header) 185 | 186 | state.crc = 0 187 | state:crc32(header, 38, 6); -- 0xD7245B6B 188 | state.adler = 1 189 | 190 | state.positionX = 0 191 | state.positionY = 0 192 | state.deflateFilled = 0 193 | 194 | return state 195 | end 196 | 197 | --------------------------------------------end------------------------------------------- 198 | return begin 199 | end 200 | -------------------------------------------------------------------------------- /files/libs/stream.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Stream https://github.com/DelusionalLogic/pngLua 3 | ]] 4 | 5 | function stream_wrapper() 6 | local class = library and library.log30 or log30_wrapper() 7 | --------------------------------------------begin------------------------------------------- 8 | 9 | local Stream = class() 10 | Stream.data = {} 11 | Stream.position = 1 12 | Stream.__name = "Stream" 13 | 14 | function Stream:__init(param) 15 | local str = "" 16 | if (param.inputF ~= nil) then 17 | str = io.open(param.inputF, "rb"):read("*all") 18 | end 19 | if (param.input ~= nil) then 20 | str = param.input 21 | end 22 | 23 | for i=1,#str do 24 | self.data[i] = str:byte(i, i) 25 | end 26 | end 27 | 28 | function Stream:bsRight(num, pow) 29 | return math.floor(num / 2^pow) 30 | end 31 | 32 | function Stream:bsLeft(num, pow) 33 | return math.floor(num * 2^pow) 34 | end 35 | 36 | function Stream:bytesToNum(bytes) 37 | local n = 0 38 | for k,v in ipairs(bytes) do 39 | n = self:bsLeft(n, 8) + v 40 | end 41 | n = (n > 2147483647) and (n - 4294967296) or n 42 | return n 43 | end 44 | 45 | function Stream:seek(amount) 46 | self.position = self.position + amount 47 | end 48 | 49 | function Stream:readByte() 50 | if self.position <= 0 then self:seek(1) return nil end 51 | local byte = self.data[self.position] 52 | self:seek(1) 53 | return byte 54 | end 55 | 56 | function Stream:readChars(num) 57 | if self.position <= 0 then self:seek(1) return nil end 58 | local str = "" 59 | local i = 1 60 | while i <= num do 61 | str = str .. self:readChar() 62 | i = i + 1 63 | end 64 | return str, i-1 65 | end 66 | 67 | function Stream:readChar() 68 | if self.position <= 0 then self:seek(1) return nil end 69 | return string.char(self:readByte()) 70 | end 71 | 72 | function Stream:readBytes(num) 73 | if self.position <= 0 then self:seek(1) return nil end 74 | local tabl = {} 75 | local i = 1 76 | while i <= num do 77 | local curByte = self:readByte() 78 | if curByte == nil then break end 79 | tabl[i] = curByte 80 | i = i + 1 81 | end 82 | return tabl, i-1 83 | end 84 | 85 | function Stream:readInt(num) 86 | if self.position <= 0 then self:seek(1) return nil end 87 | num = num or 4 88 | local bytes, count = self:readBytes(num) 89 | return self:bytesToNum(bytes), count 90 | end 91 | 92 | function Stream:writeByte(byte) 93 | if self.position <= 0 then self:seek(1) return end 94 | self.data[self.position] = byte 95 | self:seek(1) 96 | end 97 | 98 | function Stream:writeChar(char) 99 | if self.position <= 0 then self:seek(1) return end 100 | self:writeByte(string.byte(char)) 101 | end 102 | 103 | function Stream:writeBytes(buffer) 104 | if self.position <= 0 then self:seek(1) return end 105 | local str = "" 106 | for k,v in pairs(buffer) do 107 | str = str .. string.char(v) 108 | end 109 | writeChars(str) 110 | end 111 | 112 | --------------------------------------------end------------------------------------------- 113 | return Stream 114 | end -------------------------------------------------------------------------------- /files/log.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | log 3 | ]] 4 | 5 | assert(Log == nil) 6 | Log = class("Log") 7 | 8 | Log.LEVEL = { 9 | DEBUG = 1, 10 | INFO = 2, 11 | WARN = 3, 12 | ERROR = 4, 13 | USER = 5, 14 | } 15 | 16 | Log.COLOR = { 17 | TAG_BG_CONTENT_FG = {4, 3}, 18 | TAG_FG_CONTENT_BG = {3, 4}, 19 | TAG_BG_CONTENT_BG = {4, 4}, 20 | TAG_FG_CONTENT_FG = {3, 3}, 21 | } 22 | 23 | local COLORS = { 24 | [Log.LEVEL.DEBUG] = "\27[%d4m %s \27[0m", 25 | [Log.LEVEL.INFO] = "\27[%d2m %s \27[0m", 26 | [Log.LEVEL.WARN] = "\27[%d3m %s \27[0m", 27 | [Log.LEVEL.ERROR] = "\27[%d1m %s \27[0m", 28 | [Log.LEVEL.USER] = "\27[%d5m %s \27[0m", 29 | } 30 | 31 | local operationg = {} 32 | 33 | function Log:__init__(path, name, level, color) 34 | assert(path == nil or string.valid(path), 'invalid log path!') 35 | assert(name == nil or string.valid(name), 'invalid log name!') 36 | assert(level == nil or is_number(level), 'invalid log level!') 37 | assert(color == nil or table.find_value(Log.COLOR, color), 'invalid log color!') 38 | self._name = name or "UNKNOWN" 39 | self._level = level or Log.LEVEL.DEBUG 40 | self._color = color or Log.COLOR.TAG_BG_CONTENT_FG 41 | if path ~= nil then 42 | self._path = path 43 | assert(operationg[self._path] == nil, 'log already opened!') 44 | operationg[self._path] = true 45 | if files.is_file(self._path) then 46 | files.delete(self._path) 47 | end 48 | if not files.is_file(self._path) then 49 | files.mk_folder(files.get_folder(self._path)) 50 | end 51 | self._file = io.open(path, "a") 52 | assert(self._file ~= nil, 'invalid log file!') 53 | end 54 | self._valid = true 55 | self:_write(Log.LEVEL.USER, "START->%s", self._name) 56 | end 57 | 58 | function Log:close() 59 | assert(self._valid == true, 'log already closed!') 60 | if self._path ~= nil then 61 | operationg[self._path] = nil 62 | self._path = nil 63 | end 64 | if self._file ~= nil then 65 | self._file:close() 66 | self._file = nil 67 | end 68 | self._valid = false 69 | end 70 | 71 | function Log:_write(level, content, ...) 72 | assert(self._valid == true, 'log already closed!') 73 | local levelName = table.find_value(Log.LEVEL, level) 74 | local logContent = string.format(content, ...) 75 | assert(levelName ~= nil, 'invalid log level!') 76 | assert(string.valid(content), 'invalid log content!') 77 | local date = os.date("%Y-%m-%d_%H:%M:%S", os.time()) 78 | local header = string.format("[%s_%s]", self._name, date) 79 | local footer = string.format("%s : %s", string.left(levelName, 5, " "), logContent) 80 | if self._file then 81 | self._file:write(string.format("%s %s\n", header, footer)) 82 | end 83 | if table.is_empty(self._color) then 84 | print(string.format("%s %s", header, footer)) 85 | else 86 | local left = string.format(COLORS[level], self._color[1], header) 87 | local right = string.format(COLORS[level], self._color[2], footer) 88 | print(string.format("%s %s", left, right)) 89 | end 90 | end 91 | 92 | function Log:user(content, ...) 93 | self:_write(Log.LEVEL.USER, content, ...) 94 | end 95 | 96 | function Log:error(content, ...) 97 | self:_write(Log.LEVEL.ERROR, content, ...) 98 | end 99 | 100 | function Log:warn(content, ...) 101 | self:_write(Log.LEVEL.WARN, content, ...) 102 | end 103 | 104 | function Log:info(content, ...) 105 | self:_write(Log.LEVEL.INFO, content, ...) 106 | end 107 | 108 | function Log:debug(content, ...) 109 | self:_write(Log.LEVEL.DEBUG, content, ...) 110 | end 111 | -------------------------------------------------------------------------------- /files/lua.lua: -------------------------------------------------------------------------------- 1 | -- lua extentions 2 | 3 | function null() 4 | return null 5 | end 6 | 7 | function is_userdata(v) 8 | return type(v) == 'userdata' 9 | end 10 | 11 | function is_table(v) 12 | return type(v) == 'table' 13 | end 14 | 15 | function is_array(v) 16 | return type(v) == 'table' and #v == table.count(v) 17 | end 18 | 19 | function is_string(v) 20 | return type(v) == 'string' 21 | end 22 | 23 | function is_number(v) 24 | return type(v) == 'number' 25 | end 26 | 27 | function is_boolean(v) 28 | return type(v) == 'boolean' 29 | end 30 | 31 | function is_nil(v) 32 | return type(v) == 'nil' or v == null 33 | end 34 | 35 | function is_empty(v) 36 | if is_nil(v) then 37 | return true 38 | end 39 | if is_table(v) then 40 | return next(v) == nil 41 | end 42 | return false 43 | end 44 | 45 | function is_function(v) 46 | return type(v) == 'function' 47 | end 48 | 49 | function is_class(v) 50 | return is_table(v) and v.__type__ == 'class' 51 | end 52 | 53 | function is_object(v) 54 | return is_table(v) and v.__type__ == 'object' 55 | end 56 | 57 | if not rawget(_G, "lua_print") then 58 | rawset(_G, "lua_print", print) 59 | end 60 | function print(...) 61 | local args = {...} 62 | for i=1,select("#", ...) do 63 | local v = args[i] 64 | if v == null then 65 | v = "null" 66 | elseif is_class(v) or is_object(v) then 67 | v = tostring(v) 68 | elseif is_table(v) then 69 | v = table.printable(v, " ") 70 | else 71 | v = tostring(v) 72 | end 73 | io.write(v, " ") 74 | end 75 | io.write('\n') 76 | end 77 | 78 | function to_type(v, tp) 79 | if type(v) == tp then 80 | return v 81 | elseif tp == 'string' then 82 | return tostring(v) 83 | elseif tp == 'number' then 84 | return tonumber(v) 85 | elseif tp == 'boolean' then 86 | if is_string(v) then 87 | v = v:lower() 88 | if v == 'true' then 89 | return true 90 | elseif v == 'false' then 91 | return false 92 | else 93 | return nil 94 | end 95 | elseif is_number(v) then 96 | if v == 1 then 97 | return true 98 | elseif v == 0 then 99 | return false 100 | else 101 | return nil 102 | end 103 | else 104 | return nil 105 | end 106 | else 107 | return nil 108 | end 109 | end 110 | 111 | function lua_to_string(t) 112 | local m = getmetatable(t) 113 | if m and m.__tostring then 114 | local tmp = m.__tostring 115 | m.__tostring = nil 116 | local ret = tostring(t) 117 | m.__tostring = tmp 118 | return ret 119 | else 120 | return tostring(t) 121 | end 122 | end 123 | 124 | function lua_get_pointer(v) 125 | local t = type(v) 126 | if t == "function" or t == "table" then 127 | local s = lua_to_string(v):explode(": ") 128 | return s[2] 129 | else 130 | return nil 131 | end 132 | end 133 | 134 | function lua_set_debug(enable) 135 | rawset(_G, 'lua-is-debug', enable == true) 136 | end 137 | 138 | function lua_is_debug() 139 | return rawget(_G, 'lua-is-debug') == true 140 | end 141 | 142 | function lua_set_user(user) 143 | assert(#user >= 3, 'invalid user format') 144 | assert(string.match(user, '%w+'), 'invalid user format') 145 | rawset(_G, 'lua-user-name', user) 146 | end 147 | 148 | function lua_get_user() 149 | return rawget(_G, 'lua-user-name') or 'unknown' 150 | end 151 | 152 | function lua_script_path(level) 153 | level = level or 0 154 | local info = debug.getinfo(2 + level, "S") 155 | if info and info.source and info.source:sub(1, 1) == "@" then 156 | return info.source:sub(2) 157 | end 158 | return nil 159 | end 160 | 161 | function lua_new_decorator(func) 162 | assert(func == nil or is_function(func)) 163 | local function _call(self, ...) 164 | local args = {...} 165 | if self._bFunc then 166 | local _results = {self._bFunc(unpack(args))} 167 | if #_results > 0 then 168 | args = _results 169 | end 170 | end 171 | assert(self._fFunc, 'decorator func not found') 172 | local results = nil 173 | if not self._eFunc then 174 | results = {self._fFunc(unpack(args))} 175 | else 176 | xpcall(function() 177 | results = {self._fFunc(unpack(args))} 178 | end, function(e) 179 | results = {self._eFunc(e)} 180 | end) 181 | end 182 | assert(results ~= nil, 'decorator logic eror found') 183 | if self._aFunc then 184 | local _results = {self._aFunc(unpack(results))} 185 | if #_results > 0 then 186 | results = _results 187 | end 188 | end 189 | return unpack(results) 190 | end 191 | local decorator = { 192 | _bFunc = nil, 193 | _fFunc = func, 194 | _eFunc = nil, 195 | _aFunc = nil, 196 | } 197 | function decorator:before(func) 198 | assert(func == nil or is_function(func)) 199 | self._bFunc = func 200 | return self 201 | end 202 | function decorator:after(func) 203 | assert(func == nil or is_function(func)) 204 | self._aFunc = func 205 | return self 206 | end 207 | function decorator:error(func) 208 | assert(func == nil or is_function(func)) 209 | self._eFunc = func 210 | return self 211 | end 212 | function decorator:func(func) 213 | assert(func == nil or is_function(func)) 214 | self._fFunc = func 215 | return self 216 | end 217 | function decorator:call(...) 218 | return _call(self, ...) 219 | end 220 | setmetatable(decorator, {__call = _call}) 221 | return decorator 222 | end 223 | 224 | local function __lua_delegate_func(t, k) 225 | local v = rawget(t, k) 226 | if v ~= nil then 227 | return 228 | end 229 | local _meta = rawget(t, "__delegated") 230 | local _dlgt = rawget(t, "__delegation") 231 | if _meta ~= nil and _meta.__index ~= nil and _meta.__index ~= __lua_delegate_func then 232 | v = _meta.__index[k] 233 | end 234 | if v ~= nil then 235 | return v 236 | end 237 | -- 238 | if is_function(_dlgt) then 239 | v = function(...) return _dlgt(k, ...) end 240 | elseif is_userdata(_dlgt) then 241 | if is_function(_dlgt[k]) then 242 | v = function(_t, ...) return _dlgt[k](_dlgt, ...) end 243 | else 244 | v = _dlgt[k] 245 | end 246 | end 247 | return v 248 | end 249 | 250 | function lua_set_delegate(obj, delegation) 251 | rawset(obj, '__delegation', delegation) 252 | if rawget(obj, '__delegated') ~= nil or delegation == nil then return end 253 | local _meta = getmetatable(obj) 254 | rawset(obj, '__delegated', _meta) 255 | setmetatable(obj, {__index = __lua_delegate_func}) 256 | end 257 | 258 | -------------------------------------------------------------------------------- /files/math.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | math 3 | ]] 4 | 5 | function math.radian(angle) 6 | return angle * math.pi / 180 7 | end 8 | 9 | function math.angle(radian) 10 | return radian * 180 / math.pi 11 | end 12 | 13 | function math.round(value) 14 | return math.floor(value + 0.5) 15 | end 16 | -------------------------------------------------------------------------------- /files/number.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | number 3 | ]] 4 | 5 | number = number or {} 6 | 7 | function number.is_odd(v) 8 | return v % 2 ~= 0 9 | end 10 | 11 | function number.is_even() 12 | return not number.is_odd(v) 13 | end 14 | 15 | function number.is_int(v) 16 | return math.floor(v) == v 17 | end 18 | 19 | function number.is_float(v) 20 | return not number.is_int(v) 21 | end 22 | -------------------------------------------------------------------------------- /files/object.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Object 3 | ]] 4 | 5 | assert(Object == nil) 6 | Object = {} 7 | Object.__index = Object 8 | 9 | function Object:init() 10 | end 11 | 12 | function Object:new(...) 13 | assert(self.__class == nil, 'can not instantiate object!') 14 | local obj = setmetatable({}, self) 15 | obj.__class = self 16 | obj:init(...) 17 | return obj 18 | end 19 | 20 | function Object:ext() 21 | assert(self.__class == nil, 'can not extend object!') 22 | local Cls = {} 23 | Cls.__index = Cls 24 | setmetatable(Cls, self) 25 | return Cls 26 | end 27 | 28 | function Object:is(Cls) 29 | assert(self.__class ~= nil, 'can not check Object!') 30 | local mt = getmetatable(self) 31 | while mt do 32 | if mt == Cls then return true end 33 | mt = getmetatable(mt) 34 | end 35 | return false 36 | end 37 | -------------------------------------------------------------------------------- /files/package.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | package 3 | ]] 4 | 5 | local recursiveMap = {} 6 | local modulesMap = {} 7 | 8 | local function load(path, env) 9 | local f, err = loadfile(path) 10 | assert(f ~= nil or err == nil, err) 11 | if env then setfenv(f, env) end 12 | local r, msg = pcall(f) 13 | assert(r == true, msg) 14 | modulesMap[path] = msg ~= nil and msg or true 15 | return msg 16 | end 17 | 18 | local function search(path) 19 | if files.is_file(files.csd() .. path) then 20 | return files.csd() .. path 21 | elseif files.is_file(files.cwd() .. path) then 22 | return files.cwd() .. path 23 | elseif files.is_file(path) then 24 | return path 25 | end 26 | end 27 | 28 | function package.doload(path, env) 29 | path = search(tostring(path)) 30 | if path and modulesMap[path] then 31 | return modulesMap[path] ~= true and modulesMap[path] or nil 32 | end 33 | assert(path ~= nil) 34 | assert(recursiveMap[path] == nil) 35 | recursiveMap[path] = true 36 | local r = load(path, env) 37 | recursiveMap[path] = nil 38 | return r 39 | end 40 | 41 | function package.unload(path) 42 | path = search(tostring(path)) 43 | if path and modulesMap[path] then modulesMap[path] = nil end 44 | end 45 | 46 | function package.isloaded(path) 47 | path = search(tostring(path)) 48 | return path ~= nil and modulesMap[path] ~= nil 49 | end 50 | -------------------------------------------------------------------------------- /files/path.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Path 3 | ]] 4 | 5 | assert(Path == nil) 6 | Path = class("Path") 7 | 8 | function Path:__init__(value) 9 | self._stack = table.new() 10 | if value then 11 | self:set(value) 12 | end 13 | end 14 | 15 | function Path:_parse(value) 16 | if value:starts("~") then 17 | value = files.home() .. "/" .. value:sub(2, -1) 18 | elseif value == "." or value:starts("./") then 19 | value = files.cwd() .. "/" .. value:sub(2, -1) 20 | elseif value == ".." or value:starts("../") then 21 | value = files.cwd() .. "/../" .. value:sub(3, -1) 22 | elseif value:starts("/") then 23 | value = files.root() .. "/" .. value:sub(2, -1) 24 | end 25 | return value 26 | end 27 | 28 | function Path:_explode(value) 29 | return files.unixify(value):trim("/"):explode("/") 30 | end 31 | 32 | function Path:_implode(stack) 33 | return table.implode(stack, "/") 34 | end 35 | 36 | function Path:_validate() 37 | local size = #self._stack 38 | local count = 0 39 | for i=size,1,-1 do 40 | local item = self._stack[i] 41 | if i == 1 then 42 | break 43 | elseif item == "" then 44 | table.remove(self._stack, i) 45 | elseif item == "." then 46 | table.remove(self._stack, i) 47 | elseif item == ".." then 48 | count = count + 1 49 | table.remove(self._stack, i) 50 | elseif count > 0 then 51 | count = count - 1 52 | table.remove(self._stack, i) 53 | end 54 | assert(count >= 0, 'invalid path validate') 55 | end 56 | end 57 | 58 | function Path:cd(value) 59 | value = files.unixify(value) 60 | if not string.valid(value) then 61 | return self 62 | end 63 | if #self._stack == 0 or value:starts("~") or value:starts("/") then 64 | value = self:_parse(value) 65 | self._stack = self:_explode(value) 66 | else 67 | self:push(value) 68 | end 69 | self:_validate() 70 | return self 71 | end 72 | 73 | function Path:set(value) 74 | value = files.unixify(value) 75 | assert(string.valid(value), 'invalid path value') 76 | value = self:_parse(value) 77 | self._stack = self:_explode(value) 78 | self:_validate() 79 | return self 80 | end 81 | 82 | function Path:get() 83 | return self:_implode(self._stack) 84 | end 85 | 86 | function Path:push(...) 87 | local values = {...} 88 | for i,value in ipairs(values) do 89 | value = files.unixify(value) 90 | assert(string.valid(value), 'invalid path value') 91 | local stack = self:_explode(value) 92 | self._stack:append(stack) 93 | end 94 | self:_validate() 95 | return self 96 | end 97 | 98 | function Path:pop(count) 99 | count = count or 1 100 | for i=1,count do 101 | table.remove(self._stack, #self._stack) 102 | end 103 | self:_validate() 104 | return self 105 | end 106 | 107 | function Path:equal(other) 108 | assert(type(other) == "table") 109 | return self:get() == other:get() 110 | end 111 | 112 | function Path:relative(other) 113 | if self._stack[1] ~= other._stack[1] then 114 | return 115 | end 116 | local max = math.max(#self._stack, #other._stack) 117 | local diff = "./" 118 | for i=1,max do 119 | local v1 = self._stack[i] 120 | local v2 = other._stack[i] 121 | if v1 ~= nil and v2 ~= nil then 122 | if v1 ~= v2 then 123 | diff = diff .. "../" .. v2 124 | end 125 | elseif v1 and not v2 then 126 | diff = ".." .. "/" .. diff 127 | elseif not v1 and v2 then 128 | diff = diff .. "/" .. v2 129 | end 130 | end 131 | print(diff) 132 | return Path(diff) 133 | end 134 | 135 | function Path:clone() 136 | local oldPath = self:get() 137 | local objPath = Path(oldPath) 138 | local newPath = objPath:get() 139 | assert(oldPath == newPath, 'buggy path operation') 140 | return objPath 141 | end 142 | 143 | function Path:root() 144 | return self:pop(#self._stack - 1) 145 | end 146 | 147 | function Path:size() 148 | return #self._stack 149 | end 150 | 151 | function Path:isRoot() 152 | return #self._stack == 1 153 | end 154 | 155 | function Path:isFile() 156 | local last = self._stack[#self._stack] 157 | return last ~= nil and string.match(last, '%.%w+$') ~= nil 158 | end 159 | 160 | function Path:getDir() 161 | local stack = table.copy(self._stack) 162 | if self:isFile() then 163 | table.remove(stack, #stack) 164 | end 165 | return self:_implode(stack) 166 | end 167 | 168 | function Path:getNameWithExt() 169 | if self:isFile() then 170 | local nameWithExe = self._stack[#self._stack] 171 | local arr = files.unixify(nameWithExe):trim():explode("%.") 172 | local nam = arr[1] 173 | local ext = arr[2] 174 | return nameWithExe, nam, ext 175 | end 176 | end 177 | 178 | -------------------------------------------------------------------------------- /files/plist.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | plist 3 | ]] 4 | 5 | plist = plist or {} 6 | 7 | local plist_indent = 2 8 | local plist_prefix = string.rep(" ", plist_indent) 9 | local function plist_encode(value, indent) 10 | local prefix1 = string.rep(plist_prefix, indent + 1) 11 | local prefix2 = string.rep(plist_prefix, indent) 12 | if is_string(value) then 13 | return prefix2 .. "" .. value .. "" 14 | elseif is_number(value) then 15 | return prefix2 .. "" .. tostring(value) .. "" 16 | elseif is_boolean(value) then 17 | return prefix2 .. (value and "" or "") 18 | elseif is_array(value) then 19 | local arr = {} 20 | for i, v in ipairs(value) do 21 | table.insert(arr, plist_encode(v, indent + 1)) 22 | end 23 | local itm = table.concat(arr, '\n') 24 | if string.valid(itm) then 25 | itm = "\n" .. itm .. "\n" .. prefix2 26 | end 27 | return prefix2 .. "" .. itm .. "" 28 | elseif is_table(value) then 29 | local dic = {} 30 | for k, v in pairs(value) do 31 | table.insert(dic, prefix1 .. "" .. tostring(k) .. "") 32 | table.insert(dic, plist_encode(v, indent + 1)) 33 | end 34 | return prefix2 .. "\n" .. table.concat(dic, '\n') .. "\n" .. prefix2 .. "" 35 | else 36 | error("unsupported value for plist: " .. tostring(value)) 37 | end 38 | end 39 | 40 | function plist.encode(t) 41 | assert(is_table(t)) 42 | local d = {} 43 | table.insert(d, '') 44 | table.insert(d, '') 45 | table.insert(d, '') 46 | table.insert(d, plist_encode(t, 0)) 47 | table.insert(d, '') 48 | return table.concat(d, '\n') 49 | end 50 | 51 | function plist.decode(text) 52 | local stack = {} 53 | local root = {} 54 | local curr = nil 55 | local key = nil 56 | for tag, value in string.gmatch(text, "<(/?%w+/?)>([^<]*)") do 57 | if tag == "dict" then 58 | if not curr then 59 | assert(key == nil) 60 | curr = root 61 | else 62 | local k = key or #curr + 1 63 | curr[k] = {} 64 | curr = curr[k] 65 | end 66 | table.insert(stack, curr) 67 | elseif tag == "/dict" then 68 | table.remove(stack) 69 | curr = stack[#stack] 70 | key = nil 71 | elseif tag == "array" then 72 | local k = key or #curr + 1 73 | curr[k] = {} 74 | curr = curr[k] 75 | table.insert(stack, curr) 76 | key = nil 77 | elseif tag == "/array" then 78 | table.remove(stack) 79 | curr = stack[#stack] 80 | key = nil 81 | elseif tag == "key" then 82 | key = value 83 | elseif tag == "string" then 84 | local k = key or #curr + 1 85 | curr[k] = value 86 | elseif tag == "real" then 87 | local k = key or #curr + 1 88 | curr[k] = tonumber(value) 89 | elseif tag == "true/" then 90 | local k = key or #curr + 1 91 | curr[k] = true 92 | elseif tag == "false/" then 93 | local k = key or #curr + 1 94 | curr[k] = false 95 | end 96 | end 97 | 98 | return root 99 | end 100 | -------------------------------------------------------------------------------- /files/point.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Point 3 | ]] 4 | 5 | assert(Point == nil) 6 | Point = class("Point") 7 | 8 | function Point:__init__(x, y) 9 | self.x = x 10 | self.y = y 11 | end 12 | 13 | -- math.pi / 4 -> {x = 0.7, y = 0.7} 14 | function Point.from_radian(radian) 15 | return Point(math.cos(radian), math.sin(radian)) 16 | end 17 | 18 | -- 0° -> {x = 1, y = 0} 19 | function Point.from_angle(angle) 20 | local radian = angle * (math.pi / 180) 21 | return Point.from_radian(radian) 22 | end 23 | 24 | function Point:clone() 25 | return Point(self.x, self.y) 26 | end 27 | 28 | function Point:add(other) 29 | return Point(self.x + other.x, self.y + other.y) 30 | end 31 | 32 | function Point:sub(other) 33 | return Point(self.x - other.x, self.y - other.y) 34 | end 35 | 36 | function Point:mul(pt, factor) 37 | return Point(self.x * factor, self.y * factor) 38 | end 39 | 40 | function Point:div(pt, factor) 41 | return Point(self.x / factor, self.y / factor) 42 | end 43 | 44 | function Point:middle() 45 | return Point(self.x / 2, self.y / 2) 46 | end 47 | 48 | function Point:length() 49 | return math.sqrt(self.x ^ 2 + self.y ^ 2) 50 | end 51 | 52 | function Point:distance(other) 53 | return self:sub(other):length() 54 | end 55 | 56 | function Point:normalize() 57 | local length = self:length() 58 | local x, y = 1, 0 59 | if length > 0 then 60 | x, y = self.x / length, self.y / length 61 | end 62 | return Point(x, y) 63 | end 64 | 65 | function Point:cross(other) 66 | return self.x * other.y - self.y * other.x 67 | end 68 | 69 | function Point:dot(other) 70 | return self.x * other.x + self.y * other.y 71 | end 72 | 73 | function Point:radian() 74 | return math.atan2(self.x, self.y) 75 | end 76 | 77 | function Point:angleWithOther(other) 78 | local normal1 = other:normalize() 79 | local normal2 = self:normalize() 80 | local angle = math.atan2(normal1:cross(normal2), normal1:dot(normal2)) 81 | if math.abs(angle) < 1.192092896e-7 then 82 | return 0.0 83 | end 84 | return angle * 180 / math.pi; 85 | end 86 | 87 | function Point:angleOfSelf(base) 88 | if not base then 89 | base = Point(0, 0) 90 | end 91 | local dx = self.x - base.x 92 | local dy = self.y - base.y 93 | local angle = math.atan2(dy, dx) * 180 / math.pi 94 | return angle 95 | end 96 | 97 | function Point:pProject(other) 98 | local oDot = other:dot(other) 99 | return Point(other.x * (self:dot(other) / oDot) , pt2.y * (self:dot(other) / oDot)) 100 | end 101 | 102 | function Point:pRotate(other) 103 | return Point(self.x * other.x - self.y * other.y, self.x * other.y + self.y * other.x) 104 | end 105 | 106 | function Point:rotate(angle, base) 107 | if not base then 108 | base = Point(0, 0) 109 | end 110 | local vector = self:sub(base):pRotate(Point.from_angle(angle)) 111 | local normal = vector:normalize() 112 | local length = self:length() 113 | local temp = Point(normal.x * length, normal.y * length) 114 | local point = base:add(vector) 115 | return point 116 | end 117 | -------------------------------------------------------------------------------- /files/shell.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | shell 3 | ]] 4 | 5 | shell = shell or {} 6 | 7 | local smt = {} 8 | setmetatable(shell, smt) 9 | 10 | local function shell_execute(cmd, ...) 11 | for _, v in ipairs({...}) do 12 | cmd = cmd .. ' ' .. v 13 | end 14 | local isOk, out = tools.execute(cmd) 15 | return isOk, out 16 | end 17 | 18 | smt.__index = function(t, cmd) 19 | return function(...) 20 | return shell_execute(cmd, ...) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /files/storage.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | storage 3 | ]] 4 | 5 | assert(Storage == nil) 6 | Storage = class("Storage") 7 | 8 | local operationg = {} 9 | 10 | function Storage:__init__(path, shiftCount) 11 | assert(is_string(path) and #path > 0, 'invalid storage path!') 12 | assert(shiftCount == nil or is_number(shiftCount), 'invalid salt type!') 13 | self._path = path ~= files.get_folder(path) and path or path .. ".db" 14 | self._shift = shiftCount or 0 15 | self._data = nil 16 | if files.is_file(self._path) then 17 | self:_read() 18 | end 19 | if not self._data then 20 | self._data = {} 21 | self:_write() 22 | end 23 | assert(files.is_file(self._path), 'storage initialize failed!') 24 | assert(operationg[self._path] == nil, 'storage already in use!') 25 | operationg[self._path] = true 26 | end 27 | 28 | function Storage:close() 29 | assert(self._path ~= nil, 'storage already closed!') 30 | operationg[self._path] = nil 31 | self._path = nil 32 | self._data = nil 33 | end 34 | 35 | function Storage:_read() 36 | assert(self._path ~= nil, 'storage already closed!') 37 | local content = files.read(self._path) 38 | assert(content ~= nil, "invalid storage file:" .. self._path) 39 | if self._shift > 0 then 40 | content = encryption.base64_decode(content) 41 | local list = {} 42 | for i = 1, #content do 43 | list[i] = string.char(string.byte(content:sub(i,i)) - self._shift) 44 | end 45 | content = table.implode(list) 46 | content = encryption.base64_decode(content) 47 | end 48 | self._data = json.decode(content) 49 | end 50 | 51 | function Storage:_write() 52 | assert(self._path ~= nil, 'storage already closed!') 53 | local content = json.encode(self._data) 54 | if self._shift > 0 then 55 | content = encryption.base64_encode(content) 56 | local list = {} 57 | for i = 1, #content do 58 | list[i] = string.char(string.byte(content:sub(i,i)) + self._shift) 59 | end 60 | content = table.implode(list) 61 | content = encryption.base64_encode(content) 62 | end 63 | if not files.is_file(self._path) then 64 | files.mk_folder(files.get_folder(self._path)) 65 | end 66 | return files.write(self._path, content) 67 | end 68 | 69 | function Storage:get(key, default) 70 | assert(self._path ~= nil, 'storage already closed!') 71 | assert(is_string(key), 'invalid storage key!') 72 | if self._data[key] == nil then return default end 73 | assert(type(self._data[key]) == type(default) or default == nil, 'invalid data type!') 74 | return self._data[key] 75 | end 76 | 77 | function Storage:set(key, value) 78 | assert(self._path ~= nil, 'storage already closed!') 79 | assert(is_string(key), 'invalid storage key!') 80 | if self._data[key] == nil then 81 | self._data[key] = value 82 | else 83 | assert(type(self._data[key]) == type(value) or value == nil, 'invalid data type!') 84 | self._data[key] = value 85 | end 86 | return self:_write() 87 | end 88 | -------------------------------------------------------------------------------- /files/string.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | string 3 | ]] 4 | 5 | function string.new(v) 6 | assert(v ~= string) 7 | assert(v == nil or type(v) == 'string') 8 | return v or "" 9 | end 10 | 11 | function string.append(this, other) 12 | return this .. other 13 | end 14 | 15 | function string.prepend(this, other) 16 | return other .. this 17 | end 18 | 19 | function string.ltrim(this, pattern) 20 | pattern = pattern or " \t\n\r" 21 | return string.gsub(this, "^[" .. pattern .. "]+", "") 22 | end 23 | 24 | function string.rtrim(this, pattern) 25 | pattern = pattern or " \t\n\r" 26 | return string.gsub(this, "[" .. pattern .. "]+$", "") 27 | end 28 | 29 | function string.trim(this, pattern) 30 | return this:ltrim(pattern):rtrim(pattern) 31 | end 32 | 33 | function string.slash(this) 34 | return this:gsub('\\', '/') 35 | end 36 | 37 | function string.implode(t, separator) 38 | return table.concat(t, separator) 39 | end 40 | 41 | function string.explode(this, separator, maxCount) 42 | assert(is_string(this)) 43 | assert(is_string(separator)) 44 | local splitArray = table.new() 45 | if #this == 0 then 46 | return splitArray 47 | end 48 | if #separator == 0 then 49 | for i=1,#this do 50 | if #splitArray >= maxCount then 51 | splitArray[i] = string.sub(this, i, string.len(this)) 52 | break 53 | end 54 | splitArray[i] = string.sub(this, i, i) 55 | end 56 | return splitArray 57 | end 58 | local startIndex = 1 59 | local splitIndex = 1 60 | while true do 61 | local foundIndex, endIndex = string.find(this, separator, startIndex) 62 | if not foundIndex or (maxCount and #splitArray >= maxCount) then 63 | splitArray[splitIndex] = string.sub(this, startIndex, string.len(this)) 64 | break 65 | end 66 | splitArray[splitIndex] = string.sub(this, startIndex, foundIndex - 1) 67 | startIndex = foundIndex + (endIndex - foundIndex + 1) 68 | splitIndex = splitIndex + 1 69 | end 70 | return splitArray 71 | end 72 | 73 | function string.valid(v) 74 | return type(v) == 'string' and #v > 0 75 | end 76 | 77 | function string.center(this, len, char) 78 | len = math.max(len, 0) 79 | local need = len - #this 80 | if need <= 0 then 81 | return this:sub(1, len) 82 | else 83 | local l = math.floor(need / 2) 84 | local r = need - l 85 | return char:rep(l) .. this .. char:rep(r) 86 | end 87 | end 88 | 89 | function string.left(this, len, char) 90 | len = math.max(len, 0) 91 | local need = len - #this 92 | return need <= 0 and this:sub(1, len) or this .. char:rep(need) 93 | end 94 | 95 | function string.right(this, len, char) 96 | len = math.max(len, 0) 97 | local need = len - #this 98 | return need <= 0 and this:sub(#this - len + 1, #this) or char:rep(need) .. this 99 | end 100 | 101 | function string.table(this) 102 | local f = loadstring("return " .. this) 103 | if not f then return end 104 | local t = f() 105 | if not is_table(t) then return end 106 | return table.new(t) 107 | end 108 | 109 | function string.encode(tb) 110 | assert(is_table(tb)) 111 | return tb:string() 112 | end 113 | 114 | function string.decode(this) 115 | assert(is_string(this)) 116 | return this:table() 117 | end 118 | 119 | function string.print(this) 120 | print(this) 121 | end 122 | 123 | function string.execute(this) 124 | local f = loadstring(this) 125 | assert(is_function(f), "invalid script string") 126 | return f() 127 | end 128 | 129 | function string.escape(s) 130 | s = s:gsub('%%', '%%%%') 131 | :gsub('^%^', '%%^') 132 | :gsub('%$$', '%%$') 133 | :gsub('%(', '%%(') 134 | :gsub('%)', '%%)') 135 | :gsub('%.', '%%.') 136 | :gsub('%[', '%%[') 137 | :gsub('%]', '%%]') 138 | :gsub('%*', '%%*') 139 | :gsub('%+', '%%+') 140 | :gsub('%-', '%%-') 141 | :gsub('%?', '%%?') 142 | return s 143 | end 144 | 145 | function string.starts(this, s) 146 | return string.sub(this, 1, #s) == s 147 | end 148 | 149 | function string.ends(this, s) 150 | return string.sub(this, -#s, -1) == s 151 | end 152 | 153 | function string.limit(this, length, suffix) 154 | assert(length > 0, 'invalid limit length') 155 | suffix = suffix or "..." 156 | assert(length > #suffix, 'invalid limit length') 157 | if #this <= length then 158 | return this 159 | else 160 | return stirng.sub(this, 1, length - #suffix) .. suffix 161 | end 162 | end 163 | 164 | function string.render(this, ...) 165 | local args = {...} 166 | if is_table(args[1]) then 167 | args = args[1] 168 | end 169 | local rgxp = is_array(args) and "{(%d+)}" or "{(%w+)}" 170 | local result = this:gsub(rgxp, function(val) 171 | local key = is_array(args) and tonumber(val) or val 172 | return args[key] and tostring(args[key]) or "{" .. key .. "}" 173 | end) 174 | return result 175 | end 176 | -------------------------------------------------------------------------------- /files/table.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | table 3 | ]] 4 | 5 | function table.new(t, mode) 6 | assert(t ~= table) 7 | assert(t == nil or type(t) == 'table') 8 | return setmetatable(t or {}, {__index = table, __mode = mode}) 9 | end 10 | 11 | function table.new_with_weak_key(t) 12 | return table.new(t, 'k') 13 | end 14 | 15 | function table.new_with_weak_value(t) 16 | return table.new(t, 'v') 17 | end 18 | 19 | function table.clear(this) 20 | for k,v in pairs(this) do 21 | this[k] = nil 22 | end 23 | return this 24 | end 25 | 26 | function table.keys(this) 27 | local keys = table.new() 28 | for key, _ in pairs(this) do 29 | table.insert(keys, key) 30 | end 31 | return keys 32 | end 33 | 34 | function table.values(this) 35 | local values = table.new() 36 | for _, value in pairs(this) do 37 | table.insert(values, value) 38 | end 39 | return values 40 | end 41 | 42 | function table.merge(this, that) 43 | for k,v in pairs(that) do 44 | this[k] = v 45 | end 46 | return this 47 | end 48 | 49 | function table.sub(this, from, to) 50 | assert(is_array(this)) 51 | local ret = {} 52 | local len = #this 53 | from = from or 1 54 | to = to or #this 55 | if from < 0 then 56 | from = from + len + 1 57 | end 58 | if to < 0 then 59 | to = to + len + 1 60 | end 61 | from = math.max(0, math.min(from, len)) 62 | to = math.max(0, math.min(to, len)) 63 | for i=from,to do 64 | table.insert(ret, this[i]) 65 | end 66 | return ret 67 | end 68 | 69 | function table.filter(this, func) 70 | local ret = {} 71 | table.foreach(this, function(k, v) 72 | if func(k, v) then 73 | if is_array(this) then 74 | table.insert(ret, v) 75 | else 76 | ret[k] = v 77 | end 78 | end 79 | end) 80 | return ret 81 | end 82 | 83 | function table.copy(this) 84 | local tbs = table.new() 85 | local function cp(t) 86 | if type(t) ~= 'table' then return t end 87 | local ret = tbs[t] 88 | if ret then return ret end 89 | ret = table.new() 90 | tbs[t] = ret 91 | for k, v in pairs(t) do 92 | ret[k] = cp(v) 93 | end 94 | return ret 95 | end 96 | return cp(this) 97 | end 98 | 99 | function table.count(this, countKeyType, countValueType) 100 | local totalCount = 0 101 | local keyCount = 0 102 | local valueCount = 0 103 | local k,v = next(this) 104 | while k do 105 | totalCount = totalCount + 1 106 | if countKeyType and type(k) == countKeyType then 107 | keyCount = keyCount + 1 108 | end 109 | if countValueType and type(v) == countValueType then 110 | valueCount = valueCount + 1 111 | end 112 | k, v = next(this, k) 113 | end 114 | return totalCount, keyCount, valueCount 115 | end 116 | 117 | function table.is_array(this) 118 | return is_array(this) 119 | end 120 | 121 | function table.implode(this, separator) 122 | return table.concat(this, separator) 123 | end 124 | 125 | function table.explode(s, separator, maxCount) 126 | return string.explode(s, separator, maxCount) 127 | end 128 | 129 | function table.read_from_file(path) 130 | assert(is_string(path)) 131 | local c = files.read(path) 132 | if not c then return end 133 | return c:table() 134 | end 135 | 136 | function table.write_to_file(this, path) 137 | assert(is_table(this)) 138 | assert(is_string(path)) 139 | files.write(path, table.string(this)) 140 | end 141 | 142 | local function to_string(v) 143 | local t = type(v) 144 | if t == 'nil' or t == 'number' or t == 'string' then 145 | return tostring(v) 146 | end 147 | end 148 | 149 | local function convert_key(key, isEcho) 150 | local t = type(key) 151 | if t == 'number' then 152 | return isEcho and "(" .. tostring(key) .. ")" or "[" .. key .. "]" 153 | elseif t == 'string' then 154 | return isEcho and "[" .. key .. "]" or tostring(key) 155 | end 156 | end 157 | 158 | local function convert_value(value, isEcho) 159 | local t = type(value) 160 | if t == 'boolean' then 161 | return tostring(value) 162 | elseif t == 'number' then 163 | return "" .. value .. "" 164 | elseif t == 'string' then 165 | return "\"" .. value .. "\"" 166 | elseif not isEcho then 167 | return 168 | elseif t == 'function' then 169 | return "[" .. tostring(value) .. "]" 170 | elseif is_class(value) then 171 | return "[Class:" .. tostring(value) .. "]" 172 | elseif is_object(value) then 173 | return "[Object:" .. tostring(value) .. "]" 174 | else 175 | return tostring(value) 176 | end 177 | end 178 | 179 | function table.string(this, blank, keys, isEcho, withHidden, arrKeyless, _storey, _record) 180 | assert(is_table(this)) 181 | _storey = _storey or 1 182 | _record = _record or {} 183 | _record[this] = true 184 | local result = table.new() 185 | blank = blank or " " 186 | local function try_convert(k, v, ignoreKey) 187 | local valid = withHidden or not string.starts(k, "__") 188 | local key = convert_key(k, isEcho) 189 | local value = nil 190 | if is_table(v) then 191 | if _record[v] then 192 | value = tostring(v) 193 | else 194 | value = table.string(v, blank, keys, isEcho, withHidden, arrKeyless, _storey + 1, _record) 195 | end 196 | else 197 | value = convert_value(v, isEcho) 198 | end 199 | if valid and key and value then 200 | if ignoreKey then 201 | result:insert(blank:rep(_storey) .. value) 202 | else 203 | result:insert(blank:rep(_storey) .. key .. " = " .. value) 204 | end 205 | end 206 | end 207 | if table.is_array(this) then 208 | for i,v in ipairs(this) do try_convert(i, v, arrKeyless) end 209 | elseif keys then 210 | for i,k in ipairs(keys) do 211 | local v = this[k] 212 | if v then 213 | try_convert(k, v) 214 | end 215 | end 216 | else 217 | for k,v in pairs(this) do try_convert(k, v) end 218 | end 219 | local content = "" 220 | if #result > 0 then 221 | content = "\n" .. result:implode(",\n") .. ",\n" 222 | content = content .. blank:rep(_storey - 1) 223 | end 224 | return string.new("{" .. content .. "}") 225 | end 226 | 227 | function table.printable(this, blank, keys) 228 | return table.string(this, blank, keys, nil, true) 229 | end 230 | 231 | function table.encode(this) 232 | assert(is_table(this)) 233 | return this:string() 234 | end 235 | 236 | function table.decode(st) 237 | assert(is_string(st)) 238 | return st:table() 239 | end 240 | 241 | function table.print(this) 242 | print("[table:" .. lua_get_pointer(this) .. "](" .. table.printable(this, "| ") .. ")") 243 | end 244 | 245 | function table.is_empty(this) 246 | return next(this) == nil 247 | end 248 | 249 | function table.is_equal(this, that) 250 | assert(is_table(this)) 251 | if not is_table(that) then 252 | return false 253 | end 254 | for k, v in pairs(this) do 255 | if is_table(v) then 256 | if not table.is_equal(v, that[k]) then 257 | return false 258 | end 259 | else 260 | if v ~= that[k] then 261 | return false 262 | end 263 | end 264 | end 265 | for k, v in pairs(that) do 266 | if is_table(v) then 267 | if not table.is_equal(v, this[k]) then 268 | return false 269 | end 270 | else 271 | if v ~= this[k] then 272 | return false 273 | end 274 | end 275 | end 276 | return true 277 | end 278 | 279 | function table.is_same(this, that) 280 | assert(is_table(this)) 281 | if not is_table(that) then 282 | return false 283 | end 284 | for k, v in pairs(this) do 285 | if is_table(v) then 286 | if not table.is_same(v, that[k]) then 287 | return false 288 | end 289 | else 290 | if type(v) ~= type(that[k]) then 291 | return false 292 | end 293 | end 294 | end 295 | for k, v in pairs(that) do 296 | if is_table(v) then 297 | if not table.is_same(v, this[k]) then 298 | return false 299 | end 300 | else 301 | if type(v) ~= type(this[k]) then 302 | return false 303 | end 304 | end 305 | end 306 | return true 307 | end 308 | 309 | function table.find_value(this, value) 310 | local rKey, rVal = nil, nil 311 | table.foreach(this, function(k, v) 312 | if v == value then 313 | rKey, rVal = k, v 314 | return true 315 | end 316 | end) 317 | return rKey, rVal 318 | end 319 | 320 | function table.find_key(this, key) 321 | local rKey, rVal = nil, nil 322 | table.foreach(this, function(k, v) 323 | if k == key then 324 | rKey, rVal = k, v 325 | return true 326 | end 327 | end) 328 | return rKey, rVal 329 | end 330 | 331 | function table.find_if(this, func) 332 | local rKey, rVal = nil, nil 333 | table.foreach(this, function(k, v) 334 | if func(k, v) then 335 | rKey, rVal = k, v 336 | return true 337 | end 338 | end) 339 | return rKey, rVal 340 | end 341 | 342 | function table.reorder(this, isAsc, ...) 343 | local conditions = {...} 344 | if #conditions == 0 then 345 | return this 346 | end 347 | local condition = nil 348 | table.sort(this, function(t1, t2) 349 | for i = 1, #conditions do 350 | condition = conditions[i] 351 | if t1[condition] ~= t2[condition] then 352 | if isAsc then 353 | return t1[condition] < t2[condition] 354 | else 355 | return t1[condition] > t2[condition] 356 | end 357 | end 358 | end 359 | return nil 360 | end) 361 | return this 362 | end 363 | 364 | function table.foreach(this, func) 365 | if table.is_array(this) then 366 | for i,v in ipairs(this) do 367 | if func(i,v) then 368 | return 369 | end 370 | end 371 | else 372 | for k,v in pairs(this) do 373 | if func(k,v) then 374 | break 375 | end 376 | end 377 | end 378 | end 379 | 380 | function table.map(this, func) 381 | local ret = {} 382 | table.foreach(this, function(k, v) 383 | local r = func(k,v) 384 | if r then 385 | if is_array(this) then 386 | table.insert(ret, r) 387 | else 388 | ret[k] = r 389 | end 390 | end 391 | end) 392 | return ret 393 | end 394 | 395 | function table.reduce(this, func, accumulator) 396 | if not is_function(func) and is_function(accumulator) then 397 | local temp = func 398 | func = accumulator 399 | accumulator = temp 400 | end 401 | table.foreach(this, function(k, v) 402 | if not accumulator then 403 | if is_number(v) then 404 | accumulator = 0 405 | elseif is_string(v) then 406 | accumulator = "" 407 | elseif is_table(v) then 408 | accumulator = {} 409 | end 410 | end 411 | accumulator = func(accumulator, v) 412 | end) 413 | return accumulator 414 | end 415 | 416 | function table.reverse(this) 417 | local ret = {} 418 | for i = #this, 1, -1 do 419 | table.insert(ret, this[i]) 420 | end 421 | return ret 422 | end 423 | 424 | function table.append(this, other) 425 | for i,v in ipairs(other) do 426 | table.insert(this, v) 427 | end 428 | end 429 | 430 | function table.remove_value(this, value, count) 431 | local num = 0 432 | local key = table.find_value(this, value) 433 | while key and (not count or num < count) do 434 | table.remove_key(this, key) 435 | num = num + 1 436 | key = table.find_value(this, value) 437 | end 438 | end 439 | 440 | function table.remove_key(this, key) 441 | if is_array(this) then 442 | assert(is_number(key)) 443 | table.remove(this, key) 444 | else 445 | assert(is_number(key) or is_string(key)) 446 | this[key] = nil 447 | end 448 | end 449 | 450 | function table.remove_if(this, func) 451 | if is_array(this) then 452 | for i = #this, 1, -1 do 453 | if func(i, this[i]) then 454 | table.remove(this, i) 455 | end 456 | end 457 | else 458 | for k,v in pairs(this) do 459 | if func(k, this[k]) then 460 | this[k] = nil 461 | end 462 | end 463 | end 464 | end 465 | -------------------------------------------------------------------------------- /files/time.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Time 3 | ]] 4 | 5 | assert(Time == nil) 6 | Time = class("Time") 7 | 8 | local SECONDS_WEEK = 60 * 60 * 24 * 7 9 | local SECONDS_DAY = 60 * 60 * 24 10 | local SECONDS_HOUR = 60 * 60 11 | local SECONDS_MINUTE = 60 12 | local SECONDS_SECOND = 1 13 | 14 | function Time:__init__(time, zone) 15 | self._time = time or os.time() 16 | self._zone = zone 17 | if not self._zone then 18 | local now = os.time() 19 | local utc = os.time(os.date("!*t", now)) 20 | local diff = os.difftime(now, utc) 21 | self._zone = math.floor(diff / SECONDS_HOUR) 22 | end 23 | end 24 | 25 | function Time:getValue() 26 | return self._time 27 | end 28 | 29 | function Time:getDate(desc) 30 | return os.date(desc or "%Y-%m-%d_%H:%M:%S", self._time) 31 | end 32 | 33 | function Time:getTime() 34 | return self._time 35 | end 36 | 37 | function Time:setTime(time) 38 | assert(time ~= nil) 39 | self._time = time 40 | return self 41 | end 42 | 43 | function Time:getZone() 44 | return self._zone 45 | end 46 | 47 | function Time:setZone(zone) 48 | assert(zone ~= nil) 49 | self._time = self._time - self._zone * SECONDS_HOUR 50 | self._zone = zone 51 | self._time = self._time + self._zone * SECONDS_HOUR 52 | return self 53 | end 54 | 55 | function Time:getYear() 56 | return tonumber(os.date("%Y", self._time)) 57 | end 58 | 59 | function Time:getMonth() 60 | return tonumber(os.date("%m", self._time)) 61 | end 62 | 63 | function Time:nameMonth(isFull) 64 | return os.date(isFull and "%B" or "%b", self._time) 65 | end 66 | 67 | function Time:getDay() 68 | return tonumber(os.date("%d", self._time)) 69 | end 70 | 71 | function Time:getYMD() 72 | return self:getYear(), self:getMonth(), self:getDay() 73 | end 74 | 75 | function Time:getHour() 76 | return tonumber(os.date("%H", self._time)) 77 | end 78 | 79 | function Time:getMinute() 80 | return tonumber(os.date("%M", self._time)) 81 | end 82 | 83 | function Time:getSecond() 84 | return tonumber(os.date("%S", self._time)) 85 | end 86 | 87 | function Time:getHMS() 88 | return self:getHour(), self:getMinute(), self:getSecond() 89 | end 90 | 91 | function Time:getWeek() 92 | local w = tonumber(os.date("%w", self._time)) 93 | return w == 0 and 7 or w 94 | end 95 | 96 | function Time:nameWeek(isFull) 97 | return os.date(isFull and "%A" or "%a", self._time) 98 | end 99 | 100 | function Time:isAm() 101 | return self:getHour() < 12 102 | end 103 | 104 | function Time:isLeap() 105 | local year = self:getYear() 106 | if year % 4 == 0 and year % 100 ~= 0 then 107 | return true 108 | end 109 | if year % 400 == 0 then 110 | return true 111 | end 112 | return false 113 | end 114 | 115 | function Time:isSameWeek(time) 116 | return self:countWeek() == time:countWeek() 117 | end 118 | 119 | function Time:isSameDay(time) 120 | return self:countDay() == time:countDay() 121 | end 122 | 123 | function Time:isSameHour(time) 124 | return self:countHour() == time:countHour() 125 | end 126 | 127 | function Time:isSameMinute(time) 128 | return self:countMinute() == time:countMinute() 129 | end 130 | 131 | function Time:getYMDHMS() 132 | return self:getYear(), self:getMonth(), self:getDay(), self:getHour(), self:getMinute(), self:getSecond() 133 | end 134 | 135 | function Time:countWeek() 136 | local second = self._time % SECONDS_WEEK 137 | local hour = (self._time - second) / SECONDS_WEEK 138 | local time = Time(second) 139 | local result = {time:countDay()} 140 | table.insert(result, 1, hour) 141 | return unpack(result) 142 | end 143 | 144 | function Time:countDay() 145 | local second = self._time % SECONDS_DAY 146 | local hour = (self._time - second) / SECONDS_DAY 147 | local time = Time(second) 148 | local result = {time:countHour()} 149 | table.insert(result, 1, hour) 150 | return unpack(result) 151 | end 152 | 153 | function Time:countHour() 154 | local second = self._time % SECONDS_HOUR 155 | local hour = (self._time - second) / SECONDS_HOUR 156 | local time = Time(second) 157 | local result = {time:countMinute()} 158 | table.insert(result, 1, hour) 159 | return unpack(result) 160 | end 161 | 162 | function Time:countMinute() 163 | local second = self._time % SECONDS_MINUTE 164 | local minute = (self._time - second) / SECONDS_MINUTE 165 | return minute, second 166 | end 167 | 168 | function Time:addWeek(count) 169 | assert(count ~= nil) 170 | self._time = self._time + count * SECONDS_WEEK 171 | return self 172 | end 173 | 174 | function Time:addDay(count) 175 | assert(count ~= nil) 176 | self._time = self._time + count * SECONDS_DAY 177 | return self 178 | end 179 | 180 | function Time:addHour(count) 181 | assert(count ~= nil) 182 | self._time = self._time + count * SECONDS_HOUR 183 | return self 184 | end 185 | 186 | function Time:addMinute(count) 187 | assert(count ~= nil) 188 | self._time = self._time + count * SECONDS_MINUTE 189 | return self 190 | end 191 | 192 | function Time:addSecond(count) 193 | assert(count ~= nil) 194 | self._time = self._time + count * SECONDS_SECOND 195 | return self 196 | end 197 | 198 | function Time:diffTime(time) 199 | assert(time ~= nil) 200 | local distance = self:getValue() - time:getValue() 201 | return Time(math.abs(distance)), distance > 0 202 | end 203 | 204 | function Time:addTime(time) 205 | assert(time ~= nil) 206 | self._time = self._time + time:getValue() 207 | return self 208 | end 209 | -------------------------------------------------------------------------------- /files/timer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | timer 3 | ]] 4 | 5 | timer = timer or {} 6 | local timers = {} 7 | 8 | local function timer_insert(sec, action) 9 | local deadline = os.clock() + sec 10 | local pos = 1 11 | for i, v in ipairs(timers) do 12 | if v.deadline > deadline then 13 | break 14 | end 15 | pos = i + 1 16 | end 17 | local tm = { 18 | deadline = deadline, 19 | action = action 20 | } 21 | table.insert(timers, pos, tm) 22 | end 23 | 24 | local function timer_check() 25 | local tm = timers[1] 26 | if tm.deadline <= os.clock() then 27 | table.remove(timers, 1) 28 | local isOk, error = xpcall(tm.action, debug.traceback) 29 | if isOk then return end 30 | print(error) 31 | end 32 | end 33 | 34 | function timer.flag() 35 | return {} 36 | end 37 | 38 | function timer.finish(flag) 39 | flag.ok = true 40 | end 41 | 42 | function timer.running(flag) 43 | return flag.ok ~= true 44 | end 45 | 46 | function timer.async(func) 47 | local co = coroutine.create(func) 48 | coroutine.resume(co) 49 | end 50 | 51 | function timer.sleep(seconds) 52 | local co = coroutine.running() 53 | timer_insert(seconds, function() 54 | coroutine.resume(co) 55 | end) 56 | coroutine.yield() 57 | end 58 | 59 | function timer.wait(flag) 60 | while timer.running(flag) do 61 | timer.sleep(0.1) 62 | end 63 | end 64 | 65 | local function timer_delay(seconds, func, _flag) 66 | _flag = _flag or timer.flag() 67 | timer_insert(seconds, function() 68 | if not timer.running(_flag) then 69 | return 70 | end 71 | local isOk, s = xpcall(func, debug.traceback) 72 | if not isOk then 73 | print(s) 74 | return 75 | end 76 | if not s or s <= 0 then 77 | timer.finish(_flag) 78 | else 79 | timer_delay(s, func, _flag) 80 | end 81 | end) 82 | return _flag 83 | end 84 | 85 | function timer.delay(seconds, func) 86 | return timer_delay(seconds, func) 87 | end 88 | 89 | function timer.start() 90 | while #timers > 0 do 91 | timer_check() 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /files/tools.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | tools 3 | ]] 4 | 5 | tools = tools or {} 6 | 7 | local isWindows = nil 8 | function tools.is_windows() 9 | if is_boolean(isWindows) then return isWindows end 10 | isWindows = package.config:sub(1,1) == "\\" 11 | return isWindows 12 | end 13 | 14 | local isLinux = nil 15 | function tools.is_linux() 16 | if is_boolean(isLinux) then return isLinux end 17 | isLinux = not tools.is_windows() and string.find(os.getenv("HOME") or "", '/home/') ~= nil 18 | return isLinux 19 | end 20 | 21 | local isLinux = nil 22 | function tools.is_mac() 23 | if is_boolean(isLinux) then return isLinux end 24 | isLinux = not tools.is_windows() and string.find(os.getenv("HOME") or "", '/Users/') ~= nil 25 | return isLinux 26 | end 27 | 28 | function tools.execute(cmd) 29 | local flag = "::MY_ERROR_FLAG::" 30 | local file = io.popen(cmd .. [[ 2>&1 || echo ]] .. flag, "r") 31 | local out = file:read("*all"):trim() 32 | local isOk = not out:find(flag) 33 | if not isOk then 34 | out = out:sub(1, #out - #flag) 35 | end 36 | file:close() 37 | out = out:trim() 38 | return isOk, out 39 | end 40 | 41 | function tools.get_timezone() 42 | local now = os.time() 43 | local utc = os.time(os.date("!*t", now)) 44 | local diff = os.difftime(now, utc) 45 | local zone = math.floor(diff / 60 / 60) 46 | return zone 47 | end 48 | 49 | function tools.get_milliseconds() 50 | local clock = os.clock() 51 | local _, milli = math.modf(clock) 52 | return math.floor(os.time() * 1000 + milli * 1000) 53 | end 54 | 55 | function tools.where_is(program) 56 | local command = tools.is_windows() and "where" or "which" 57 | local isOk, result = tools.execute(command .. [[ "]] .. program .. [["]]) 58 | if isOk then 59 | local results = string.explode(result, "\n") 60 | return unpack(results) 61 | end 62 | end 63 | 64 | local editorNames = {'notepad', 'code'} 65 | function tools.edit_file(path) 66 | for i,editorName in ipairs(editorNames) do 67 | local isFound = tools.where_is(editorName) ~= nil 68 | if isFound then 69 | os.execute(editorName .. " " .. path) 70 | return true 71 | end 72 | end 73 | return false 74 | end 75 | 76 | function tools.open_url(url) 77 | return tools.execute([[start "]] .. url .. [["]]) 78 | end 79 | -------------------------------------------------------------------------------- /files/yaml.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | yaml 3 | ]] 4 | 5 | yaml = yaml or {} 6 | 7 | function yaml.convert(val) 8 | val = string.trim(val) 9 | -- 10 | local low = string.lower(val) 11 | if low == "null" or low == "~" then 12 | return null 13 | elseif low == "true" or low == "yes" or low == "on" then 14 | return true 15 | elseif low == "false" or low == "no" or low == "off" then 16 | return false 17 | end 18 | -- 19 | if (val:starts("'") and val:ends("'")) or (val:starts('"') and val:ends('"')) then 20 | return string.sub(val, 2, -2) 21 | end 22 | -- 23 | local num = tonumber(val) 24 | if num then 25 | return num 26 | end 27 | -- 28 | local time = {string.match(val, "^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt_ ](%d%d):(%d%d):(%d%d)([%+%-]%d%d?)$")} 29 | if table.is_empty(time) then 30 | time = {string.match(val, "^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt_ ](%d%d):(%d%d):(%d%d)([%+%-]%d%d?)$")} 31 | end 32 | if table.is_empty(time) then 33 | time = {string.match(val, "^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt_ ](%d%d):(%d%d):(%d%d)$")} 34 | end 35 | if table.is_empty(time) then 36 | time = {string.match(val, "^(%d%d%d%d)%-(%d%d)%-(%d%d)$")} 37 | end 38 | if not table.is_empty(time) then 39 | local y = tonumber(time[1]) 40 | local m = tonumber(time[2]) 41 | local d = tonumber(time[3]) 42 | assert(d ~= nil) 43 | local hou = tonumber(time[4]) or 0 44 | local min = tonumber(time[5]) or 0 45 | local sec = tonumber(time[6]) or 0 46 | local zone = tonumber(time[7]) or 0 47 | local t = os.time({year = y, month = m, day = d, hour = hou, min = min, sec = sec}) 48 | t = t - zone * 60 * 60 49 | return t 50 | end 51 | -- 52 | return val 53 | end 54 | 55 | function yaml.filter(txt) 56 | if txt:starts("'") or txt:starts('"') then 57 | -- quote appended comment 58 | local firstChar = txt:sub(1, 1) 59 | local secondIdx = txt:find(firstChar .. "[^" .. firstChar .. "]*#[^" .. firstChar .. "]*", 2, false) 60 | if secondIdx then 61 | txt = txt:sub(1, secondIdx) 62 | end 63 | else 64 | -- other appended comment 65 | local commentIdx = txt:find("%s*#.*", 1, false) 66 | if commentIdx then 67 | txt = txt:sub(1, commentIdx - 1) 68 | end 69 | end 70 | return txt 71 | end 72 | 73 | function yaml.decode(text) 74 | local spacing = 2 75 | local ready = false 76 | local indenting = 1 77 | local result = {} 78 | local stack = { result } 79 | local current = result 80 | local anchors = {} 81 | local index = 0 82 | local lines = {} 83 | for line in text:gmatch("[^\r\n]+") do 84 | table.insert(lines, line) 85 | end 86 | -- 87 | local function assertExt(bool, msg) 88 | assert(bool, msg .. " at line:" .. index .. ", content:[[" .. lines[index] .. "]]") 89 | end 90 | local function pushStack() 91 | current = {} 92 | table.insert(stack, current) 93 | indenting = indenting + 1 94 | return current 95 | end 96 | local function popStack() 97 | table.remove(stack) 98 | current = stack[#stack] 99 | indenting = indenting - 1 100 | return current 101 | end 102 | local function writeAnchor(name, content) 103 | assertExt(anchors[name] == nil, 'multiple yaml anchor') 104 | anchors[name] = content 105 | end 106 | local function readAnchor(name, tp) 107 | local content = anchors[name] 108 | assertExt(content ~= nil, 'invalid yaml anchor!') 109 | if tp then 110 | assertExt(type(content) == tp, 'invalid anchor type!') 111 | end 112 | return content 113 | end 114 | -- 115 | local function consume(index, line, t) 116 | line = string.trim(line) 117 | -- line comment 118 | if line:starts("#") then 119 | return 120 | end 121 | -- appended comment 122 | local frm = 1 123 | if line:starts("'") then 124 | print('-') 125 | frm = line:find("'", 2, true) 126 | elseif line:starts('"') then 127 | print('-') 128 | frm = line:find('"', 2, true) 129 | end 130 | -- 131 | if line:match("^-%s*%&%a%w+%s+[^%s]+$") then 132 | -- array anchor define 133 | local name, val = line:match("^-%s*%&(%a%w+)%s+([^%s]+)$") 134 | val = yaml.filter(val) 135 | local key = #t + 1 136 | t[key] = yaml.convert(val) 137 | writeAnchor(name, t[key]) 138 | elseif line:match("^-%s*%*%s*[^%s]+$") then 139 | -- array anchor use 140 | local name = line:match("^-%s*%*%s*([^%s]+)$") 141 | local key = #t + 1 142 | t[key] = readAnchor(name, nil) 143 | elseif line:match("^-%s*") then 144 | -- array normal process 145 | local val = line:sub(2) 146 | val = yaml.filter(val) 147 | local key = #t + 1 148 | t[key] = yaml.convert(val) 149 | elseif line:match("^%w+%s*:%s*%&%a%w+$") then 150 | -- map anchor define 151 | local key, name = line:match("^(%a%w*)%s*:%s*%&(%a%w*)$") 152 | name = yaml.filter(name) 153 | t[key] = pushStack() 154 | writeAnchor(name, t[key]) 155 | elseif line:match("^<<:%s*%*%a%w*$") then 156 | -- map anchor use 157 | local name = line:match("*(%a%w*)$") 158 | for k,v in pairs(readAnchor(name, 'table')) do 159 | t[k] = v 160 | end 161 | elseif line:match("%w+%s*:") then 162 | -- map normal process 163 | local key, val = line:match("^%s*(%w+)%s*:%s*(.*)$") 164 | val = yaml.filter(val) 165 | assertExt(string.valid(key), 'invalid yaml key') 166 | if not string.valid(val) then 167 | t[key] = pushStack() 168 | else 169 | t[key] = yaml.convert(val) 170 | end 171 | else 172 | assertExt(false, 'invalid yaml line!') 173 | end 174 | end 175 | -- 176 | for _,line in ipairs(lines) do 177 | index = index + 1 178 | local spaces = line:match("^(%s*)") 179 | local count = #spaces 180 | -- spacing 181 | if count > 0 and not ready then 182 | spacing = count 183 | ready = true 184 | end 185 | -- lines 186 | local indent = #spaces / spacing + 1 187 | if indent == indenting then 188 | consume(index, line, current) 189 | elseif indent < indenting then 190 | local num = indenting - indent 191 | assertExt(num < #stack, 'invalid yaml stack!') 192 | for i=1,num do popStack() end 193 | assertExt(current ~= nil, 'invalid yaml stack!') 194 | consume(index, line, current) 195 | else 196 | assertExt(false, 'invalid yaml indent!') 197 | end 198 | end 199 | return result 200 | end 201 | -------------------------------------------------------------------------------- /others/graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/graphic.png -------------------------------------------------------------------------------- /others/test.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test.ico -------------------------------------------------------------------------------- /others/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test.png -------------------------------------------------------------------------------- /others/test_bmp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_bmp.bmp -------------------------------------------------------------------------------- /others/test_canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_canvas.png -------------------------------------------------------------------------------- /others/test_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_dialog.png -------------------------------------------------------------------------------- /others/test_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_log.png -------------------------------------------------------------------------------- /others/test_object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_object.png -------------------------------------------------------------------------------- /others/test_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/test_qrcode.png -------------------------------------------------------------------------------- /others/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Atypicalim/pure-lua-tools/d8e22ac20fc3e21b4b18fe0f6c09c5ec6e6c198c/others/yellow.png -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | test 3 | ]] 4 | 5 | local file = debug.getinfo(function() end)['short_src'] 6 | local folder = string.gsub(file, "%a+%.%a+", "") 7 | package.path = package.path .. ";" .. folder .. "/?.lua;" 8 | 9 | local folder = "./files/" 10 | local orders = { 11 | "lua", 12 | "number", 13 | "math", 14 | "string", 15 | "table", 16 | "json", 17 | "yaml", 18 | "plist", 19 | "class", 20 | "Path", 21 | "files", 22 | "bit", 23 | "encryption", 24 | "time", 25 | "timer", 26 | "http", 27 | "package", 28 | "tools", 29 | "console", 30 | "shell", 31 | "Point", 32 | "Object", 33 | "Events", 34 | "Storage", 35 | "Log", 36 | "Graphic", 37 | "dialog", 38 | "canvas", 39 | "bmp", 40 | "colors", 41 | -- 42 | "libs/log30", 43 | "libs/deflate", 44 | "libs/stream", 45 | "libs/qrcode", 46 | "libs/png_decode", 47 | "libs/png_encode", 48 | 'library', 49 | } 50 | 51 | for i,v in ipairs(orders) do 52 | require(folder .. v) 53 | end 54 | 55 | local function build() 56 | local target = "./tools.lua" 57 | local content = string.format("\n-- tools:[%s]\n", os.date("%Y-%m-%d_%H:%M:%S", os.time())) 58 | print('pure-lua-tools:') 59 | print('building:') 60 | 61 | for i,name in ipairs(orders) do 62 | local path = string.format("%s%s.lua", folder, name) 63 | assert(files.is_file(path), 'file not found:' .. name) 64 | -- 65 | print('including:' .. path) 66 | content = content .. string.format("\n-- file:[%s]", path) .. "\n\n" 67 | local code = "" 68 | local skip = false 69 | files.read(path):explode("\n"):foreach(function(k, v) 70 | local text = string.trim(v) 71 | if #text == 0 then 72 | return 73 | elseif string.match(text, "^%s*%-%-%[%[.*") then 74 | skip = true 75 | return 76 | elseif skip and string.match(text, ".*%]%]%s*$") then 77 | skip = false 78 | return 79 | elseif skip then 80 | return 81 | elseif string.match(text, "^%s*%-%-.*") then 82 | return 83 | end 84 | code = code .. v .. "\n" 85 | end) 86 | content = content .. code 87 | end 88 | print('writing:' .. target) 89 | files.write(target, content) 90 | print('finished!') 91 | end 92 | -- build() 93 | -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | origin content ... --------------------------------------------------------------------------------