diff --git a/status/Makefile b/status/Makefile new file mode 100644 index 0000000..78fce1e --- /dev/null +++ b/status/Makefile @@ -0,0 +1,22 @@ +CFLAGS += -g + +run: status + ./status + +clean: + rm -f status *.o + +status: status.c order.c io.o keyval.o + $(CC) $(CFLAGS) -o $@ $@.c io.o keyval.o + +order.c: order.def.c + - cp -v order.def.c $@ + +io.o: io.c io.h + $(CC) $(CFLAGS) -c -o $@ io.c + +keyval.o: keyval.c keyval.h settings.c + $(CC) $(CFLAGS) -c -o $@ keyval.c + +settings.c: settings.def.c + - cp -v settings.def.c $@ diff --git a/status/README b/status/README new file mode 100644 index 0000000..2881d41 --- /dev/null +++ b/status/README @@ -0,0 +1,6 @@ + __ ___ __ ___ _ _ __ + / _// // // // // // _/ STATUS + _'. / // _ // / / // /_'. how's your day going? +/__/ /_//_//_//_/ /__/ /__/ DTB 2025. Public Domain. + +build: $ make diff --git a/status/io.c b/status/io.c new file mode 100644 index 0000000..d8151dd --- /dev/null +++ b/status/io.c @@ -0,0 +1,18 @@ +#undef NDEBUG /* LOAD-BEARING ASSERTS! */ +#include /* assert(3) */ +#include /* errno */ +#include /* fputs(3), stdout, NULL */ +#include "io.h" + +/* output */ +static char *hold = NULL; + +void +drop(void) { hold = NULL; } + +void +out(char *s) { + if (hold != NULL) + { errno = 0; fputs(hold, stdout); assert(errno == 0); } + hold = s; +} diff --git a/status/io.h b/status/io.h new file mode 100644 index 0000000..7554589 --- /dev/null +++ b/status/io.h @@ -0,0 +1,2 @@ +void drop(void); /* Set the buffer to NULL. */ +void out(char *s); /* Print the buffer if not NULL and replace it with s. */ diff --git a/status/keyval.c b/status/keyval.c new file mode 100644 index 0000000..4c5c6ef --- /dev/null +++ b/status/keyval.c @@ -0,0 +1,64 @@ +/* 2025 DTB. Public domain. */ + +#undef NDEBUG /* LOAD-BEARING ASSERTS! */ +#include /* assert(3) */ +#include /* strcmp(3) */ +#include "keyval.h" + +static struct State state = { 0 }; + +void +cpy(char *key) { + state.settings[state.setting_last].key = key; + state.settings[state.setting_last].val = get(key); + assert(++state.setting_last < state_settings_size); +} + +void +del(char *key) { + size_t i; + for (i = idx(key); i < state.setting_last; ++i) { + state.settings[i].key = state.settings[i + 1].key; + state.settings[i].val = state.settings[i + 1].val; + } + --state.setting_last; +} + +char * +get(char *key) { return state.settings[idx(key)].val; } + +size_t +idx(char *key) { + size_t r; + for (r = 0; state.settings[r].key != NULL + && strcmp(state.settings[r].key, key) != 0; + ++r); + return (state.settings[r].key == NULL) ? state.setting_last : r; +} + +void +keyval_init(void) { + /* Unnecessary, but means a wrong state.setting_last won't blow up. */ + (void)memset(&state.settings[state.setting_last], + state_settings_size - state.setting_last, '\0'); +#include "settings.c" +} + +size_t +ren(char *key, char *new) { + size_t i; + if ((i = idx(key)) != state.setting_last) + { state.settings[i].key = new; } + return i; +} + +size_t +set(char *key, char *val) { + size_t i; + i = idx(key); + state.settings[i].key = key; + state.settings[i].val = val; + if (i == state.setting_last) + { assert(++state.setting_last < state_settings_size); } + return i; +} diff --git a/status/keyval.h b/status/keyval.h new file mode 100644 index 0000000..fba2276 --- /dev/null +++ b/status/keyval.h @@ -0,0 +1,22 @@ +/* 2025 DTB. Public domain. */ + +/* keyval store */ +#ifndef state_settings_size +# define state_settings_size 100 +#endif + +struct State { + size_t setting_last; + struct Setting { char *key; char *val; } settings[state_settings_size]; +}; + +void cpy(char *key); /* Duplicate the setting key. */ +void del(char *key); /* Delete the first instance of key in settings. */ +char * get(char *key); /* Get the corresponding val to key in settings. */ +void keyval_init(void); /* Sanitize the settings store. */ +size_t idx(char *key); /* Returns the index of key if in settings, or + * state.setting_last. */ +size_t ren(char *key, char *dst); /* Rename the first instance of key in + * settings. */ +size_t set(char *key, char *val); /* Set the first instance of key in + * settings. */ diff --git a/status/order.def.c b/status/order.def.c new file mode 100644 index 0000000..8afdc68 --- /dev/null +++ b/status/order.def.c @@ -0,0 +1,52 @@ +/* This is a C source file #included at global scope in status.c. There are a + * number of useful functions defined in status.c and #included headers to try + * to make iteration a little more convenient. */ + +/* Environment + * getenv(key) gets the value of an environment variable + * setenv(key, val, overwrite) sets key to val in the environment, overwriting + * val if key was already defined and overwrite is 0 + * set_timezone(val) sets TZ to val in the environment, then calls tzset(3) to + * set appropriate timezone values. if val is NULL, it clears the TZ val, + * which restores the timezone to the system local time + * get(key) gets the val corresponding to key in settings + * set(key, val) sets the val corresponding to key in settings + * idx(key) returns the index of key in settings + * del(key) deletes the first instance of key in settings + * cpy(key) creates an identical instance to key in settings + * ren(key, dst) renames key to dst in settings + * keyval_init() cleans the settings in case of a glitch */ + +/* I/O + * out(s) holds s and prints what it was previously holding, if it wasn't NULL + * drop() sets out()'s held value to NULL */ + +/* Status + * status_static(s) outputs s and then the separator val, unless + * status_static() is called as the last function in order + * status_time(fmt) outputs the time for the current time zone in the + * strftime(3) fmt */ +void +order() { + /* there are a couple good ways to print without the separator. the + * best is to use out() to write directly to the output buffer */ + out("utc"); /* => "utc" */ + /* but another way is to use status_static to write a message and + * separator, then use drop() to recall the last thing written (always + * the separator in status_* functions) */ + status_static(" "); drop(); /* => " " */ + set_timezone(""); + status_time("%Y-%m-%dT%H:%M"); /* => "2025-03-27T00:00 // " */ + + /* and another way is to use the weird key-val system */ + cpy("separator"); /* duplicate setting */ + set("separator", " "); /* overwrite first setting */ + set_timezone(NULL); + status_static("local"); /* use first setting */ + del("separator"); /* delete the first, restoring the second */ + status_time("%Y-%m-%dT%H:%M"); + drop(); + /* => "local 2025-03-26T18:00" */ + + /* all => "utc 2025-03-27T00:00 // local 2025-03-26T18:00" */ +} diff --git a/status/settings.def.c b/status/settings.def.c new file mode 100644 index 0000000..e027fbd --- /dev/null +++ b/status/settings.def.c @@ -0,0 +1,2 @@ +set("interval_seconds", "1"); +set("separator", " // "); diff --git a/status/status.c b/status/status.c new file mode 100644 index 0000000..fe144af --- /dev/null +++ b/status/status.c @@ -0,0 +1,59 @@ +#undef NDEBUG /* LOAD-BEARING ASSERTS! */ +#include /* assert(3) */ +#include /* UCHAR_MAX */ +#include /* fputs(3), stdout, NULL */ +#include /* atoi(3) */ +#include /* localtime(3), strftime(3), time(2), time_t, struct tm */ +#include /* sleep(3) */ +#include "io.h" /* drop(3), out(3) */ +#include "keyval.h" /* cpy(3), del(3), get(3), keyval_init(3), set(3) */ + +typedef unsigned char tick_t; +#define TICK_MAX UCHAR_MAX + +unsigned int slept = 0; +tick_t ticker = 0; + +void +set_timezone(char *zone) { + if (zone == NULL) { assert(unsetenv("TZ") == 0); } + else { assert(setenv("TZ", zone, 1) == 0); } + tzset(); +} + +void +status_static(char *s) { out(s); out(get("separator")); } + +#ifndef status_time_bufsize +# define status_time_bufsize 100 +#endif +void +status_time(char *fmt) { + static char buf[status_time_bufsize]; + struct tm *tm; + static time_t t = 0; + static tick_t last_tick = 0; + + if (ticker == 0) { t = time(NULL); } + else if (ticker != last_tick) { t += slept; } + last_tick = ticker; + assert((tm = localtime(&t)) != NULL); + assert(strftime(buf, sizeof(buf) / sizeof(*buf), fmt, tm) != 0); + + out(buf); + out(get("separator")); +} + +#include "order.c" + +int main(int argc, char *argv) { + keyval_init(); + + while(1) { + order(); + drop(); + out("\n"); + slept = sleep(atoi(get("interval_seconds"))); + ticker = ++ticker % TICK_MAX; + } +}