clone url: git://git.m455.casa/fa
dists/fa.lua
1 | #!/usr/bin/lua5.3 |
2 | -- Authors: |
3 | -- - Jesse Laprade <jesselaprade@gmail.com> |
4 | -- - Will Sinatra <wpsinatra@gmail.com> |
5 | -- License: AGPLv3 |
6 | local lume = nil |
7 | package.preload["lume"] = package.preload["lume"] or function(...) |
8 | -- |
9 | -- lume |
10 | -- |
11 | -- Copyright (c) 2020 rxi |
12 | -- |
13 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of |
14 | -- this software and associated documentation files (the "Software"), to deal in |
15 | -- the Software without restriction, including without limitation the rights to |
16 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
17 | -- of the Software, and to permit persons to whom the Software is furnished to do |
18 | -- so, subject to the following conditions: |
19 | -- |
20 | -- The above copyright notice and this permission notice shall be included in all |
21 | -- copies or substantial portions of the Software. |
22 | -- |
23 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
24 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
25 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
26 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
27 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
28 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
29 | -- SOFTWARE. |
30 | -- |
31 | |
32 | local lume = { _version = "2.3.0" } |
33 | |
34 | local pairs, ipairs = pairs, ipairs |
35 | local type, assert, unpack = type, assert, unpack or table.unpack |
36 | local tostring, tonumber = tostring, tonumber |
37 | local math_floor = math.floor |
38 | local math_ceil = math.ceil |
39 | local math_atan2 = math.atan2 or math.atan |
40 | local math_sqrt = math.sqrt |
41 | local math_abs = math.abs |
42 | |
43 | local noop = function() |
44 | end |
45 | |
46 | local identity = function(x) |
47 | return x |
48 | end |
49 | |
50 | local patternescape = function(str) |
51 | return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") |
52 | end |
53 | |
54 | local absindex = function(len, i) |
55 | return i < 0 and (len + i + 1) or i |
56 | end |
57 | |
58 | local iscallable = function(x) |
59 | if type(x) == "function" then return true end |
60 | local mt = getmetatable(x) |
61 | return mt and mt.__call ~= nil |
62 | end |
63 | |
64 | local getiter = function(x) |
65 | if lume.isarray(x) then |
66 | return ipairs |
67 | elseif type(x) == "table" then |
68 | return pairs |
69 | end |
70 | error("expected table", 3) |
71 | end |
72 | |
73 | local iteratee = function(x) |
74 | if x == nil then return identity end |
75 | if iscallable(x) then return x end |
76 | if type(x) == "table" then |
77 | return function(z) |
78 | for k, v in pairs(x) do |
79 | if z[k] ~= v then return false end |
80 | end |
81 | return true |
82 | end |
83 | end |
84 | return function(z) return z[x] end |
85 | end |
86 | |
87 | |
88 | |
89 | function lume.clamp(x, min, max) |
90 | return x < min and min or (x > max and max or x) |
91 | end |
92 | |
93 | |
94 | function lume.round(x, increment) |
95 | if increment then return lume.round(x / increment) * increment end |
96 | return x >= 0 and math_floor(x + .5) or math_ceil(x - .5) |
97 | end |
98 | |
99 | |
100 | function lume.sign(x) |
101 | return x < 0 and -1 or 1 |
102 | end |
103 | |
104 | |
105 | function lume.lerp(a, b, amount) |
106 | return a + (b - a) * lume.clamp(amount, 0, 1) |
107 | end |
108 | |
109 | |
110 | function lume.smooth(a, b, amount) |
111 | local t = lume.clamp(amount, 0, 1) |
112 | local m = t * t * (3 - 2 * t) |
113 | return a + (b - a) * m |
114 | end |
115 | |
116 | |
117 | function lume.pingpong(x) |
118 | return 1 - math_abs(1 - x % 2) |
119 | end |
120 | |
121 | |
122 | function lume.distance(x1, y1, x2, y2, squared) |
123 | local dx = x1 - x2 |
124 | local dy = y1 - y2 |
125 | local s = dx * dx + dy * dy |
126 | return squared and s or math_sqrt(s) |
127 | end |
128 | |
129 | |
130 | function lume.angle(x1, y1, x2, y2) |
131 | return math_atan2(y2 - y1, x2 - x1) |
132 | end |
133 | |
134 | |
135 | function lume.vector(angle, magnitude) |
136 | return math.cos(angle) * magnitude, math.sin(angle) * magnitude |
137 | end |
138 | |
139 | |
140 | function lume.random(a, b) |
141 | if not a then a, b = 0, 1 end |
142 | if not b then b = 0 end |
143 | return a + math.random() * (b - a) |
144 | end |
145 | |
146 | |
147 | function lume.randomchoice(t) |
148 | return t[math.random(#t)] |
149 | end |
150 | |
151 | |
152 | function lume.weightedchoice(t) |
153 | local sum = 0 |
154 | for _, v in pairs(t) do |
155 | assert(v >= 0, "weight value less than zero") |
156 | sum = sum + v |
157 | end |
158 | assert(sum ~= 0, "all weights are zero") |
159 | local rnd = lume.random(sum) |
160 | for k, v in pairs(t) do |
161 | if rnd < v then return k end |
162 | rnd = rnd - v |
163 | end |
164 | end |
165 | |
166 | |
167 | function lume.isarray(x) |
168 | return type(x) == "table" and x[1] ~= nil |
169 | end |
170 | |
171 | |
172 | function lume.push(t, ...) |
173 | local n = select("#", ...) |
174 | for i = 1, n do |
175 | t[#t + 1] = select(i, ...) |
176 | end |
177 | return ... |
178 | end |
179 | |
180 | |
181 | function lume.remove(t, x) |
182 | local iter = getiter(t) |
183 | for i, v in iter(t) do |
184 | if v == x then |
185 | if lume.isarray(t) then |
186 | table.remove(t, i) |
187 | break |
188 | else |
189 | t[i] = nil |
190 | break |
191 | end |
192 | end |
193 | end |
194 | return x |
195 | end |
196 | |
197 | |
198 | function lume.clear(t) |
199 | local iter = getiter(t) |
200 | for k in iter(t) do |
201 | t[k] = nil |
202 | end |
203 | return t |
204 | end |
205 | |
206 | |
207 | function lume.extend(t, ...) |
208 | for i = 1, select("#", ...) do |
209 | local x = select(i, ...) |
210 | if x then |
211 | for k, v in pairs(x) do |
212 | t[k] = v |
213 | end |
214 | end |
215 | end |
216 | return t |
217 | end |
218 | |
219 | |
220 | function lume.shuffle(t) |
221 | local rtn = {} |
222 | for i = 1, #t do |
223 | local r = math.random(i) |
224 | if r ~= i then |
225 | rtn[i] = rtn[r] |
226 | end |
227 | rtn[r] = t[i] |
228 | end |
229 | return rtn |
230 | end |
231 | |
232 | |
233 | function lume.sort(t, comp) |
234 | local rtn = lume.clone(t) |
235 | if comp then |
236 | if type(comp) == "string" then |
237 | table.sort(rtn, function(a, b) return a[comp] < b[comp] end) |
238 | else |
239 | table.sort(rtn, comp) |
240 | end |
241 | else |
242 | table.sort(rtn) |
243 | end |
244 | return rtn |
245 | end |
246 | |
247 | |
248 | function lume.array(...) |
249 | local t = {} |
250 | for x in ... do t[#t + 1] = x end |
251 | return t |
252 | end |
253 | |
254 | |
255 | function lume.each(t, fn, ...) |
256 | local iter = getiter(t) |
257 | if type(fn) == "string" then |
258 | for _, v in iter(t) do v[fn](v, ...) end |
259 | else |
260 | for _, v in iter(t) do fn(v, ...) end |
261 | end |
262 | return t |
263 | end |
264 | |
265 | |
266 | function lume.map(t, fn) |
267 | fn = iteratee(fn) |
268 | local iter = getiter(t) |
269 | local rtn = {} |
270 | for k, v in iter(t) do rtn[k] = fn(v) end |
271 | return rtn |
272 | end |
273 | |
274 | |
275 | function lume.all(t, fn) |
276 | fn = iteratee(fn) |
277 | local iter = getiter(t) |
278 | for _, v in iter(t) do |
279 | if not fn(v) then return false end |
280 | end |
281 | return true |
282 | end |
283 | |
284 | |
285 | function lume.any(t, fn) |
286 | fn = iteratee(fn) |
287 | local iter = getiter(t) |
288 | for _, v in iter(t) do |
289 | if fn(v) then return true end |
290 | end |
291 | return false |
292 | end |
293 | |
294 | |
295 | function lume.reduce(t, fn, first) |
296 | local started = first ~= nil |
297 | local acc = first |
298 | local iter = getiter(t) |
299 | for _, v in iter(t) do |
300 | if started then |
301 | acc = fn(acc, v) |
302 | else |
303 | acc = v |
304 | started = true |
305 | end |
306 | end |
307 | assert(started, "reduce of an empty table with no first value") |
308 | return acc |
309 | end |
310 | |
311 | |
312 | function lume.unique(t) |
313 | local rtn = {} |
314 | for k in pairs(lume.invert(t)) do |
315 | rtn[#rtn + 1] = k |
316 | end |
317 | return rtn |
318 | end |
319 | |
320 | |
321 | function lume.filter(t, fn, retainkeys) |
322 | fn = iteratee(fn) |
323 | local iter = getiter(t) |
324 | local rtn = {} |
325 | if retainkeys then |
326 | for k, v in iter(t) do |
327 | if fn(v) then rtn[k] = v end |
328 | end |
329 | else |
330 | for _, v in iter(t) do |
331 | if fn(v) then rtn[#rtn + 1] = v end |
332 | end |
333 | end |
334 | return rtn |
335 | end |
336 | |
337 | |
338 | function lume.reject(t, fn, retainkeys) |
339 | fn = iteratee(fn) |
340 | local iter = getiter(t) |
341 | local rtn = {} |
342 | if retainkeys then |
343 | for k, v in iter(t) do |
344 | if not fn(v) then rtn[k] = v end |
345 | end |
346 | else |
347 | for _, v in iter(t) do |
348 | if not fn(v) then rtn[#rtn + 1] = v end |
349 | end |
350 | end |
351 | return rtn |
352 | end |
353 | |
354 | |
355 | function lume.merge(...) |
356 | local rtn = {} |
357 | for i = 1, select("#", ...) do |
358 | local t = select(i, ...) |
359 | local iter = getiter(t) |
360 | for k, v in iter(t) do |
361 | rtn[k] = v |
362 | end |
363 | end |
364 | return rtn |
365 | end |
366 | |
367 | |
368 | function lume.concat(...) |
369 | local rtn = {} |
370 | for i = 1, select("#", ...) do |
371 | local t = select(i, ...) |
372 | if t ~= nil then |
373 | local iter = getiter(t) |
374 | for _, v in iter(t) do |
375 | rtn[#rtn + 1] = v |
376 | end |
377 | end |
378 | end |
379 | return rtn |
380 | end |
381 | |
382 | |
383 | function lume.find(t, value) |
384 | local iter = getiter(t) |
385 | for k, v in iter(t) do |
386 | if v == value then return k end |
387 | end |
388 | return nil |
389 | end |
390 | |
391 | |
392 | function lume.match(t, fn) |
393 | fn = iteratee(fn) |
394 | local iter = getiter(t) |
395 | for k, v in iter(t) do |
396 | if fn(v) then return v, k end |
397 | end |
398 | return nil |
399 | end |
400 | |
401 | |
402 | function lume.count(t, fn) |
403 | local count = 0 |
404 | local iter = getiter(t) |
405 | if fn then |
406 | fn = iteratee(fn) |
407 | for _, v in iter(t) do |
408 | if fn(v) then count = count + 1 end |
409 | end |
410 | else |
411 | if lume.isarray(t) then |
412 | return #t |
413 | end |
414 | for _ in iter(t) do count = count + 1 end |
415 | end |
416 | return count |
417 | end |
418 | |
419 | |
420 | function lume.slice(t, i, j) |
421 | i = i and absindex(#t, i) or 1 |
422 | j = j and absindex(#t, j) or #t |
423 | local rtn = {} |
424 | for x = i < 1 and 1 or i, j > #t and #t or j do |
425 | rtn[#rtn + 1] = t[x] |
426 | end |
427 | return rtn |
428 | end |
429 | |
430 | |
431 | function lume.first(t, n) |
432 | if not n then return t[1] end |
433 | return lume.slice(t, 1, n) |
434 | end |
435 | |
436 | |
437 | function lume.last(t, n) |
438 | if not n then return t[#t] end |
439 | return lume.slice(t, -n, -1) |
440 | end |
441 | |
442 | |
443 | function lume.invert(t) |
444 | local rtn = {} |
445 | for k, v in pairs(t) do rtn[v] = k end |
446 | return rtn |
447 | end |
448 | |
449 | |
450 | function lume.pick(t, ...) |
451 | local rtn = {} |
452 | for i = 1, select("#", ...) do |
453 | local k = select(i, ...) |
454 | rtn[k] = t[k] |
455 | end |
456 | return rtn |
457 | end |
458 | |
459 | |
460 | function lume.keys(t) |
461 | local rtn = {} |
462 | local iter = getiter(t) |
463 | for k in iter(t) do rtn[#rtn + 1] = k end |
464 | return rtn |
465 | end |
466 | |
467 | |
468 | function lume.clone(t) |
469 | local rtn = {} |
470 | for k, v in pairs(t) do rtn[k] = v end |
471 | return rtn |
472 | end |
473 | |
474 | |
475 | function lume.fn(fn, ...) |
476 | assert(iscallable(fn), "expected a function as the first argument") |
477 | local args = { ... } |
478 | return function(...) |
479 | local a = lume.concat(args, { ... }) |
480 | return fn(unpack(a)) |
481 | end |
482 | end |
483 | |
484 | |
485 | function lume.once(fn, ...) |
486 | local f = lume.fn(fn, ...) |
487 | local done = false |
488 | return function(...) |
489 | if done then return end |
490 | done = true |
491 | return f(...) |
492 | end |
493 | end |
494 | |
495 | |
496 | local memoize_fnkey = {} |
497 | local memoize_nil = {} |
498 | |
499 | function lume.memoize(fn) |
500 | local cache = {} |
501 | return function(...) |
502 | local c = cache |
503 | for i = 1, select("#", ...) do |
504 | local a = select(i, ...) or memoize_nil |
505 | c[a] = c[a] or {} |
506 | c = c[a] |
507 | end |
508 | c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)} |
509 | return unpack(c[memoize_fnkey]) |
510 | end |
511 | end |
512 | |
513 | |
514 | function lume.combine(...) |
515 | local n = select('#', ...) |
516 | if n == 0 then return noop end |
517 | if n == 1 then |
518 | local fn = select(1, ...) |
519 | if not fn then return noop end |
520 | assert(iscallable(fn), "expected a function or nil") |
521 | return fn |
522 | end |
523 | local funcs = {} |
524 | for i = 1, n do |
525 | local fn = select(i, ...) |
526 | if fn ~= nil then |
527 | assert(iscallable(fn), "expected a function or nil") |
528 | funcs[#funcs + 1] = fn |
529 | end |
530 | end |
531 | return function(...) |
532 | for _, f in ipairs(funcs) do f(...) end |
533 | end |
534 | end |
535 | |
536 | |
537 | function lume.call(fn, ...) |
538 | if fn then |
539 | return fn(...) |
540 | end |
541 | end |
542 | |
543 | |
544 | function lume.time(fn, ...) |
545 | local start = os.clock() |
546 | local rtn = {fn(...)} |
547 | return (os.clock() - start), unpack(rtn) |
548 | end |
549 | |
550 | |
551 | local lambda_cache = {} |
552 | |
553 | function lume.lambda(str) |
554 | if not lambda_cache[str] then |
555 | local args, body = str:match([[^([%w,_ ]-)%->(.-)$]]) |
556 | assert(args and body, "bad string lambda") |
557 | local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend" |
558 | lambda_cache[str] = lume.dostring(s) |
559 | end |
560 | return lambda_cache[str] |
561 | end |
562 | |
563 | |
564 | local serialize |
565 | |
566 | local serialize_map = { |
567 | [ "boolean" ] = tostring, |
568 | [ "nil" ] = tostring, |
569 | [ "string" ] = function(v) return string.format("%q", v) end, |
570 | [ "number" ] = function(v) |
571 | if v ~= v then return "0/0" -- nan |
572 | elseif v == 1 / 0 then return "1/0" -- inf |
573 | elseif v == -1 / 0 then return "-1/0" end -- -inf |
574 | return tostring(v) |
575 | end, |
576 | [ "table" ] = function(t, stk) |
577 | stk = stk or {} |
578 | if stk[t] then error("circular reference") end |
579 | local rtn = {} |
580 | stk[t] = true |
581 | for k, v in pairs(t) do |
582 | rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk) |
583 | end |
584 | stk[t] = nil |
585 | return "{" .. table.concat(rtn, ",") .. "}" |
586 | end |
587 | } |
588 | |
589 | setmetatable(serialize_map, { |
590 | __index = function(_, k) error("unsupported serialize type: " .. k) end |
591 | }) |
592 | |
593 | serialize = function(x, stk) |
594 | return serialize_map[type(x)](x, stk) |
595 | end |
596 | |
597 | function lume.serialize(x) |
598 | return serialize(x) |
599 | end |
600 | |
601 | |
602 | function lume.deserialize(str) |
603 | return lume.dostring("return " .. str) |
604 | end |
605 | |
606 | |
607 | function lume.split(str, sep) |
608 | if not sep then |
609 | return lume.array(str:gmatch("([%S]+)")) |
610 | else |
611 | assert(sep ~= "", "empty separator") |
612 | local psep = patternescape(sep) |
613 | return lume.array((str..sep):gmatch("(.-)("..psep..")")) |
614 | end |
615 | end |
616 | |
617 | |
618 | function lume.trim(str, chars) |
619 | if not chars then return str:match("^[%s]*(.-)[%s]*$") end |
620 | chars = patternescape(chars) |
621 | return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$") |
622 | end |
623 | |
624 | |
625 | function lume.wordwrap(str, limit) |
626 | limit = limit or 72 |
627 | local check |
628 | if type(limit) == "number" then |
629 | check = function(s) return #s >= limit end |
630 | else |
631 | check = limit |
632 | end |
633 | local rtn = {} |
634 | local line = "" |
635 | for word, spaces in str:gmatch("(%S+)(%s*)") do |
636 | local s = line .. word |
637 | if check(s) then |
638 | table.insert(rtn, line .. "\n") |
639 | line = word |
640 | else |
641 | line = s |
642 | end |
643 | for c in spaces:gmatch(".") do |
644 | if c == "\n" then |
645 | table.insert(rtn, line .. "\n") |
646 | line = "" |
647 | else |
648 | line = line .. c |
649 | end |
650 | end |
651 | end |
652 | table.insert(rtn, line) |
653 | return table.concat(rtn) |
654 | end |
655 | |
656 | |
657 | function lume.format(str, vars) |
658 | if not vars then return str end |
659 | local f = function(x) |
660 | return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}") |
661 | end |
662 | return (str:gsub("{(.-)}", f)) |
663 | end |
664 | |
665 | |
666 | function lume.trace(...) |
667 | local info = debug.getinfo(2, "Sl") |
668 | local t = { info.short_src .. ":" .. info.currentline .. ":" } |
669 | for i = 1, select("#", ...) do |
670 | local x = select(i, ...) |
671 | if type(x) == "number" then |
672 | x = string.format("%g", lume.round(x, .01)) |
673 | end |
674 | t[#t + 1] = tostring(x) |
675 | end |
676 | print(table.concat(t, " ")) |
677 | end |
678 | |
679 | |
680 | function lume.dostring(str) |
681 | return assert((loadstring or load)(str))() |
682 | end |
683 | |
684 | |
685 | function lume.uuid() |
686 | local fn = function(x) |
687 | local r = math.random(16) - 1 |
688 | r = (x == "x") and (r + 1) or (r % 4) + 9 |
689 | return ("0123456789abcdef"):sub(r, r) |
690 | end |
691 | return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) |
692 | end |
693 | |
694 | |
695 | function lume.hotswap(modname) |
696 | local oldglobal = lume.clone(_G) |
697 | local updated = {} |
698 | local function update(old, new) |
699 | if updated[old] then return end |
700 | updated[old] = true |
701 | local oldmt, newmt = getmetatable(old), getmetatable(new) |
702 | if oldmt and newmt then update(oldmt, newmt) end |
703 | for k, v in pairs(new) do |
704 | if type(v) == "table" then update(old[k], v) else old[k] = v end |
705 | end |
706 | end |
707 | local err = nil |
708 | local function onerror(e) |
709 | for k in pairs(_G) do _G[k] = oldglobal[k] end |
710 | err = lume.trim(e) |
711 | end |
712 | local ok, oldmod = pcall(require, modname) |
713 | oldmod = ok and oldmod or nil |
714 | xpcall(function() |
715 | package.loaded[modname] = nil |
716 | local newmod = require(modname) |
717 | if type(oldmod) == "table" then update(oldmod, newmod) end |
718 | for k, v in pairs(oldglobal) do |
719 | if v ~= _G[k] and type(v) == "table" then |
720 | update(v, _G[k]) |
721 | _G[k] = v |
722 | end |
723 | end |
724 | end, onerror) |
725 | package.loaded[modname] = oldmod |
726 | if err then return nil, err end |
727 | return oldmod |
728 | end |
729 | |
730 | |
731 | local ripairs_iter = function(t, i) |
732 | i = i - 1 |
733 | local v = t[i] |
734 | if v ~= nil then |
735 | return i, v |
736 | end |
737 | end |
738 | |
739 | function lume.ripairs(t) |
740 | return ripairs_iter, t, (#t + 1) |
741 | end |
742 | |
743 | |
744 | function lume.color(str, mul) |
745 | mul = mul or 1 |
746 | local r, g, b, a |
747 | r, g, b = str:match("#(%x%x)(%x%x)(%x%x)") |
748 | if r then |
749 | r = tonumber(r, 16) / 0xff |
750 | g = tonumber(g, 16) / 0xff |
751 | b = tonumber(b, 16) / 0xff |
752 | a = 1 |
753 | elseif str:match("rgba?%s*%([%d%s%.,]+%)") then |
754 | local f = str:gmatch("[%d.]+") |
755 | r = (f() or 0) / 0xff |
756 | g = (f() or 0) / 0xff |
757 | b = (f() or 0) / 0xff |
758 | a = f() or 1 |
759 | else |
760 | error(("bad color string '%s'"):format(str)) |
761 | end |
762 | return r * mul, g * mul, b * mul, a * mul |
763 | end |
764 | |
765 | |
766 | local chain_mt = {} |
767 | chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), |
768 | function(fn) |
769 | return function(self, ...) |
770 | self._value = fn(self._value, ...) |
771 | return self |
772 | end |
773 | end) |
774 | chain_mt.__index.result = function(x) return x._value end |
775 | |
776 | function lume.chain(value) |
777 | return setmetatable({ _value = value }, chain_mt) |
778 | end |
779 | |
780 | setmetatable(lume, { |
781 | __call = function(_, ...) |
782 | return lume.chain(...) |
783 | end |
784 | }) |
785 | |
786 | |
787 | return lume |
788 | end |
789 | lume = require("lume") |
790 | local home = os.getenv("HOME") |
791 | local program_name = "fa" |
792 | local program_version = "0.5.0" |
793 | local program_file = (home .. "/." .. program_name) |
794 | local event_symbol = "!" |
795 | local keyword_today = "today" |
796 | local cmds_help = {human = "help", long = "--help", short = "-h"} |
797 | local cmds_version = {human = "version", long = "--version", short = "-v"} |
798 | local cmd_init = "init" |
799 | local cmd_add = "add" |
800 | local cmd_rm = "rm" |
801 | local cmd_ls = "ls" |
802 | local cmd_notify = "notify" |
803 | local newline = "\n" |
804 | local double_newline = "\n\n" |
805 | local space = " " |
806 | local indent_2 = " " |
807 | local messages = {["agenda-empty"] = "There is nothing in your agenda.", ["cancelling-date-creation"] = "Cancelling date creation", ["date-already-exists"] = ("The following event already exists in your agenda:" .. newline .. "%s %s" .. newline .. "Choose one of the options below:" .. newline .. "1. Replace the old event" .. newline .. "2. Add the event to the existing date" .. newline .. "3. Cancell even creation" .. newline .. "> "), ["date-format"] = ("Error: Date format must be mmdd" .. newline .. "Example: 1231 \"The last day of december\""), ["file-already-exists"] = string.format("A file or directory named '%s' already exists.", program_file), ["file-doesnt-exist"] = string.format("%s doesn't exist. Try running '%s %s' (without the quotation marks).", program_file, program_name, cmd_init), ["key-doesnt-exist"] = "The date '%s' wasn't found in your agenda.", ["not-an-option"] = "Error: '%s' is not an option.", ["program-version"] = string.format("AGPLv3 -- %s", program_version), ["sub-key-doesnt-exist"] = "The item '%s' on %s wasn't found in your agenda.", added = "Added '%s' to your agenda.", initialized = string.format("Successfully created %s", program_file), removed = "Removed '%s' from your agenda.", usage = string.format("For usage, type '%s %s'.", program_name, cmds_help.human)} |
808 | local function key_exists_3f(tbl, key) |
809 | if (type(tbl) == "table") then |
810 | local tbl_keys = lume.keys(tbl) |
811 | local function _0_(_241) |
812 | return (key == _241) |
813 | end |
814 | if lume.any(tbl_keys, _0_) then |
815 | return true |
816 | else |
817 | return false |
818 | end |
819 | else |
820 | return false |
821 | end |
822 | end |
823 | local function get_date() |
824 | return os.date("%m%d") |
825 | end |
826 | local function file_exists_3f(str) |
827 | local file_in = io.open(str, "r") |
828 | if file_in then |
829 | return file_in:close() |
830 | else |
831 | return false |
832 | end |
833 | end |
834 | local function file__3etable(str) |
835 | if file_exists_3f(str) then |
836 | local file_in = io.open(str, "r") |
837 | local function close_handlers_0_(ok_0_, ...) |
838 | file_in:close() |
839 | if ok_0_ then |
840 | return ... |
841 | else |
842 | return error(..., 0) |
843 | end |
844 | end |
845 | local function _0_() |
846 | return lume.deserialize(file_in:read("*all")) |
847 | end |
848 | return close_handlers_0_(xpcall(_0_, (package.loaded.fennel or debug).traceback)) |
849 | else |
850 | return nil |
851 | end |
852 | end |
853 | local function table__3efile(str, tbl) |
854 | if file_exists_3f(str) then |
855 | local file_out = io.open(str, "w") |
856 | local function close_handlers_0_(ok_0_, ...) |
857 | file_out:close() |
858 | if ok_0_ then |
859 | return ... |
860 | else |
861 | return error(..., 0) |
862 | end |
863 | end |
864 | local function _0_() |
865 | return file_out:write(lume.serialize(tbl)) |
866 | end |
867 | return close_handlers_0_(xpcall(_0_, (package.loaded.fennel or debug).traceback)) |
868 | else |
869 | return nil |
870 | end |
871 | end |
872 | local function create_prefix(seq, key) |
873 | if (#seq > 1) then |
874 | return string.format("%s. ", key) |
875 | else |
876 | return "" |
877 | end |
878 | end |
879 | local function print_format(str, ...) |
880 | return print(string.format(str, ...)) |
881 | end |
882 | local function init_2fcreate_file() |
883 | local file_out = io.open(program_file, "w") |
884 | local function close_handlers_0_(ok_0_, ...) |
885 | file_out:close() |
886 | if ok_0_ then |
887 | return ... |
888 | else |
889 | return error(..., 0) |
890 | end |
891 | end |
892 | local function _0_() |
893 | file_out:write(lume.serialize({})) |
894 | return print(messages.initialized) |
895 | end |
896 | return close_handlers_0_(xpcall(_0_, (package.loaded.fennel or debug).traceback)) |
897 | end |
898 | local function init() |
899 | if file_exists_3f(program_file) then |
900 | return print(messages["file-already-exists"]) |
901 | else |
902 | return init_2fcreate_file() |
903 | end |
904 | end |
905 | local function add_2fadd_event(date, event_str) |
906 | local tbl = file__3etable(program_file) |
907 | if key_exists_3f(tbl, date) then |
908 | tbl[date][(1 + #tbl[date])] = event_str |
909 | else |
910 | tbl[date] = {event_str} |
911 | end |
912 | table__3efile(program_file, tbl) |
913 | return print_format(messages.added, event_str) |
914 | end |
915 | local function add(date_str, event_str) |
916 | if file_exists_3f(program_file) then |
917 | local date = nil |
918 | if (date_str == keyword_today) then |
919 | date = get_date() |
920 | else |
921 | date = date_str |
922 | end |
923 | return add_2fadd_event(date, event_str) |
924 | else |
925 | return print(messages["file-doesnt-exist"]) |
926 | end |
927 | end |
928 | local function rm(date_str, event_str) |
929 | if file_exists_3f(program_file) then |
930 | local tbl = file__3etable(program_file) |
931 | local date = nil |
932 | if (date_str == keyword_today) then |
933 | date = get_date() |
934 | else |
935 | date = date_str |
936 | end |
937 | local events_seq = tbl[date] |
938 | local events_seq_index = tonumber(event_str) |
939 | local date_exists_3f = key_exists_3f(tbl, date) |
940 | local event_exists_3f = key_exists_3f(events_seq, events_seq_index) |
941 | local _1_0 = {date_exists_3f, event_exists_3f, event_str} |
942 | if ((type(_1_0) == "table") and (_1_0[1] == true) and (_1_0[2] == false) and (_1_0[3] == nil)) then |
943 | tbl[date] = nil |
944 | table__3efile(program_file, tbl) |
945 | return print_format(messages.removed, date) |
946 | elseif ((type(_1_0) == "table") and (_1_0[1] == true) and (_1_0[2] == true)) then |
947 | local event = events_seq[events_seq_index] |
948 | table.remove(events_seq, events_seq_index) |
949 | if (#events_seq == 0) then |
950 | tbl[date] = nil |
951 | table__3efile(program_file, tbl) |
952 | return print_format(messages.removed, event) |
953 | else |
954 | tbl[date] = events_seq |
955 | table__3efile(program_file, tbl) |
956 | return print_format(messages.removed, event) |
957 | end |
958 | elseif ((type(_1_0) == "table") and (_1_0[1] == true) and (_1_0[2] == false)) then |
959 | return print_format(messages["sub-key-doesnt-exist"], event_str, date) |
960 | elseif ((type(_1_0) == "table") and (_1_0[1] == false)) then |
961 | return print_format(messages["key-doesnt-exist"], date) |
962 | end |
963 | else |
964 | return print(messages["file-doesnt-exist"]) |
965 | end |
966 | end |
967 | local function notify() |
968 | if file_exists_3f(program_file) then |
969 | if key_exists_3f(file__3etable(program_file), get_date()) then |
970 | return io.write(event_symbol) |
971 | end |
972 | else |
973 | return print(messages["file-doesnt-exist"]) |
974 | end |
975 | end |
976 | local function ls_2fprint_seq(events_seq) |
977 | local keys = lume.keys(events_seq) |
978 | for _, key in pairs(keys) do |
979 | local event = events_seq[key] |
980 | local event_prefix = create_prefix(events_seq, key) |
981 | print_format("%s%s", event_prefix, event) |
982 | end |
983 | return nil |
984 | end |
985 | local function ls_2fls_date(date_str) |
986 | if file_exists_3f(program_file) then |
987 | local tbl = file__3etable(program_file) |
988 | local date = nil |
989 | if (date_str == keyword_today) then |
990 | date = get_date() |
991 | else |
992 | date = date_str |
993 | end |
994 | if key_exists_3f(tbl, date) then |
995 | local events_seq = tbl[date] |
996 | return ls_2fprint_seq(events_seq) |
997 | end |
998 | else |
999 | return print(messages["file-doesnt-exist"]) |
1000 | end |
1001 | end |
1002 | local function ls_2fprint_seq0(events_seq, date) |
1003 | local _0_ = events_seq |
1004 | local first_event = _0_[1] |
1005 | local _1_ = lume.keys(events_seq) |
1006 | local first_key = _1_[1] |
1007 | local rest_keys = {(table.unpack or unpack)(_1_, 2)} |
1008 | local first_event_prefix = create_prefix(events_seq, first_key) |
1009 | print_format("%s%s", first_event_prefix, first_event) |
1010 | for _, key in pairs(rest_keys) do |
1011 | local event = events_seq[key] |
1012 | local padding = string.rep(" ", (1 + #date)) |
1013 | print_format("%s %s. %s", padding, key, event) |
1014 | end |
1015 | return nil |
1016 | end |
1017 | local function ls_2fsort_and_print_tbl(tbl, tbl_keys) |
1018 | table.sort(tbl_keys) |
1019 | for _, date in pairs(tbl_keys) do |
1020 | io.write(string.format("%s: ", date)) |
1021 | local events_seq = tbl[date] |
1022 | ls_2fprint_seq0(events_seq, date) |
1023 | end |
1024 | return nil |
1025 | end |
1026 | local function ls(date_str) |
1027 | if file_exists_3f(program_file) then |
1028 | if date_str then |
1029 | return ls_2fls_date(date_str) |
1030 | else |
1031 | local tbl = file__3etable(program_file) |
1032 | local tbl_keys = lume.keys(tbl) |
1033 | local tbl_length = #tbl_keys |
1034 | if (tbl_length > 0) then |
1035 | return ls_2fsort_and_print_tbl(tbl, tbl_keys) |
1036 | else |
1037 | return print(messages["agenda-empty"]) |
1038 | end |
1039 | end |
1040 | else |
1041 | return print(messages["file-doesnt-exist"]) |
1042 | end |
1043 | end |
1044 | local function help() |
1045 | return print(("Usage:\n" .. " fa <command> [<arg>] [<arg>]\n" .. "\n" .. "Commands:\n" .. " init - Creates your agenda.\n" .. " add <mmdd|today> \"Quoted text\" - Adds an event to your agenda.\n" .. " rm <mmdd|today> - Removes a given date from your agenda.\n" .. " rm <mmdd|today> [<number>] - Removes an event from given date from your agenda.\n" .. " ls [<mmdd|today>] - Lists the events on the given date.\n" .. " ls - Lists all dates and their events.\n" .. " notify - Displays a \"!\" if an event exists today.\n" .. " version - Prints the current version of fa.\n" .. "\n" .. "Examples:\n" .. " fa init\n" .. " fa add 1231 \"Sherry's birthday\"\n" .. " fa add today \"Sherry's birthday\"\n" .. " fa rm 1231\n" .. " fa rm 1231 3 (See note below)\n" .. " fa rm today\n" .. " fa rm today 3 (See note below)\n" .. " fa ls today\n" .. " fa ls 1231\n" .. " fa ls\n" .. " fa notify\n" .. " fa version\n" .. "\n" .. "Note: You may need to run 'fa ls' to see which number correlates to which event.\n")) |
1046 | end |
1047 | local function process_args(arg_tbl) |
1048 | local _0_0, _1_0, _2_0 = arg_tbl |
1049 | if ((type(_0_0) == "table") and (_0_0[1] == cmd_add) and (nil ~= _0_0[2]) and (nil ~= _0_0[3]) and (_0_0[4] == nil)) then |
1050 | local date_str = _0_0[2] |
1051 | local event_str = _0_0[3] |
1052 | return add(date_str, event_str) |
1053 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_rm) and (nil ~= _0_0[2]) and (nil ~= _0_0[3]) and (_0_0[4] == nil)) then |
1054 | local date_str = _0_0[2] |
1055 | local event_str = _0_0[3] |
1056 | return rm(date_str, event_str) |
1057 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_rm) and (nil ~= _0_0[2]) and (_0_0[3] == nil)) then |
1058 | local date_str = _0_0[2] |
1059 | return rm(date_str) |
1060 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_ls) and (nil ~= _0_0[2]) and (_0_0[3] == nil)) then |
1061 | local date_str = _0_0[2] |
1062 | return ls(date_str) |
1063 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_ls) and (_0_0[2] == nil)) then |
1064 | return ls() |
1065 | else |
1066 | local _3_ |
1067 | do |
1068 | local cmd = _0_0[1] |
1069 | _3_ = (((type(_0_0) == "table") and (nil ~= _0_0[1]) and (_0_0[2] == nil)) and ((cmd == cmds_help.human) or (cmd == cmds_help.long) or (cmd == cmds_help.short))) |
1070 | end |
1071 | if _3_ then |
1072 | local cmd = _0_0[1] |
1073 | return help() |
1074 | else |
1075 | local _4_ |
1076 | do |
1077 | local cmd = _0_0[1] |
1078 | _4_ = (((type(_0_0) == "table") and (nil ~= _0_0[1]) and (_0_0[2] == nil)) and ((cmd == cmds_version.human) or (cmd == cmds_version.long) or (cmd == cmds_version.short))) |
1079 | end |
1080 | if _4_ then |
1081 | local cmd = _0_0[1] |
1082 | return print(messages["program-version"]) |
1083 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_init) and (_0_0[2] == nil)) then |
1084 | return init() |
1085 | elseif ((type(_0_0) == "table") and (_0_0[1] == cmd_notify) and (_0_0[2] == nil)) then |
1086 | return notify() |
1087 | else |
1088 | local _ = _0_0 |
1089 | return print(messages.usage) |
1090 | end |
1091 | end |
1092 | end |
1093 | end |
1094 | local function main(arg_tbl) |
1095 | return process_args(arg_tbl) |
1096 | end |
1097 | return main(arg) |