clone url: git://git.m455.casa/bbt
lume.lua
1 | -- |
2 | -- lume |
3 | -- |
4 | -- Copyright (c) 2020 rxi |
5 | -- |
6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of |
7 | -- this software and associated documentation files (the "Software"), to deal in |
8 | -- the Software without restriction, including without limitation the rights to |
9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
10 | -- of the Software, and to permit persons to whom the Software is furnished to do |
11 | -- so, subject to the following conditions: |
12 | -- |
13 | -- The above copyright notice and this permission notice shall be included in all |
14 | -- copies or substantial portions of the Software. |
15 | -- |
16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
22 | -- SOFTWARE. |
23 | -- |
24 |
|
25 | local lume = { _version = "2.3.0" } |
26 |
|
27 | local pairs, ipairs = pairs, ipairs |
28 | local type, assert, unpack = type, assert, unpack or table.unpack |
29 | local tostring, tonumber = tostring, tonumber |
30 | local math_floor = math.floor |
31 | local math_ceil = math.ceil |
32 | local math_atan2 = math.atan2 or math.atan |
33 | local math_sqrt = math.sqrt |
34 | local math_abs = math.abs |
35 |
|
36 | local noop = function() |
37 | end |
38 |
|
39 | local identity = function(x) |
40 | return x |
41 | end |
42 |
|
43 | local patternescape = function(str) |
44 | return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") |
45 | end |
46 |
|
47 | local absindex = function(len, i) |
48 | return i < 0 and (len + i + 1) or i |
49 | end |
50 |
|
51 | local iscallable = function(x) |
52 | if type(x) == "function" then return true end |
53 | local mt = getmetatable(x) |
54 | return mt and mt.__call ~= nil |
55 | end |
56 |
|
57 | local getiter = function(x) |
58 | if lume.isarray(x) then |
59 | return ipairs |
60 | elseif type(x) == "table" then |
61 | return pairs |
62 | end |
63 | error("expected table", 3) |
64 | end |
65 |
|
66 | local iteratee = function(x) |
67 | if x == nil then return identity end |
68 | if iscallable(x) then return x end |
69 | if type(x) == "table" then |
70 | return function(z) |
71 | for k, v in pairs(x) do |
72 | if z[k] ~= v then return false end |
73 | end |
74 | return true |
75 | end |
76 | end |
77 | return function(z) return z[x] end |
78 | end |
79 |
|
80 |
|
81 |
|
82 | function lume.clamp(x, min, max) |
83 | return x < min and min or (x > max and max or x) |
84 | end |
85 |
|
86 |
|
87 | function lume.round(x, increment) |
88 | if increment then return lume.round(x / increment) * increment end |
89 | return x >= 0 and math_floor(x + .5) or math_ceil(x - .5) |
90 | end |
91 |
|
92 |
|
93 | function lume.sign(x) |
94 | return x < 0 and -1 or 1 |
95 | end |
96 |
|
97 |
|
98 | function lume.lerp(a, b, amount) |
99 | return a + (b - a) * lume.clamp(amount, 0, 1) |
100 | end |
101 |
|
102 |
|
103 | function lume.smooth(a, b, amount) |
104 | local t = lume.clamp(amount, 0, 1) |
105 | local m = t * t * (3 - 2 * t) |
106 | return a + (b - a) * m |
107 | end |
108 |
|
109 |
|
110 | function lume.pingpong(x) |
111 | return 1 - math_abs(1 - x % 2) |
112 | end |
113 |
|
114 |
|
115 | function lume.distance(x1, y1, x2, y2, squared) |
116 | local dx = x1 - x2 |
117 | local dy = y1 - y2 |
118 | local s = dx * dx + dy * dy |
119 | return squared and s or math_sqrt(s) |
120 | end |
121 |
|
122 |
|
123 | function lume.angle(x1, y1, x2, y2) |
124 | return math_atan2(y2 - y1, x2 - x1) |
125 | end |
126 |
|
127 |
|
128 | function lume.vector(angle, magnitude) |
129 | return math.cos(angle) * magnitude, math.sin(angle) * magnitude |
130 | end |
131 |
|
132 |
|
133 | function lume.random(a, b) |
134 | if not a then a, b = 0, 1 end |
135 | if not b then b = 0 end |
136 | return a + math.random() * (b - a) |
137 | end |
138 |
|
139 |
|
140 | function lume.randomchoice(t) |
141 | return t[math.random(#t)] |
142 | end |
143 |
|
144 |
|
145 | function lume.weightedchoice(t) |
146 | local sum = 0 |
147 | for _, v in pairs(t) do |
148 | assert(v >= 0, "weight value less than zero") |
149 | sum = sum + v |
150 | end |
151 | assert(sum ~= 0, "all weights are zero") |
152 | local rnd = lume.random(sum) |
153 | for k, v in pairs(t) do |
154 | if rnd < v then return k end |
155 | rnd = rnd - v |
156 | end |
157 | end |
158 |
|
159 |
|
160 | function lume.isarray(x) |
161 | return type(x) == "table" and x[1] ~= nil |
162 | end |
163 |
|
164 |
|
165 | function lume.push(t, ...) |
166 | local n = select("#", ...) |
167 | for i = 1, n do |
168 | t[#t + 1] = select(i, ...) |
169 | end |
170 | return ... |
171 | end |
172 |
|
173 |
|
174 | function lume.remove(t, x) |
175 | local iter = getiter(t) |
176 | for i, v in iter(t) do |
177 | if v == x then |
178 | if lume.isarray(t) then |
179 | table.remove(t, i) |
180 | break |
181 | else |
182 | t[i] = nil |
183 | break |
184 | end |
185 | end |
186 | end |
187 | return x |
188 | end |
189 |
|
190 |
|
191 | function lume.clear(t) |
192 | local iter = getiter(t) |
193 | for k in iter(t) do |
194 | t[k] = nil |
195 | end |
196 | return t |
197 | end |
198 |
|
199 |
|
200 | function lume.extend(t, ...) |
201 | for i = 1, select("#", ...) do |
202 | local x = select(i, ...) |
203 | if x then |
204 | for k, v in pairs(x) do |
205 | t[k] = v |
206 | end |
207 | end |
208 | end |
209 | return t |
210 | end |
211 |
|
212 |
|
213 | function lume.shuffle(t) |
214 | local rtn = {} |
215 | for i = 1, #t do |
216 | local r = math.random(i) |
217 | if r ~= i then |
218 | rtn[i] = rtn[r] |
219 | end |
220 | rtn[r] = t[i] |
221 | end |
222 | return rtn |
223 | end |
224 |
|
225 |
|
226 | function lume.sort(t, comp) |
227 | local rtn = lume.clone(t) |
228 | if comp then |
229 | if type(comp) == "string" then |
230 | table.sort(rtn, function(a, b) return a[comp] < b[comp] end) |
231 | else |
232 | table.sort(rtn, comp) |
233 | end |
234 | else |
235 | table.sort(rtn) |
236 | end |
237 | return rtn |
238 | end |
239 |
|
240 |
|
241 | function lume.array(...) |
242 | local t = {} |
243 | for x in ... do t[#t + 1] = x end |
244 | return t |
245 | end |
246 |
|
247 |
|
248 | function lume.each(t, fn, ...) |
249 | local iter = getiter(t) |
250 | if type(fn) == "string" then |
251 | for _, v in iter(t) do v[fn](v, ...) end |
252 | else |
253 | for _, v in iter(t) do fn(v, ...) end |
254 | end |
255 | return t |
256 | end |
257 |
|
258 |
|
259 | function lume.map(t, fn) |
260 | fn = iteratee(fn) |
261 | local iter = getiter(t) |
262 | local rtn = {} |
263 | for k, v in iter(t) do rtn[k] = fn(v) end |
264 | return rtn |
265 | end |
266 |
|
267 |
|
268 | function lume.all(t, fn) |
269 | fn = iteratee(fn) |
270 | local iter = getiter(t) |
271 | for _, v in iter(t) do |
272 | if not fn(v) then return false end |
273 | end |
274 | return true |
275 | end |
276 |
|
277 |
|
278 | function lume.any(t, fn) |
279 | fn = iteratee(fn) |
280 | local iter = getiter(t) |
281 | for _, v in iter(t) do |
282 | if fn(v) then return true end |
283 | end |
284 | return false |
285 | end |
286 |
|
287 |
|
288 | function lume.reduce(t, fn, first) |
289 | local started = first ~= nil |
290 | local acc = first |
291 | local iter = getiter(t) |
292 | for _, v in iter(t) do |
293 | if started then |
294 | acc = fn(acc, v) |
295 | else |
296 | acc = v |
297 | started = true |
298 | end |
299 | end |
300 | assert(started, "reduce of an empty table with no first value") |
301 | return acc |
302 | end |
303 |
|
304 |
|
305 | function lume.unique(t) |
306 | local rtn = {} |
307 | for k in pairs(lume.invert(t)) do |
308 | rtn[#rtn + 1] = k |
309 | end |
310 | return rtn |
311 | end |
312 |
|
313 |
|
314 | function lume.filter(t, fn, retainkeys) |
315 | fn = iteratee(fn) |
316 | local iter = getiter(t) |
317 | local rtn = {} |
318 | if retainkeys then |
319 | for k, v in iter(t) do |
320 | if fn(v) then rtn[k] = v end |
321 | end |
322 | else |
323 | for _, v in iter(t) do |
324 | if fn(v) then rtn[#rtn + 1] = v end |
325 | end |
326 | end |
327 | return rtn |
328 | end |
329 |
|
330 |
|
331 | function lume.reject(t, fn, retainkeys) |
332 | fn = iteratee(fn) |
333 | local iter = getiter(t) |
334 | local rtn = {} |
335 | if retainkeys then |
336 | for k, v in iter(t) do |
337 | if not fn(v) then rtn[k] = v end |
338 | end |
339 | else |
340 | for _, v in iter(t) do |
341 | if not fn(v) then rtn[#rtn + 1] = v end |
342 | end |
343 | end |
344 | return rtn |
345 | end |
346 |
|
347 |
|
348 | function lume.merge(...) |
349 | local rtn = {} |
350 | for i = 1, select("#", ...) do |
351 | local t = select(i, ...) |
352 | local iter = getiter(t) |
353 | for k, v in iter(t) do |
354 | rtn[k] = v |
355 | end |
356 | end |
357 | return rtn |
358 | end |
359 |
|
360 |
|
361 | function lume.concat(...) |
362 | local rtn = {} |
363 | for i = 1, select("#", ...) do |
364 | local t = select(i, ...) |
365 | if t ~= nil then |
366 | local iter = getiter(t) |
367 | for _, v in iter(t) do |
368 | rtn[#rtn + 1] = v |
369 | end |
370 | end |
371 | end |
372 | return rtn |
373 | end |
374 |
|
375 |
|
376 | function lume.find(t, value) |
377 | local iter = getiter(t) |
378 | for k, v in iter(t) do |
379 | if v == value then return k end |
380 | end |
381 | return nil |
382 | end |
383 |
|
384 |
|
385 | function lume.match(t, fn) |
386 | fn = iteratee(fn) |
387 | local iter = getiter(t) |
388 | for k, v in iter(t) do |
389 | if fn(v) then return v, k end |
390 | end |
391 | return nil |
392 | end |
393 |
|
394 |
|
395 | function lume.count(t, fn) |
396 | local count = 0 |
397 | local iter = getiter(t) |
398 | if fn then |
399 | fn = iteratee(fn) |
400 | for _, v in iter(t) do |
401 | if fn(v) then count = count + 1 end |
402 | end |
403 | else |
404 | if lume.isarray(t) then |
405 | return #t |
406 | end |
407 | for _ in iter(t) do count = count + 1 end |
408 | end |
409 | return count |
410 | end |
411 |
|
412 |
|
413 | function lume.slice(t, i, j) |
414 | i = i and absindex(#t, i) or 1 |
415 | j = j and absindex(#t, j) or #t |
416 | local rtn = {} |
417 | for x = i < 1 and 1 or i, j > #t and #t or j do |
418 | rtn[#rtn + 1] = t[x] |
419 | end |
420 | return rtn |
421 | end |
422 |
|
423 |
|
424 | function lume.first(t, n) |
425 | if not n then return t[1] end |
426 | return lume.slice(t, 1, n) |
427 | end |
428 |
|
429 |
|
430 | function lume.last(t, n) |
431 | if not n then return t[#t] end |
432 | return lume.slice(t, -n, -1) |
433 | end |
434 |
|
435 |
|
436 | function lume.invert(t) |
437 | local rtn = {} |
438 | for k, v in pairs(t) do rtn[v] = k end |
439 | return rtn |
440 | end |
441 |
|
442 |
|
443 | function lume.pick(t, ...) |
444 | local rtn = {} |
445 | for i = 1, select("#", ...) do |
446 | local k = select(i, ...) |
447 | rtn[k] = t[k] |
448 | end |
449 | return rtn |
450 | end |
451 |
|
452 |
|
453 | function lume.keys(t) |
454 | local rtn = {} |
455 | local iter = getiter(t) |
456 | for k in iter(t) do rtn[#rtn + 1] = k end |
457 | return rtn |
458 | end |
459 |
|
460 |
|
461 | function lume.clone(t) |
462 | local rtn = {} |
463 | for k, v in pairs(t) do rtn[k] = v end |
464 | return rtn |
465 | end |
466 |
|
467 |
|
468 | function lume.fn(fn, ...) |
469 | assert(iscallable(fn), "expected a function as the first argument") |
470 | local args = { ... } |
471 | return function(...) |
472 | local a = lume.concat(args, { ... }) |
473 | return fn(unpack(a)) |
474 | end |
475 | end |
476 |
|
477 |
|
478 | function lume.once(fn, ...) |
479 | local f = lume.fn(fn, ...) |
480 | local done = false |
481 | return function(...) |
482 | if done then return end |
483 | done = true |
484 | return f(...) |
485 | end |
486 | end |
487 |
|
488 |
|
489 | local memoize_fnkey = {} |
490 | local memoize_nil = {} |
491 |
|
492 | function lume.memoize(fn) |
493 | local cache = {} |
494 | return function(...) |
495 | local c = cache |
496 | for i = 1, select("#", ...) do |
497 | local a = select(i, ...) or memoize_nil |
498 | c[a] = c[a] or {} |
499 | c = c[a] |
500 | end |
501 | c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)} |
502 | return unpack(c[memoize_fnkey]) |
503 | end |
504 | end |
505 |
|
506 |
|
507 | function lume.combine(...) |
508 | local n = select('#', ...) |
509 | if n == 0 then return noop end |
510 | if n == 1 then |
511 | local fn = select(1, ...) |
512 | if not fn then return noop end |
513 | assert(iscallable(fn), "expected a function or nil") |
514 | return fn |
515 | end |
516 | local funcs = {} |
517 | for i = 1, n do |
518 | local fn = select(i, ...) |
519 | if fn ~= nil then |
520 | assert(iscallable(fn), "expected a function or nil") |
521 | funcs[#funcs + 1] = fn |
522 | end |
523 | end |
524 | return function(...) |
525 | for _, f in ipairs(funcs) do f(...) end |
526 | end |
527 | end |
528 |
|
529 |
|
530 | function lume.call(fn, ...) |
531 | if fn then |
532 | return fn(...) |
533 | end |
534 | end |
535 |
|
536 |
|
537 | function lume.time(fn, ...) |
538 | local start = os.clock() |
539 | local rtn = {fn(...)} |
540 | return (os.clock() - start), unpack(rtn) |
541 | end |
542 |
|
543 |
|
544 | local lambda_cache = {} |
545 |
|
546 | function lume.lambda(str) |
547 | if not lambda_cache[str] then |
548 | local args, body = str:match([[^([%w,_ ]-)%->(.-)$]]) |
549 | assert(args and body, "bad string lambda") |
550 | local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend" |
551 | lambda_cache[str] = lume.dostring(s) |
552 | end |
553 | return lambda_cache[str] |
554 | end |
555 |
|
556 |
|
557 | local serialize |
558 |
|
559 | local serialize_map = { |
560 | [ "boolean" ] = tostring, |
561 | [ "nil" ] = tostring, |
562 | [ "string" ] = function(v) return string.format("%q", v) end, |
563 | [ "number" ] = function(v) |
564 | if v ~= v then return "0/0" -- nan |
565 | elseif v == 1 / 0 then return "1/0" -- inf |
566 | elseif v == -1 / 0 then return "-1/0" end -- -inf |
567 | return tostring(v) |
568 | end, |
569 | [ "table" ] = function(t, stk) |
570 | stk = stk or {} |
571 | if stk[t] then error("circular reference") end |
572 | local rtn = {} |
573 | stk[t] = true |
574 | for k, v in pairs(t) do |
575 | rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk) |
576 | end |
577 | stk[t] = nil |
578 | return "{" .. table.concat(rtn, ",") .. "}" |
579 | end |
580 | } |
581 |
|
582 | setmetatable(serialize_map, { |
583 | __index = function(_, k) error("unsupported serialize type: " .. k) end |
584 | }) |
585 |
|
586 | serialize = function(x, stk) |
587 | return serialize_map[type(x)](x, stk) |
588 | end |
589 |
|
590 | function lume.serialize(x) |
591 | return serialize(x) |
592 | end |
593 |
|
594 |
|
595 | function lume.deserialize(str) |
596 | return lume.dostring("return " .. str) |
597 | end |
598 |
|
599 |
|
600 | function lume.split(str, sep) |
601 | if not sep then |
602 | return lume.array(str:gmatch("([%S]+)")) |
603 | else |
604 | assert(sep ~= "", "empty separator") |
605 | local psep = patternescape(sep) |
606 | return lume.array((str..sep):gmatch("(.-)("..psep..")")) |
607 | end |
608 | end |
609 |
|
610 |
|
611 | function lume.trim(str, chars) |
612 | if not chars then return str:match("^[%s]*(.-)[%s]*$") end |
613 | chars = patternescape(chars) |
614 | return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$") |
615 | end |
616 |
|
617 |
|
618 | function lume.wordwrap(str, limit) |
619 | limit = limit or 72 |
620 | local check |
621 | if type(limit) == "number" then |
622 | check = function(s) return #s >= limit end |
623 | else |
624 | check = limit |
625 | end |
626 | local rtn = {} |
627 | local line = "" |
628 | for word, spaces in str:gmatch("(%S+)(%s*)") do |
629 | local s = line .. word |
630 | if check(s) then |
631 | table.insert(rtn, line .. "\n") |
632 | line = word |
633 | else |
634 | line = s |
635 | end |
636 | for c in spaces:gmatch(".") do |
637 | if c == "\n" then |
638 | table.insert(rtn, line .. "\n") |
639 | line = "" |
640 | else |
641 | line = line .. c |
642 | end |
643 | end |
644 | end |
645 | table.insert(rtn, line) |
646 | return table.concat(rtn) |
647 | end |
648 |
|
649 |
|
650 | function lume.format(str, vars) |
651 | if not vars then return str end |
652 | local f = function(x) |
653 | return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}") |
654 | end |
655 | return (str:gsub("{(.-)}", f)) |
656 | end |
657 |
|
658 |
|
659 | function lume.trace(...) |
660 | local info = debug.getinfo(2, "Sl") |
661 | local t = { info.short_src .. ":" .. info.currentline .. ":" } |
662 | for i = 1, select("#", ...) do |
663 | local x = select(i, ...) |
664 | if type(x) == "number" then |
665 | x = string.format("%g", lume.round(x, .01)) |
666 | end |
667 | t[#t + 1] = tostring(x) |
668 | end |
669 | print(table.concat(t, " ")) |
670 | end |
671 |
|
672 |
|
673 | function lume.dostring(str) |
674 | return assert((loadstring or load)(str))() |
675 | end |
676 |
|
677 |
|
678 | function lume.uuid() |
679 | local fn = function(x) |
680 | local r = math.random(16) - 1 |
681 | r = (x == "x") and (r + 1) or (r % 4) + 9 |
682 | return ("0123456789abcdef"):sub(r, r) |
683 | end |
684 | return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) |
685 | end |
686 |
|
687 |
|
688 | function lume.hotswap(modname) |
689 | local oldglobal = lume.clone(_G) |
690 | local updated = {} |
691 | local function update(old, new) |
692 | if updated[old] then return end |
693 | updated[old] = true |
694 | local oldmt, newmt = getmetatable(old), getmetatable(new) |
695 | if oldmt and newmt then update(oldmt, newmt) end |
696 | for k, v in pairs(new) do |
697 | if type(v) == "table" then update(old[k], v) else old[k] = v end |
698 | end |
699 | end |
700 | local err = nil |
701 | local function onerror(e) |
702 | for k in pairs(_G) do _G[k] = oldglobal[k] end |
703 | err = lume.trim(e) |
704 | end |
705 | local ok, oldmod = pcall(require, modname) |
706 | oldmod = ok and oldmod or nil |
707 | xpcall(function() |
708 | package.loaded[modname] = nil |
709 | local newmod = require(modname) |
710 | if type(oldmod) == "table" then update(oldmod, newmod) end |
711 | for k, v in pairs(oldglobal) do |
712 | if v ~= _G[k] and type(v) == "table" then |
713 | update(v, _G[k]) |
714 | _G[k] = v |
715 | end |
716 | end |
717 | end, onerror) |
718 | package.loaded[modname] = oldmod |
719 | if err then return nil, err end |
720 | return oldmod |
721 | end |
722 |
|
723 |
|
724 | local ripairs_iter = function(t, i) |
725 | i = i - 1 |
726 | local v = t[i] |
727 | if v ~= nil then |
728 | return i, v |
729 | end |
730 | end |
731 |
|
732 | function lume.ripairs(t) |
733 | return ripairs_iter, t, (#t + 1) |
734 | end |
735 |
|
736 |
|
737 | function lume.color(str, mul) |
738 | mul = mul or 1 |
739 | local r, g, b, a |
740 | r, g, b = str:match("#(%x%x)(%x%x)(%x%x)") |
741 | if r then |
742 | r = tonumber(r, 16) / 0xff |
743 | g = tonumber(g, 16) / 0xff |
744 | b = tonumber(b, 16) / 0xff |
745 | a = 1 |
746 | elseif str:match("rgba?%s*%([%d%s%.,]+%)") then |
747 | local f = str:gmatch("[%d.]+") |
748 | r = (f() or 0) / 0xff |
749 | g = (f() or 0) / 0xff |
750 | b = (f() or 0) / 0xff |
751 | a = f() or 1 |
752 | else |
753 | error(("bad color string '%s'"):format(str)) |
754 | end |
755 | return r * mul, g * mul, b * mul, a * mul |
756 | end |
757 |
|
758 |
|
759 | local chain_mt = {} |
760 | chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), |
761 | function(fn) |
762 | return function(self, ...) |
763 | self._value = fn(self._value, ...) |
764 | return self |
765 | end |
766 | end) |
767 | chain_mt.__index.result = function(x) return x._value end |
768 |
|
769 | function lume.chain(value) |
770 | return setmetatable({ _value = value }, chain_mt) |
771 | end |
772 |
|
773 | setmetatable(lume, { |
774 | __call = function(_, ...) |
775 | return lume.chain(...) |
776 | end |
777 | }) |
778 |
|
779 |
|
780 | return lume |