local posix = require("posix") local function time() if posix then local s, ns = posix.clock_gettime() return s + ns / 10^9 else return os.time() end end --[[ show progress thru a review session, conditioned on the user configuring that to happen ]] local function showprogress(k, n, conf) if conf:get("ShowProgress") and conf:get("ShowProgress") ~= "false" then io.write(string.format("(%d/%d) ", k, n)) end end --[[ display text with images, inserted with `IMG[path]` implementation may be platform-specific `basepath` points to the base directory of the images ]] local function imgify(text, basepath, ind, climn, conf, question) local fh = io.popen("tput lines") local tl = tonumber(fh:read("a")) fh:close() io.write(("\n"):rep(math.floor(0.4 * tl))) local pt = text:gsub(IMG_PATTERN, "") showprogress(ind, climn, conf) if conf:get("BoldContext") then local csi, cei = pt:find("^[^:]+:") if csi then local cpt = pt:sub(csi, cei) io.write("\x1b[1m") io.write(cpt) io.write("\x1b[0m") pt = pt:sub(cei + 1) if question then io.flush() os.execute("sleep 0.3") end end end io.write(pt) local ifl = {} for p in text:gmatch(IMG_PATTERN) do table.insert(ifl, 1, basepath .. p) end -- Linux/dwm if #ifl > 0 then os.execute(string.format( "/usr/local/bin/iv -t '%s' %s &", pt:gsub("\n", " "):gsub("'", "\""):gsub("\x1b[^m]+m", ""), table.concat(ifl, " ") )) end end --[[ rounds `x` to `n` places (default 0) after the radix point in base `b` (default 10) ]] local function round(x, n, b) local b = b or 10 local n = n or 0 if n == 0 then return math.tointeger(math.floor(x * b ^ n) / b ^ n) else return math.floor(x * b ^ n) / b ^ n end end --[[ return human-friendly string to present a duration (input in seconds), e.g. "44:37", "4.8h", "11d", "8.3m", "17.4y" ]] local function disptime(ts) local YDAY = 365.24 local MDAY = YDAY / 12 local DSEC = 86400 local HSEC = 3600 if ts >= 3 * YDAY * DSEC then return round(ts / (YDAY * DSEC), 1) .. "y" elseif ts >= 4 * MDAY * DSEC then return round(ts / (MDAY * DSEC), 1) .. "m" elseif ts >= 10 * DSEC then return round(ts / DSEC) .. "d" elseif ts >= 1.5 * DSEC then return round(ts / DSEC, 1) .. "d" elseif ts >= 8 * HSEC then return round(ts / HSEC) .. "h" elseif ts >= HSEC then return round(ts / HSEC, 1) .. "h" else return string.format("%d:%02d", round(ts / 60), round(ts % 60)) end end local function fn(config, deck, deckfname) if not isatty() then io.write("Review should be done in the terminal\n") return nil end -- collect list of index-pairs for prompts to be reviewed this session local ctrt = review.list(deck, config) io.write(string.format( "%d prompts are ready for review.\nHow many to review in this session? ", #ctrt )) io.flush() local climt = io.read("l") local climn = tonumber(climt) if not climn then io.write("Input not a number; aborting\n") return nil end climn = math.min(climn, #ctrt) local lrc = 0 local editqueue = {} local start = time() local rt = {} -- review prompts in this session's set for ind, ip in ipairs(ctrt) do if ind > climn then break end local pr = deck[ip[1]].pr[ip[2]] local pst = pr:text() local st = time() imgify(pst:gsub("%%A", "___"), deckfname:match(".*/") or "", ind, climn, config, true) io.write("\n") table.insert(rt, time()) io.read("l") rt[#rt] = time() - rt[#rt] clear() imgify( pst:gsub("%%A", "\x1b[1m" .. (pr:ans():gsub("%%", "%%%%")) .. "\x1b[0m"), deckfname:match(".*/") or "", ind, climn, config, false ) io.write(string.format( "\nAgain / Hard (%s) / Good (%s) / Easy (%s) / Correction ", disptime(review.schedule(pr, st, 0.5, config)), disptime(review.schedule(pr, st, 1, config)), disptime(review.schedule(pr, st, 1.5, config)) )) io.flush() local resp = io.read("l") local sc = review.succfromstr(resp) if sc == -1 then table.insert(editqueue, ip) else if sc == 0 then lrc = lrc + 1 end review.update(deck[ip[1]].pr[ip[2]], st, sc, config) end clear() if resp:sub(#resp):lower() == "q" then climn = ind break end end if #ctrt > 0 then local acr = math.min(#ctrt, climn) local endt = time() io.write(string.format( "Reviewed %d prompts in %f seconds\n", acr, endt - start )) local s if lrc == 1 then s = "" else s = "s" end io.write(string.format( "(%d lapse%s, %f seconds per card)\n", lrc, s, (endt - start) / acr )) local trt = 0 for _, x in ipairs(rt) do trt = trt + x end io.write(string.format( "Average %f seconds for recall per card\n", trt / acr )) end if #editqueue > 0 then io.write(string.format("The following cards are marked for correction:\n")) for _, ip in ipairs(editqueue) do io.write(string.format("%d %s\n", ip[1], deck[ip[1]])) end end if climn > 0 then return deck end end return { desc = "(r)eview sesion", nd = true, fn = fn }