13 const wait_days : int[:] = [ 0, 1, 2, 3, 5, 8, 13, 21, 34, 55 ][:]
15 const fg_plain = `termdraw.RGB (0x20, 0x1b, 0x0c)
16 const fg_lighter = `termdraw.RGB (0x60, 0x5b, 0x5c)
17 const fg_bluer = `termdraw.RGB (0x1b, 0x28, 0x70)
18 const bg_plain = `termdraw.RGB (0xb2, 0xad, 0x99)
19 const bg_alt = `termdraw.RGB (0xa4, 0x9d, 0x88)
20 const bg_alt2 = `termdraw.RGB (0x4e, 0x4a, 0x42)
23 var dir_arg : byte[:] = [][:]
24 var basedir : byte[:] = [][:]
25 var dir_checked = false
26 var cmd : std.optparsed = std.optparse(args, &[
30 [ .opt = 'd', .arg = "dir", .desc = "base directory" ],
36 | ('d', dir): dir_arg = dir
37 | _: std.fatal("impossible {}\n", opt)
41 match get_basedir(dir_arg)
42 | `std.Err e: std.fatal("{}\n", e)
43 | `std.Ok b: basedir = b
46 auto (basedir : t.doomed_str)
50 const do_session = {d : byte[:]
51 var sched_dir = std.fmt("{}/schedule", d)
52 var now : date.instant = date.utcnow()
53 var cards : byte[:][:] = [][:]
54 var files_to_rm : byte[:][:] = [][:]
56 auto (sched_dir : t.doomed_str)
58 /* Pick up the cards for today */
59 for f : fileutil.bywalk(sched_dir)
60 var fbase = std.basename(f)
62 match date.parsefmt("%Y-%m-%d", fbase)
63 | `std.Ok then: deal_with = (now.actual >= then.actual)
64 | `std.Err e: deal_with = true
66 auto (fbase : t.doomed_str)
71 std.fput(std.Err, "std.slurp(\"{}\"): {}\n", f, e)
73 var byline = std.strsplit(lines, "\n")
74 std.sljoin(&cards, byline)
76 std.slpush(&files_to_rm, std.sldup(f))
81 /* sort according to a shuffled (but consistent) order */
82 var nowhash = std.hash(now.actual)
83 std.sort(cards, { a : byte[:], b : byte[:]
84 var ahash = std.hash(a) ^ nowhash
85 var bhash = std.hash(b) ^ nowhash
86 -> std.numcmp(ahash, bhash)})
88 /* uniq (and check validity) */
89 for var j = 1; j < cards.len; ++j
90 if std.eq(cards[j], cards[j - 1])
96 for var j = 0; j < cards.len; ++j
103 for var j = 0; j < cards.len; ++j
104 var dirpath = std.fmt("{}/cards/{}", d, cards[j])
105 match std.diropen(dirpath)
116 std.put("All up-to-date; no review necessary.\n")
119 match show_each_card(d, cards)
120 | `std.Err e: std.fatal("{}\n", e)
122 for (card, passed) : res
123 /* compute new level for this card */
124 var levelpath = std.fmt("{}/cards/{}/level", d, card)
126 auto (levelpath : t.doomed_str)
128 match std.slurp(levelpath)
131 match std.intparse(levelstr)
132 | `std.Some l: level = l
143 /* write new level for this card */
144 match std.open(levelpath, std.Owrite | std.Ocreat | std.Otrunc)
145 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", levelpath, e)
147 std.fput(lfd, "{}\n", level)
151 /* determine when this card will be next studied */
152 if level >= wait_days.len
156 var when = date.addperiod(now, `date.Day wait_days[std.max(0, level)])
157 var sched_path = std.fmt("{}/schedule/{f=%Y-%m-%d}", d, when)
158 auto (sched_path : t.doomed_str)
160 match std.open(sched_path, std.Owrite | std.Ocreat | std.Oappend)
161 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", sched_path, e)
163 std.fput(sfd, "{}\n", card)
170 /* delete the old lists */
172 std.put("removing {}\n", f)
177 const show_each_card : (d : byte[:], cards : byte[:][:] -> std.result((byte[:], bool)[:], byte[:])) = {d : byte[:], cards : byte[:][:]
178 var ret : (byte[:], bool)[:] = [][:]
179 var t : termdraw.term# = termdraw.mk(1)
181 var progress : byte[:] = [][:]
186 var c : byte[:] = cards[k]
188 progress = std.fmt("{} / {}", k + 1, cards.len)
189 var sideapath : byte[:] = std.fmt("{}/cards/{}/side_A", d, c)
190 var sidebpath : byte[:] = std.fmt("{}/cards/{}/side_B", d, c)
194 var sideafull : byte[:] = [][:]
195 var sidebfull : byte[:] = [][:]
196 var sidea : byte[:][:] = [][:]
197 var sideb : byte[:][:] = [][:]
198 var dispside : byte[:][:] = [][:]
199 var dispwidth : int = 0
200 var still : bool = true
201 var seen_side_b : bool = false
203 match std.slurp(sideapath)
207 | `std.Ok b: sideafull = b
210 match std.slurp(sidebpath)
214 | `std.Ok b: sidebfull = b
217 sidea = std.strsplit(sideafull, "\n")
218 for var j = 0; j < sidea.len; ++j
219 widtha = std.max(widtha, (std.strcellwidth(sidea[j]) : int))
222 sideb = std.strsplit(sidebfull, "\n")
223 for var j = 0; j < sideb.len; ++j
224 widthb = std.max(widthb, (std.strcellwidth(sideb[j]) : int))
230 print_file(t, dispside, dispwidth)
231 print_instructions(t, seen_side_b, k > 0)
232 print_progress(t, progress)
234 match termdraw.event(t)
235 | `termdraw.Ctrl 'c':
238 | `termdraw.Kc 'a' || `termdraw.Kc 'A':
241 | `termdraw.Kc 'b' || `termdraw.Kc 'B':
245 | `termdraw.Kc 'y' || `termdraw.Kc 'Y':
247 std.slpush(&ret, (c, true))
251 | `termdraw.Kc 'n' || `termdraw.Kc 'N':
253 std.slpush(&ret, (c, false))
259 std.sldel(&ret, ret.len - 1)
269 std.slfree(sideafull)
270 std.slfree(sidebfull)
273 std.slfree(sideapath)
274 std.slfree(sidebpath)
281 const print_file = {t : termdraw.term#, lines : byte[:][:], lines_width : int
282 var start_y : int = 0
283 var start_x : int = 0
284 termdraw.setbg(t, bg_plain)
285 termdraw.setfg(t, fg_plain)
286 termdraw.clear(t, 0, 0, t.width, t.height)
288 if (lines_width < t.width) && (lines.len < t.height - 1)
289 /* We have enough room to center it */
290 start_y = (t.height - 1 - lines.len) / 2
291 start_x = (t.width - lines_width) / 2
293 for var j = 0; j < lines.len; ++j
294 termdraw.move(t, start_x, start_y + j)
295 termdraw.put(t, lines[j])
298 /* We don't. Lines/breaks/whatever. */
300 for var j = 0; j < lines.len; ++j
301 var immediate_dispatch : byte[:] = [][:]
302 var to_print : byte[:] = lines[j]
308 while to_print.len > 0 && y < t.height - 1
309 (immediate_dispatch, to_print) = split_by_cell_width(to_print, (t.width : std.size))
310 termdraw.move(t, 0, y)
311 termdraw.put(t, immediate_dispatch)
322 const print_instructions = {t : termdraw.term#, seen_side_b : bool, can_go_back : bool
323 termdraw.setbg(t, bg_alt)
324 termdraw.clear(t, 0, t.height - 1, t.width, t.height)
325 termdraw.move(t, 0, t.height - 1)
327 show_instruction(t, "a", "side A", true)
328 show_instruction(t, "b", "side B", true)
329 show_instruction(t, "y", "pass", seen_side_b)
330 show_instruction(t, "n", "fail", seen_side_b)
331 show_instruction(t, "<", "go back", can_go_back)
334 const show_instruction = {t : termdraw.term#, key : byte[:], desc : byte[:], visible
336 termdraw.setfg(t, fg_bluer)
338 termdraw.setfg(t, fg_lighter)
340 termdraw.put(t, "[{}]", key)
342 termdraw.setfg(t, fg_plain)
344 termdraw.setfg(t, fg_lighter)
346 termdraw.put(t, " {} ", desc)
349 const print_progress = {t : termdraw.term#, s : byte[:]
350 termdraw.setbg(t, bg_alt)
351 termdraw.setfg(t, fg_plain)
352 termdraw.move(t, t.width - s.len - 1, t.height - 1)
356 const split_by_cell_width = {base : byte[:], width : std.size
357 var position : std.size = 0
358 var cur_width : std.size = 0
359 for (c, o) : std.bycharoff(base)
360 cur_width += (std.cellwidth(c) : std.size)
362 -> (base[:position], base[position:])