14 var dir_arg : byte[:] = [][:]
15 var a_string : byte[:] = [][:]
16 var b_string : byte[:] = [][:]
17 var name : byte[:] = [][:]
18 var basedir : byte[:] = [][:]
19 var dir_checked = false
20 var cmd : std.optparsed = std.optparse(args, &[
24 [ .opt = 'd', .arg = "dir", .desc = "base directory", .optional = true ],
30 | ('d', dir): dir_arg = dir
31 | _: std.fatal("impossible {}\n", opt)
35 match get_basedir(dir_arg)
36 | `std.Err e: std.fatal("{}\n", e)
37 | `std.Ok b: basedir = b
39 auto (basedir : t.doomed_str)
42 std.fatal("exactly one argument required\n")
45 match from_jisho(cmd.args[0])
46 | `std.Ok (n, a, b): (name, a_string, b_string) = (n, a, b)
47 | `std.Err e: std.fatal("{}\n", e)
49 var card_name : byte[:] = std.fmt("jisho_word_{}", name)
50 auto (card_name : t.doomed_str)
51 var card_dir : byte[:] = std.pathjoin([basedir, "cards", card_name][:])
53 match std.diropen(card_dir)
56 std.fatal("card \"{}\" already exists\n", name)
60 match std.mkpath(card_dir)
62 | e: std.fatal("mkdir(\"{}\"): {}\n", card_dir, e)
65 var sched_dir_path = std.pathjoin([basedir, "schedule"][:])
66 match std.mkpath(sched_dir_path)
68 | e: std.fatal("mkdir(\"{}\"): {}\n", sched_dir_path, e)
71 var a_path : byte[:] = std.pathjoin([card_dir, "side_A"][:])
72 var b_path : byte[:] = std.pathjoin([card_dir, "side_B"][:])
73 var level_path : byte[:] = std.pathjoin([card_dir, "level"][:])
74 var next_review : date.instant = date.subperiod(date.utcnow(), `date.Day 7)
75 var sched_path = std.pathjoin([sched_dir_path, std.fmt("{f=%Y-%m-%d}", next_review)][:])
77 match std.open(sched_path, std.Owrite | std.Ocreat | std.Oappend)
78 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", sched_path, e)
80 std.fput(fd, "{}\n", card_name)
84 match std.open(level_path, std.Owrite | std.Ocreat | std.Oappend)
85 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", level_path, e)
91 match std.open(a_path, std.Owrite | std.Ocreat | std.Oappend)
92 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", a_path, e)
94 std.fput(fd, "{}\n", a_string)
98 match std.open(b_path, std.Owrite | std.Ocreat | std.Oappend)
99 | `std.Err e: std.fput(std.Err, "std.open(\"{}\"): {}\n", b_path, e)
101 std.fput(fd, "{}\n", b_string)
105 std.put("Added to {}\n", card_dir)
108 const from_jisho = { w : byte[:]
109 var url = std.fmt("https://jisho.org/api/v1/search/words?keyword={}", escfmt.url(w))
110 var human_url = std.fmt("https://jisho.org/word/{}", escfmt.url(w))
111 var res : byte[:] = [][:]
113 var found_anything : bool = false
114 var name : byte[:] = [][:]
115 var side_b : std.strbuf# = std.mksb()
116 auto (url : t.doomed_str)
117 auto (human_url : t.doomed_str)
119 match std.spork(["curl", "-s", url][:])
120 | `std.Err e: -> `std.Err std.fmt("spork(): {}", e)
121 | `std.Ok (pid, infd, outfd):
123 match std.fslurp(outfd)
124 | `std.Ok buf: res = buf
125 | `std.Err e: -> `std.Err std.fmt("slurp(): {}", e)
129 | `std.Waiterror: -> `std.Err "waitpid(curl): Waiterror"
130 | `std.Wfailure: -> `std.Err "waitpid(curl): Wfailure"
131 | `std.Wsignalled : -> `std.Err "waitpid(curl): Wsignalled"
136 match json.parse(res)
137 | `std.Err e: -> `std.Err std.fmt("json.parse()", res)
138 | `std.Ok r: root = r
141 match by_path(root, ["meta", "status"][:])
144 | `json.Num json_code:
145 if json_code != 200.0
146 -> `std.Err std.fmt("response code: {}", json_code)
148 | _: -> `std.Err std.fmt("response {}", v)
150 | _: -> `std.Err "malformed json response (no status code)"
153 /* Now walk through the data and try to pick out parts that match */
154 var data : json.elt#[:] = [][:]
155 match by_path(root, ["data"][:])
156 | `std.Some &(`json.Arr v): data = v
157 | `std.Some v: -> `std.Err std.fmt("malformed json response (data was \"{}\")", v)
158 | `std.None: -> `std.Err "malformed json response (no data)"
161 std.sbfmt(side_b, "{}\n\n", human_url)
163 for var j = 0; j < data.len; ++j
164 var srs = get_spelling_readings(data[j])
165 for (spelling, reading) : srs
166 if std.eq(spelling, w) || std.eq(reading, w)
167 append_word_desc(side_b, data[j], srs)
174 -> `std.Ok (w, w, std.sbfin(side_b))
177 const by_path : (root : json.elt#, seq : byte[:][:] -> std.option(json.elt#)) = {root : json.elt#, seq : byte[:][:]
182 for var j = 0; j < a.len; ++j
183 var key : byte[:] = a[j].0
184 var val : json.elt# = a[j].1
185 if std.eq(key, seq[0])
204 /* Just take the first entry of "japanese" that has both a spelling and a reading */
205 const get_spelling_readings = {root : json.elt#
206 var ret : (byte[:], byte[:])[:] = [][:]
207 match by_path(root, ["japanese"][:])
208 | `std.Some &(`json.Arr a):
209 for var j = 0; j < a.len; ++j
210 match (by_path(a[j], ["word"][:]), by_path(a[j], ["reading"][:]))
211 | (`std.Some &(`json.Str w), `std.Some &(`json.Str r)):
212 std.slpush(&ret, (w, r))
213 | (`std.None, `std.Some &(`json.Str r)):
214 std.slpush(&ret, ("", r))
224 const append_word_desc = {sb : std.strbuf#, root : json.elt#, srs : (byte[:], byte[:])[:]
230 1. book; volume; script
242 5. counter for long cylindrical things; counter for films, TV shows, etc.; counter for goals, home runs, etc.; counter for telephone calls
245 for (spelling, reading) : srs
247 std.sbfmt(sb, "{} ({})\n", spelling, reading)
249 std.sbfmt(sb, "{}\n", reading)
254 match by_path(root, ["senses"][:])
255 | `std.Some &(`json.Arr a):
256 for var j = 0; j < a.len; ++j
257 match by_path(a[j], ["parts_of_speech"][:])
258 | `std.Some &(`json.Arr ap):
260 for var k = 0; k < ap.len; ++k
266 std.sbfmt(sb, "{}", s)
272 std.sbfmt(sb, "\n\n")
277 match by_path(a[j], ["english_definitions"][:])
278 | `std.Some &(`json.Arr ap):
280 for var k = 0; k < ap.len; ++k
286 std.sbfmt(sb, " {w=3,p= }. ", n)
288 std.sbfmt(sb, "{}", s)
295 std.sbfmt(sb, "\n\n")