allow sispare-from-jisho to handle words with only readings
[sispare.git] / sispare.myr
blobe58af5095b000934052049431986298b5d754178
1 use std
3 use bio
4 use date
5 use fileutil
6 use sys
8 use t
9 use termdraw
11 use "util"
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)
22 const main = {args
23         var dir_arg : byte[:] = [][:]
24         var basedir : byte[:] = [][:]
25         var dir_checked = false
26         var cmd : std.optparsed = std.optparse(args, &[
27                 .minargs = 0,
28                 .maxargs = 0,
29                 .opts = [
30                         [ .opt = 'd', .arg = "dir", .desc = "base directory" ],
31                 ][:],
32         ])
34         for opt : cmd.opts
35                 match opt
36                 | ('d', dir): dir_arg = dir
37                 | _: std.fatal("impossible {}\n", opt)
38                 ;;
39         ;;
41         match get_basedir(dir_arg)
42         | `std.Err e: std.fatal("{}\n", e)
43         | `std.Ok b: basedir = b
44         ;;
46         auto (basedir : t.doomed_str)
47         do_session(basedir)
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)
61                 var deal_with = false
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
65                 ;;
66                 auto (fbase : t.doomed_str)
68                 if deal_with
69                         match std.slurp(f)
70                         | `std.Err e:
71                                 std.fput(std.Err, "std.slurp(\"{}\"): {}\n", f, e)
72                         | `std.Ok lines:
73                                 var byline = std.strsplit(lines, "\n")
74                                 std.sljoin(&cards, byline)
75                                 std.slfree(byline)
76                                 std.slpush(&files_to_rm, std.sldup(f))
77                         ;;
78                 ;;
79         ;;
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])
91                         std.sldel(&cards, j)
92                         j--
93                 ;;
94         ;;
96         for var j = 0; j < cards.len; ++j
97                 if cards[j].len == 0
98                         std.sldel(&cards, j)
99                         j--
100                 ;;
101         ;;
103         for var j = 0; j < cards.len; ++j
104                 var dirpath = std.fmt("{}/cards/{}", d, cards[j])
105                 match std.diropen(dirpath)
106                 | `std.Ok d2:
107                         std.dirclose(d2)
108                 | `std.Err _:
109                         std.sldel(&cards, j)
110                         j--
111                 ;;
112                 std.slfree(dirpath)
113         ;;
115         if cards.len == 0
116                 std.put("All up-to-date; no review necessary.\n")
117         ;;
119         match show_each_card(d, cards)
120         | `std.Err e: std.fatal("{}\n", e)
121         | `std.Ok res:
122                 for (card, passed) : res
123                         /* compute new level for this card */
124                         var levelpath = std.fmt("{}/cards/{}/level", d, card)
125                         var level = 1
126                         auto (levelpath : t.doomed_str)
128                         match std.slurp(levelpath)
129                         | `std.Err e:
130                         | `std.Ok levelstr:
131                                 match std.intparse(levelstr)
132                                 | `std.Some l: level = l
133                                 | `std.None:
134                                 ;;
135                         ;;
137                         if passed
138                                 level++
139                         else
140                                 level = 1
141                         ;;
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)
146                         | `std.Ok lfd:
147                                 std.fput(lfd, "{}\n", level)
148                                 std.close(lfd)
149                         ;;
151                         /* determine when this card will be next studied */
152                         if level >= wait_days.len
153                                 continue
154                         ;;
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)
162                         | `std.Ok sfd:
163                                 std.fput(sfd, "{}\n", card)
164                                 std.close(sfd)
165                         ;;
166                 ;;
167         ;;
170         /* delete the old lists */
171         for f : files_to_rm
172                 std.put("removing {}\n", f)
173                 std.remove(f)
174         ;;
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)
180         var k : std.size = 0
181         var progress : byte[:] = [][:]
183         termdraw.raw(t)
185         while k < cards.len
186                 var c : byte[:] = cards[k]
187                 std.slfree(progress)
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)
191                 var widtha : int = 0
192                 var widthb : int = 0
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)
204                 | `std.Err e:
205                         std.sldel(&cards, k)
206                         goto done_with_this
207                 | `std.Ok b: sideafull = b
208                 ;;
210                 match std.slurp(sidebpath)
211                 | `std.Err e:
212                         std.sldel(&cards, k)
213                         goto done_with_this
214                 | `std.Ok b: sidebfull = b
215                 ;;
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))
220                 ;;
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))
225                 ;;
227                 dispside = sidea
228                 dispwidth = widtha
229                 while still
230                         print_file(t, dispside, dispwidth)
231                         print_instructions(t, seen_side_b, k > 0)
232                         print_progress(t, progress)
233                         termdraw.flush(t)
234                         match termdraw.event(t)
235                         | `termdraw.Ctrl 'c':
236                                 termdraw.free(t)
237                                 std.fatal("")
238                         | `termdraw.Kc 'a' || `termdraw.Kc 'A':
239                                 dispside = sidea
240                                 dispwidth = widtha
241                         | `termdraw.Kc 'b' || `termdraw.Kc 'B':
242                                 dispside = sideb
243                                 dispwidth = widthb
244                                 seen_side_b = true
245                         | `termdraw.Kc 'y' || `termdraw.Kc 'Y':
246                                 if seen_side_b
247                                         std.slpush(&ret, (c, true))
248                                         still = false
249                                         k++
250                                 ;;
251                         | `termdraw.Kc 'n' || `termdraw.Kc 'N':
252                                 if seen_side_b
253                                         std.slpush(&ret, (c, false))
254                                         still = false
255                                         k++
256                                 ;;
257                         | `termdraw.Kc '<':
258                                 if k > 0
259                                         std.sldel(&ret, ret.len - 1)
260                                         k--
261                                         still = false
262                                 ;;
263                         | _:
264                         ;;
266                 ;;
268 :done_with_this
269                 std.slfree(sideafull)
270                 std.slfree(sidebfull)
271                 std.slfree(sidea)
272                 std.slfree(sideb)
273                 std.slfree(sideapath)
274                 std.slfree(sidebpath)
275         ;;
277         termdraw.free(t)
278         -> `std.Ok ret
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])
296                 ;;
297         else
298                 /* We don't. Lines/breaks/whatever. */
299                 var y = start_y
300                 for var j = 0; j < lines.len; ++j
301                         var immediate_dispatch : byte[:] = [][:]
302                         var to_print : byte[:] = lines[j]
304                         if to_print.len == 0
305                                 y++
306                         ;;
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)
312                                 y++
313                         ;;
315                         if y >= t.height - 1
316                                 break
317                         ;;
318                 ;;
319         ;;
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
335         if visible
336                 termdraw.setfg(t, fg_bluer)
337         else
338                 termdraw.setfg(t, fg_lighter)
339         ;;
340         termdraw.put(t, "[{}]", key)
341         if visible
342                 termdraw.setfg(t, fg_plain)
343         else
344                 termdraw.setfg(t, fg_lighter)
345         ;;
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)
353         termdraw.put(t, s)
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)
361                 if cur_width > width
362                         -> (base[:position], base[position:])
363                 ;;
364                 position = o
365         ;;
367         -> (base[:], [][:])