summaryrefslogtreecommitdiff
path: root/timer.ha
blob: 75d99b4f27d6ff5684161ebe6b129b05c9ea320e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use fmt;
use ev;
use getopt;
use os;
use time;
use strings;
use strconv;
use math;

type uniterror = !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::fprint(os::stderr, "\x1B[1K\r")!;
	const count = human_readable(currentduration);
	defer free(count);
	fmt::fprint(os::stderr, 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 | uniterror | strconv::invalid | strconv::overflow) = {
	const s = strings::toutf8(duration);
	const unit = s[len(s)-1];
	const unit = switch (unit) {
	case 's' =>
		yield time::SECOND;
	case 'm' =>
		yield time::MINUTE;
	case 'h' =>
		yield time::HOUR;
	case =>
		return uniterror;
	};
	const d = strconv::stoi(strings::sub(duration, 0, len(duration) - 1))?;
	return d * unit;
};

export fn main() void = {
	const cmd = getopt::parse(os::args,
		"timer for alarms",
		"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 duration = parse_duration(duration)!;

	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::println()!;
};