Reduce time windows a bit
[sispare.git] / sispare.myr
blob2d2a7c89fd18a4c4e0552049c0d5e2fb7b8db661
1 use std
3 use bio
4 use crypto
5 use date
6 use fileutil
7 use sys
9 use t
10 use termdraw
12 use "util"
14 const wait_days : (int,int)[:] = [
15         ( 0, 0),
16         ( 1, 1),
17         ( 1, 2),
18         ( 2, 3),
19         ( 3, 5),
20         ( 5, 8),
21         ( 8,13),
22         (13,21),
23         (21,34),
24         (34,55),
25 ][:]
27 const fg_plain   = `termdraw.RGB (0x20, 0x1b, 0x0c)
28 const fg_lighter = `termdraw.RGB (0x60, 0x5b, 0x5c)
29 const fg_bluer   = `termdraw.RGB (0x1b, 0x28, 0x70)
30 const bg_plain   = `termdraw.RGB (0xb2, 0xad, 0x99)
31 const bg_alt     = `termdraw.RGB (0xa4, 0x9d, 0x88)
32 const bg_alt2    = `termdraw.RGB (0x4e, 0x4a, 0x42)
34 const main = {args
35         var dir_arg : byte[:] = [][:]
36         var basedir : byte[:] = [][:]
37         var dir_checked = false
38         var cmd : std.optparsed = std.optparse(args, &[
39                 .minargs = 0,
40                 .maxargs = 0,
41                 .opts = [
42                         [ .opt = 'd', .arg = "dir", .desc = "base directory" ],
43                 ][:],
44         ])
46         for opt : cmd.opts
47                 match opt
48                 | ('d', dir): dir_arg = dir
49                 | _: std.fatal("impossible {}\n", opt)
50                 ;;
51         ;;
53         match get_basedir(dir_arg)
54         | `std.Err e: std.fatal("{}\n", e)
55         | `std.Ok b: basedir = b
56         ;;
58         auto (basedir : t.doomed_str)
59         do_session(basedir)
62 const do_session = {d : byte[:]
63         var sched_dir = std.fmt("{}/schedule", d)
64         var now : date.instant = date.utcnow()
65         var cards : byte[:][:] = [][:]
66         var files_to_scrub : byte[:][:] = [][:]
67         var npassed : int = 0
68         var nfailed : int = 0
70         auto (sched_dir : t.doomed_str)
72         /* Pick up the cards for today */
73         for f : fileutil.bywalk(sched_dir)
74                 var fbase = std.basename(f)
75                 var deal_with = false
76                 match date.parsefmt("%Y-%m-%d", fbase)
77                 | `std.Ok then:
78                         deal_with = (now.actual >= then.actual)
79                         std.slpush(&files_to_scrub, std.sldup(f))
80                 | `std.Err e: deal_with = true
81                 ;;
82                 auto (fbase : t.doomed_str)
84                 if deal_with
85                         match std.slurp(f)
86                         | `std.Err e:
87                                 std.fput(std.Err, "std.slurp(\"{}\"): {}\n", f, e)
88                         | `std.Ok lines:
89                                 var byline = std.strsplit(lines, "\n")
90                                 std.sljoin(&cards, byline)
91                                 std.slfree(byline)
92                         ;;
93                 ;;
94         ;;
96         /* sort according to a shuffled (but consistent) order */
97         var now_salt = std.fmt("{}", now.actual)
98         auto (now_salt : t.doomed_str)
99         std.sort(cards, { a : byte[:], b : byte[:]
100                 var ast : crypto.md5
101                 var bst : crypto.md5
102                 var ahash : byte[:] = std.slalloc(impl(crypto.Hashsz, crypto.md5))
103                 var bhash : byte[:] = std.slalloc(impl(crypto.Hashsz, crypto.md5))
104                 auto (ahash : t.doomed_str)
105                 auto (bhash : t.doomed_str)
107                 crypto.hinit(&ast)
108                 crypto.hadd(&ast, a)
109                 crypto.hadd(&ast, now_salt)
110                 crypto.hfin(&ast, ahash)
112                 crypto.hinit(&bst)
113                 crypto.hadd(&bst, b)
114                 crypto.hadd(&bst, now_salt)
115                 crypto.hfin(&bst, bhash)
117                 for var j = 0; j < ahash.len; ++j
118                         match std.numcmp(ahash[j], bhash[j])
119                         | `std.Equal:
120                         | x: -> x
121                         ;;
122                 ;;
124                 -> `std.Equal
125         })
127         /* uniq (and check validity) */
128         for var j = 1; j < cards.len; ++j
129                 if std.eq(cards[j], cards[j - 1])
130                         std.sldel(&cards, j)
131                         j--
132                 ;;
133         ;;
135         for var j = 0; j < cards.len; ++j
136                 if cards[j].len == 0
137                         std.sldel(&cards, j)
138                         j--
139                 ;;
140         ;;
142         for var j = 0; j < cards.len; ++j
143                 var dirpath = std.fmt("{}/cards/{}", d, cards[j])
144                 match std.diropen(dirpath)
145                 | `std.Ok d2:
146                         std.dirclose(d2)
147                 | `std.Err _:
148                         std.sldel(&cards, j)
149                         j--
150                 ;;
151                 std.slfree(dirpath)
152         ;;
154         if cards.len == 0
155                 std.put("All up-to-date; no review necessary.\n")
156                 -> void
157         ;;
159         match show_each_card(d, cards)
160         | `std.Err e: std.fatal("{}\n", e)
161         | `std.Ok res:
162                 /*
163                    Scrub all the cards we've need to schedule from every
164                    other schedule.
165                  */
166                 for f : files_to_scrub
167                         match std.slurp(f)
168                         | `std.Err e:
169                         | `std.Ok contents:
170                                 var per_line = std.strsplit(contents, "\n")
171                                 var j = 0
172                                 while j < per_line.len
173                                         var keep_this : bool = true
174                                         for (card, _) : res
175                                                 if std.eq(card, per_line[j])
176                                                         keep_this = false
177                                                 ;;
178                                         ;;
180                                         if !keep_this
181                                                 std.sldel(&per_line, j)
182                                         else
183                                                 j++
184                                         ;;
185                                 ;;
187                                 if per_line.len == 0
188                                         std.remove(f)
189                                 else
190                                         match std.open(f, std.Owrite | std.Ocreat | std.Otrunc)
191                                         | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", f, e)
192                                         | `std.Ok sfd:
193                                                 for line : per_line
194                                                         if line.len > 0
195                                                                 std.fput(sfd, "{}\n", line)
196                                                         ;;
197                                                 ;;
198                                                 std.close(sfd)
199                                         ;;
201                                 ;;
202                         ;;
203                 ;;
205                 for (card, passed) : res
206                         /* compute new level for this card */
207                         var levelpath = std.fmt("{}/cards/{}/level", d, card)
208                         var level = 1
209                         auto (levelpath : t.doomed_str)
211                         match std.slurp(levelpath)
212                         | `std.Err e:
213                         | `std.Ok levelstr:
214                                 var len = 0
215                                 while len <= levelstr.len && std.isdigit((levelstr[len] : char))
216                                         len++
217                                 ;;
218                                 levelstr = levelstr[:len]
220                                 match std.intparse(levelstr)
221                                 | `std.Some l: level = l
222                                 | `std.None:
223                                 ;;
224                         ;;
226                         if passed
227                                 npassed++
228                                 level++
229                         else
230                                 nfailed++
231                                 level = 1
232                         ;;
234                         /* write new level for this card */
235                         match std.open(levelpath, std.Owrite | std.Ocreat | std.Otrunc)
236                         | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", levelpath, e)
237                         | `std.Ok lfd:
238                                 std.fput(lfd, "{}\n", level)
239                                 std.close(lfd)
240                         ;;
242                         /* determine when this card will be next studied */
243                         if level >= wait_days.len
244                                 continue
245                         ;;
247                         var wait_pair = wait_days[std.max(0, level)]
248                         var when = date.addperiod(now, `date.Day std.rand(wait_pair.0, wait_pair.1 + 1))
249                         var sched_path = std.fmt("{}/schedule/{f=%Y-%m-%d}", d, when)
250                         auto (sched_path : t.doomed_str)
252                         match std.open(sched_path, std.Owrite | std.Ocreat | std.Oappend)
253                         | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", sched_path, e)
254                         | `std.Ok sfd:
255                                 std.fput(sfd, "{}\n", card)
256                                 std.close(sfd)
257                         ;;
258                 ;;
259         ;;
261         std.put("{w=3,p= } passed\n", npassed)
262         std.put("{w=3,p= } failed\n", nfailed)
264         for f : files_to_scrub
265                 std.slfree(f)
266         ;;
267         std.slfree(files_to_scrub)
270 const show_each_card : (d : byte[:], cards : byte[:][:] -> std.result((byte[:], bool)[:], byte[:])) = {d : byte[:], cards : byte[:][:]
271         var ret : (byte[:], bool)[:] = [][:]
272         var t : termdraw.term# = termdraw.mk(1)
273         var k : std.size = 0
274         var progress : byte[:] = [][:]
276         termdraw.raw(t)
278         while k < cards.len
279                 var c : byte[:] = cards[k]
280                 std.slfree(progress)
281                 progress = std.fmt("{} / {}", k + 1, cards.len)
282                 var sideapath : byte[:] = std.fmt("{}/cards/{}/side_A", d, c)
283                 var sidebpath : byte[:] = std.fmt("{}/cards/{}/side_B", d, c)
284                 var widtha : int = 0
285                 var widthb : int = 0
287                 var sideafull : byte[:] = [][:]
288                 var sidebfull : byte[:] = [][:]
289                 var sidea : byte[:][:] = [][:]
290                 var sideb : byte[:][:] = [][:]
291                 var dispside : byte[:][:] = [][:]
292                 var dispwidth : int = 0
293                 var still : bool = true
294                 var seen_side_b : bool = false
296                 match std.slurp(sideapath)
297                 | `std.Err e:
298                         std.sldel(&cards, k)
299                         goto done_with_this
300                 | `std.Ok b: sideafull = b
301                 ;;
303                 match std.slurp(sidebpath)
304                 | `std.Err e:
305                         std.sldel(&cards, k)
306                         goto done_with_this
307                 | `std.Ok b: sidebfull = b
308                 ;;
310                 sidea = std.strsplit(sideafull, "\n")
311                 for var j = 0; j < sidea.len; ++j
312                         widtha = std.max(widtha, (std.strcellwidth(sidea[j]) : int))
313                 ;;
315                 sideb = std.strsplit(sidebfull, "\n")
316                 for var j = 0; j < sideb.len; ++j
317                         widthb = std.max(widthb, (std.strcellwidth(sideb[j]) : int))
318                 ;;
320                 dispside = sidea
321                 dispwidth = widtha
322                 while still
323                         print_file(t, dispside, dispwidth)
324                         print_instructions(t, seen_side_b, k > 0)
325                         print_progress(t, progress)
326                         termdraw.flush(t)
327                         match termdraw.event(t)
328                         | `termdraw.Ctrl 'c':
329                                 termdraw.free(t)
330                                 std.fatal("")
331                         | `termdraw.Kc 'q' || `termdraw.Kc 'Q':
332                                 k = cards.len
333                                 still = false
334                         | `termdraw.Kc 'a' || `termdraw.Kc 'A':
335                                 dispside = sidea
336                                 dispwidth = widtha
337                         | `termdraw.Kc 'b' || `termdraw.Kc 'B':
338                                 dispside = sideb
339                                 dispwidth = widthb
340                                 seen_side_b = true
341                         | `termdraw.Kc 'y' || `termdraw.Kc 'Y':
342                                 if seen_side_b
343                                         std.slpush(&ret, (c, true))
344                                         still = false
345                                         k++
346                                 ;;
347                         | `termdraw.Kc 'n' || `termdraw.Kc 'N':
348                                 if seen_side_b
349                                         std.slpush(&ret, (c, false))
350                                         still = false
351                                         k++
352                                 ;;
353                         | `termdraw.Kc '<':
354                                 if k > 0
355                                         std.sldel(&ret, ret.len - 1)
356                                         k--
357                                         still = false
358                                 ;;
359                         | _:
360                         ;;
362                 ;;
364 :done_with_this
365                 std.slfree(sideafull)
366                 std.slfree(sidebfull)
367                 std.slfree(sidea)
368                 std.slfree(sideb)
369                 std.slfree(sideapath)
370                 std.slfree(sidebpath)
371         ;;
373         termdraw.free(t)
374         -> `std.Ok ret
377 const print_file = {t : termdraw.term#, lines : byte[:][:], lines_width : int
378         var start_y : int = 0
379         var start_x : int = 0
380         termdraw.setbg(t, bg_plain)
381         termdraw.setfg(t, fg_plain)
382         termdraw.clear(t, 0, 0, t.width, t.height)
384         if (lines_width < t.width) && (lines.len < t.height - 1)
385                 /* We have enough room to center it */
386                 start_y = (t.height - 1 - lines.len) / 2
387                 start_x = (t.width - lines_width) / 2
389                 for var j = 0; j < lines.len; ++j
390                         termdraw.move(t, start_x, start_y + j)
391                         termdraw.put(t, lines[j])
392                 ;;
393         else
394                 /* We don't. Lines/breaks/whatever. */
395                 var y = start_y
396                 for var j = 0; j < lines.len; ++j
397                         var immediate_dispatch : byte[:] = [][:]
398                         var to_print : byte[:] = lines[j]
400                         if to_print.len == 0
401                                 y++
402                         ;;
404                         while to_print.len > 0 && y < t.height - 1
405                                 (immediate_dispatch, to_print) = split_by_cell_width(to_print, (t.width : std.size))
406                                 termdraw.move(t, 0, y)
407                                 termdraw.put(t, immediate_dispatch)
408                                 y++
409                         ;;
411                         if y >= t.height - 1
412                                 break
413                         ;;
414                 ;;
415         ;;
418 const print_instructions = {t : termdraw.term#, seen_side_b : bool, can_go_back : bool
419         termdraw.setbg(t, bg_alt)
420         termdraw.clear(t, 0, t.height - 1, t.width, t.height)
421         termdraw.move(t, 0, t.height - 1)
423         show_instruction(t, "q", "quit early", true)
424         show_instruction(t, "a", "side A", true)
425         show_instruction(t, "b", "side B", true)
426         show_instruction(t, "y", "pass", seen_side_b)
427         show_instruction(t, "n", "fail", seen_side_b)
428         show_instruction(t, "<", "go back", can_go_back)
431 const show_instruction = {t : termdraw.term#, key : byte[:], desc : byte[:], visible
432         if visible
433                 termdraw.setfg(t, fg_bluer)
434         else
435                 termdraw.setfg(t, fg_lighter)
436         ;;
437         termdraw.put(t, "[{}]", key)
438         if visible
439                 termdraw.setfg(t, fg_plain)
440         else
441                 termdraw.setfg(t, fg_lighter)
442         ;;
443         termdraw.put(t, " {}   ", desc)
446 const print_progress = {t : termdraw.term#, s : byte[:]
447         termdraw.setbg(t, bg_alt)
448         termdraw.setfg(t, fg_plain)
449         termdraw.move(t, t.width - s.len - 1, t.height - 1)
450         termdraw.put(t, s)
453 const split_by_cell_width = {base : byte[:], width : std.size
454         var position : std.size = 0
455         var cur_width : std.size = 0
456         for (c, o) : std.bycharoff(base)
457                 cur_width += (std.cellwidth(c) : std.size)
458                 if cur_width > width
459                         -> (base[:position], base[position:])
460                 ;;
461                 position = o
462         ;;
464         -> (base[:], [][:])