diff options
author | Linnnus <[email protected]> | 2024-11-16 12:29:08 +0100 |
---|---|---|
committer | Linnnus <[email protected]> | 2024-11-16 12:29:08 +0100 |
commit | ef73ba2355fb5e48a1921c656f1b8e2106700b61 (patch) | |
tree | c9d14cb6f5c072b3002832fa764c82601919cea2 | |
parent | e89335c5f59963d902ac35df6ad9e3bac1040195 (diff) |
pkgs: Add human-sleep
-rw-r--r-- | pkgs/default.nix | 2 | ||||
-rw-r--r-- | pkgs/human-sleep/default.nix | 27 | ||||
-rw-r--r-- | pkgs/human-sleep/human-sleep.c | 344 |
3 files changed, 373 insertions, 0 deletions
diff --git a/pkgs/default.nix b/pkgs/default.nix index 6419d47..c97f271 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -23,6 +23,8 @@ pkgs: { cscript = pkgs.callPackage ./cscript {}; + human-sleep = pkgs.callPackage ./human-sleep {}; + # TODO: These should be contained in the 'vimPlugins' attrset. This turns out # to be non-trivial because this module is both consumed in a flake output # context and an overlay context. diff --git a/pkgs/human-sleep/default.nix b/pkgs/human-sleep/default.nix new file mode 100644 index 0000000..0a7db19 --- /dev/null +++ b/pkgs/human-sleep/default.nix @@ -0,0 +1,27 @@ +{ + stdenv, + lib, +}: +stdenv.mkDerivation { + pname = "human-sleep"; + version = "16-11-2024"; # Date of last change + + src = ./.; + buildPhase = '' + cc human-sleep.c -Wall -Wextra -o human-sleep + ''; + + # TODO: Run check phase: `cscript -DTEST human-sleep.c` + + installPhase = '' + mkdir -p $out/bin + mv human-sleep $out/bin/human-sleep + ln -s human-sleep $out/bin/hsleep + ''; + + meta = with lib; { + description = "Variant of the classic 'sleep' command that accepts suffixed numbers like '5 minutes'"; + license = licenses.unlicense; + mainProgram = "human-sleep"; + }; +} diff --git a/pkgs/human-sleep/human-sleep.c b/pkgs/human-sleep/human-sleep.c new file mode 100644 index 0000000..5381fc4 --- /dev/null +++ b/pkgs/human-sleep/human-sleep.c @@ -0,0 +1,344 @@ +/* + * This file uses code stolen from systemd to implement a smarter version of the `sleep` command. + */ + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +// Misc. utitlites +#define streq(a, b) (strcmp((a), (b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +// String categories +#define WHITESPACE " \t\n\r" +#define DIGITS "0123456789" + +// Type representing XXX. +typedef uint64_t nsec_t; + +// Conversion rates +#define NSEC_PER_USEC ((nsec_t) 1000ULL) +#define NSEC_PER_MSEC ((nsec_t) 1000000ULL) +#define NSEC_PER_SEC ((nsec_t) 1000000000ULL) +#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC)) +#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE)) +#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR)) +#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY)) +#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC)) +#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) + +// XXX Constants +#define NSEC_INFINITY ((nsec_t) UINT64_MAX) + +char *startswith(const char *s, const char *prefix) { + size_t l; + + assert(s); + assert(prefix); + + l = strlen(prefix); + if (!strneq(s, prefix, l)) + return NULL; + + return (char*) s + l; +} + +static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "seconds", NSEC_PER_SEC }, + { "second", NSEC_PER_SEC }, + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "minutes", NSEC_PER_MINUTE }, + { "minute", NSEC_PER_MINUTE }, + { "min", NSEC_PER_MINUTE }, + { "months", NSEC_PER_MONTH }, + { "month", NSEC_PER_MONTH }, + { "M", NSEC_PER_MONTH }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "hours", NSEC_PER_HOUR }, + { "hour", NSEC_PER_HOUR }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "days", NSEC_PER_DAY }, + { "day", NSEC_PER_DAY }, + { "d", NSEC_PER_DAY }, + { "weeks", NSEC_PER_WEEK }, + { "week", NSEC_PER_WEEK }, + { "w", NSEC_PER_WEEK }, + { "years", NSEC_PER_YEAR }, + { "year", NSEC_PER_YEAR }, + { "y", NSEC_PER_YEAR }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "μs", NSEC_PER_USEC }, /* U+03bc (aka GREEK LETTER MU) */ + { "µs", NSEC_PER_USEC }, /* U+b5 (aka MICRO SIGN) */ + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", NSEC_PER_SEC }, /* CHANGED: default is sec */ + }; + size_t i; + + assert(p); + assert(ret); + + for (i = 0; i < ELEMENTSOF(table); i++) { + char *e; + + e = startswith(p, table[i].suffix); + if (e) { + *ret = table[i].nsec; + return e; + } + } + + return p; +} + +int parse_nsec(const char *t, nsec_t *ret) { + const char *p, *s; + nsec_t nsec = 0; + bool something = false; + + assert(t); + assert(ret); + + p = t; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *ret = NSEC_INFINITY; + return 0; + } + + for (;;) { + // CHANGED: Use seconds per default + nsec_t multiplier = 0, k; + long long l; + char *e; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + if (*p == '-') /* Don't allow "-0" */ + return -ERANGE; + + errno = 0; + l = strtoll(p, &e, 10); + if (errno > 0) + return -errno; + if (l < 0) + return -ERANGE; + + if (*e == '.') { + p = e + 1; + p += strspn(p, DIGITS); + } else if (e == p) + return -EINVAL; + else + p = e; + + s = extract_nsec_multiplier(p + strspn(p, WHITESPACE), &multiplier); + assert(multiplier != 0); + if (s == p && *s != '\0') + /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */ + return -EINVAL; + + p = s; + + if ((nsec_t) l >= NSEC_INFINITY / multiplier) + return -ERANGE; + +#if 0 +printf("Using multiplier: %llu\n", multiplier); +#endif + + k = (nsec_t) l * multiplier; + if (k >= NSEC_INFINITY - nsec) + return -ERANGE; + + nsec += k; + + something = true; + + if (*e == '.') { + nsec_t m = multiplier / 10; + const char *b; + + for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) { + k = (nsec_t) (*b - '0') * m; + if (k >= NSEC_INFINITY - nsec) + return -ERANGE; + + nsec += k; + } + + /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ + if (b == e + 1) + return -EINVAL; + } + } + + *ret = nsec; + + return 0; +} + +static char *concat_arguments(int argc, char *argv[]) { + size_t buffer_length = 0; + for (int i = 1; i < argc; i++) { + buffer_length += strlen(argv[i]); + } + buffer_length += argc - 1; // spaces + + char *buffer = calloc(1, buffer_length); + if (buffer == NULL) { + perror("failed to alloc (argument concatenation)"); + } + + for (int i = 1; i < argc; i++) { + if (i != 1) { + strlcat(buffer, " ", buffer_length); + } + strlcat(buffer, argv[i], buffer_length); + } + + return buffer; +} + +static void usage(FILE *fp) { + const char *name = "human-sleep"; + + fprintf(fp, "Usage: %s <description>\n\n", name); + fprintf(fp, "Description must be something like '1 day' or '2 hours'.\n"); + fprintf(fp, "All arguments are concatenated.\n"); +} + +static void xnanosleep(struct timespec *ts) { + struct timespec remainder = *ts; + while (true) { + if (nanosleep(&remainder, &remainder) < 0) { + if (errno == EINTR) { + continue; + } else { + perror("failed to sleep"); + exit(EXIT_FAILURE); + } + } else { + break; // successfully slept + } + } +} + +int main(int argc, char *argv[]) +{ +#ifdef TEST + nsec_t u; + assert(parse_nsec("5s", &u) >= 0); + assert(u == 5 * NSEC_PER_SEC); + assert(parse_nsec("5s500ms", &u) >= 0); + assert(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert(parse_nsec(" 5s 500ms ", &u) >= 0); + assert(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert(parse_nsec(" 5.5s ", &u) >= 0); + assert(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert(parse_nsec(" 5.5s 0.5ms ", &u) >= 0); + assert(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC + 500 * NSEC_PER_USEC); + assert(parse_nsec(" .22s ", &u) >= 0); + assert(u == 220 * NSEC_PER_MSEC); + assert(parse_nsec(" .50y ", &u) >= 0); + assert(u == NSEC_PER_YEAR / 2); + assert(parse_nsec("2.5", &u) >= 0); + assert(u == 2); + assert(parse_nsec(".7", &u) >= 0); + assert(u == 0); + assert(parse_nsec("infinity", &u) >= 0); + assert(u == NSEC_INFINITY); + assert(parse_nsec(" infinity ", &u) >= 0); + assert(u == NSEC_INFINITY); + assert(parse_nsec("+3.1s", &u) >= 0); + assert(u == 3100 * NSEC_PER_MSEC); + assert(parse_nsec("3.1s.2", &u) >= 0); + assert(u == 3100 * NSEC_PER_MSEC); + assert(parse_nsec("3.1 .2s", &u) >= 0); + assert(u == 200 * NSEC_PER_MSEC + 3); + assert(parse_nsec("3.1 sec .2 sec", &u) >= 0); + assert(u == 3300 * NSEC_PER_MSEC); + assert(parse_nsec("3.1 sec 1.2 sec", &u) >= 0); + assert(u == 4300 * NSEC_PER_MSEC); + assert(parse_nsec(" xyz ", &u) < 0); // failures... + assert(parse_nsec("", &u) < 0); + assert(parse_nsec(" . ", &u) < 0); + assert(parse_nsec(" 5. ", &u) < 0); + assert(parse_nsec(".s ", &u) < 0); + assert(parse_nsec(" infinity .7", &u) < 0); + assert(parse_nsec(".3 infinity", &u) < 0); + assert(parse_nsec("-5s ", &u) < 0); + assert(parse_nsec("-0.3s ", &u) < 0); + assert(parse_nsec("-0.0s ", &u) < 0); + assert(parse_nsec("-0.-0s ", &u) < 0); + assert(parse_nsec("0.-0s ", &u) < 0); + assert(parse_nsec("3.-0s ", &u) < 0); + assert(parse_nsec(" infinity .7", &u) < 0); + assert(parse_nsec(".3 infinity", &u) < 0); + assert(parse_nsec("3.+1s", &u) < 0); + assert(parse_nsec("3. 1s", &u) < 0); + assert(parse_nsec("3.s", &u) < 0); + assert(parse_nsec("12.34.56", &u) < 0); + assert(parse_nsec("12..34", &u) < 0); + assert(parse_nsec("..1234", &u) < 0); + assert(parse_nsec("1234..", &u) < 0); + assert(parse_nsec("1111111111111y", &u) == -ERANGE); + assert(parse_nsec("1.111111111111y", &u) >= 0); +#endif + + if (argc < 2) { + usage(stderr); + exit(EXIT_FAILURE); + } + + for (int i = 1; i < argc; ++i) { + if (streq(argv[i], "--help") || streq(argv[i], "-h")) { + usage(stdout); + exit(EXIT_SUCCESS); + } + } + + char *description = concat_arguments(argc, argv); + nsec_t result; + if (parse_nsec(description, &result) < 0) { + fprintf(stderr, "Cannot parse duration: %s\n", description); + exit(EXIT_FAILURE); + } + + struct timespec ts; + ts.tv_sec = result / NSEC_PER_SEC; + ts.tv_nsec = result % NSEC_PER_SEC; + xnanosleep(&ts); + + return EXIT_SUCCESS; +} + +// vi: ft=c noet |