use fmt; use ev; use getopt; use os; use time; use strings; use strconv; use math; type parseerror = !void; fn timer_handler(file: *ev::file) void = { const loop = ev::getloop(file); const t = ev::getuser(file): *time::instant; const t = *t; if (print_countdown(t)) { ev::stop(loop); }; }; fn print_countdown(instant: time::instant) bool = { const tn = time::now(time::clock::MONOTONIC); const currentduration = time::diff(tn, instant); // man page console_codes(4) is useful fmt::error("\x1B[1K\r")!; const count = human_readable(currentduration); defer free(count); fmt::error(count)!; return currentduration <= 0; }; // Transform a duration to a human readable string representation. fn human_readable(duration: time::duration) str = { const hours = if (duration > time::HOUR) { let hours = math::floorf64(duration: f64 / time::HOUR: f64): u64; duration = (duration % time::HOUR); yield hours; } else { yield 0u64; }; const mins = if (duration > time::MINUTE) { let mins = math::floorf64(duration: f64 / time::MINUTE: f64): u64; duration = (duration % time::MINUTE); yield mins; } else { yield 0u64; }; const secs = if (duration > time::SECOND) { yield math::floorf64(duration: f64 / time::SECOND: f64): u64; } else { yield 0u64; }; const mods = &fmt::mods { pad = '0', width = 2, ... }; return fmt::asprintf("{%}:{%}:{%}", hours, mods, mins, mods, secs, mods); }; fn parse_duration(duration: const str) (time::duration | parseerror | strconv::invalid | strconv::overflow) = { const spl = strings::split(duration, ":"); defer free(spl); if (len(spl) >= 4 || len(spl) < 1) { return parseerror; }; const hours = if (len(spl) == 3) { yield strconv::stoi(spl[0])?; } else { yield 0; }; const mins = if (len(spl) >= 2) { yield strconv::stoi(spl[len(spl)-2])?; } else { yield 0; }; const secs = strconv::stoi(spl[len(spl)-1])?; const hours = hours * time::HOUR; const mins = mins * time::MINUTE; const secs = secs * time::SECOND; return hours + mins + secs; }; fn parse_duration_hms(duration: const str) (time::duration | parseerror | strconv::invalid | strconv::overflow) = { let start = 0z; const hours = match (strings::index(duration, 'h')) { case let i: size => const s = strconv::stoi(strings::sub(duration, start, i))!; start = i + 1; yield s; case void => yield 0; }; const mins = match (strings::index(duration, 'm')) { case let i: size => const s = strconv::stoi(strings::sub(duration, start, i))!; start = i + 1; yield s; case void => yield 0; }; const secs = match (strings::index(duration, 's')) { case let i: size => fmt::error(start)!; yield strconv::stoi(strings::sub(duration, start, i))!; case void => yield 0; }; const hours = hours * time::HOUR; const mins = mins * time::MINUTE; const secs = secs * time::SECOND; return hours + mins + secs; }; export fn main() void = { const cmd = getopt::parse(os::args, "cli timer with a countdown", "duration", ); defer getopt::finish(&cmd); if (len(cmd.args) != 1) { getopt::printusage(os::stderr, os::args[0], cmd.help)!; os::exit(os::status::FAILURE); }; const duration = cmd.args[0]; const f = if (strings::contains(duration, ':')) { yield &parse_duration; } else { yield &parse_duration_hms; }; const duration = match (f(duration)) { case let d: time::duration => yield d; case parseerror => fmt::fatal("Error parsing the given duration"); case let e: strconv::invalid => fmt::fatal(strconv::strerror(e)); case let e: strconv::overflow => fmt::fatal(strconv::strerror(e)); }; const loop = ev::newloop()!; const t = time::now(time::clock::MONOTONIC); const t = time::add(t, duration); defer ev::finish(&loop); const f = ev::newtimer(&loop, &timer_handler, time::clock::MONOTONIC)!; ev::setuser(f, &t); print_countdown(t); ev::timer_configure(f, 1 * time::SECOND, 1 * time::SECOND); for (ev::dispatch(&loop, -1)!) void; fmt::errorln()!; };