diff options
author | WormHeamer | 2025-03-08 16:45:17 -0500 |
---|---|---|
committer | WormHeamer | 2025-03-08 16:45:17 -0500 |
commit | 4af753d591e61a7380735e03a8658fb8949e0448 (patch) | |
tree | ae92949580512dadecd26b8c3a359f2bd7755d58 /args.h |
initial commit
Diffstat (limited to 'args.h')
-rw-r--r-- | args.h | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/args.h b/args.h new file mode 100644 index 0000000..35c5283 --- /dev/null +++ b/args.h @@ -0,0 +1,138 @@ +#ifndef ARGS_H +#define ARGS_H + +#include "str.h" + +typedef struct { + const char **arg; + const char *opt_end; +} ArgsState; + +typedef enum { + ARG_OK = 0, + ARG_END = -1, + ARG_BAD = -2, + ARG_EMPTY = -3 +} ArgResult; + +ArgsState args_begin(const char **argv); +ArgResult arg_getv(ArgsState *a, const char *fmt, Str *arg, ...); +#define arg_get(a, fmt, arg, ...)\ + arg_getv(a, fmt, arg __VA_OPT__(,) __VA_ARGS__, NULL) + +#ifdef ARGS_IMPL +#include <stdarg.h> + +ArgsState args_begin(const char **argv) { + return (ArgsState) { argv + 1, NULL }; +} + +static int arg_opt_find(const char **opts, Str key) { + for (int i = 0; opts[i]; i++) { + const char *o = opts[i]; + if (*o == ':') o++; + if (str_eql(str_from_cstr(o), key)) return i; + } + return -1; +} + +static ArgResult arg_param(ArgsState *a, Str name, Str rem, Str *arg) { + if (rem.n > 0) { + *arg = rem; + return ARG_OK; + } else if (a->arg[1]) { + *arg = str_from_cstr(*++a->arg); + return ARG_OK; + } else { + *arg = name; + return ARG_EMPTY; + } +} + +static int arg_got_long(ArgsState *a, const char **opts, int *optv, Str *arg) { + Cut key = str_cut(str_from_cstr(*a->arg + 2), '='); + if (opts && optv) { + int o = arg_opt_find(opts, key.head); + if (o < 0) { + *arg = key.head; + return ARG_BAD; + } + if (opts[o][0] == ':') { + int x = arg_param(a, key.head, key.tail, arg); + if (x < 0) return x; + } + a->arg++; + return optv[o]; + } + *arg = key.head; + return ARG_BAD; +} + +static ArgResult arg_got_short(ArgsState *a, const char *fmt, Str *arg) { + Str opt = { (char*)*a->arg, 1 }; + Str rem = str_from_cstr(*a->arg + 1); + for (const char *f = fmt; *f; f++) { + if (*f == ':') continue; + if (*f == **a->arg) { + if (f[1] == ':') { + int x = arg_param(a, opt, rem, arg); + if (x < 0) return x; + a->arg++; + } else { + (*a->arg)++; + } + return *f; + } + } + *arg = opt; + return ARG_BAD; +} + +static ArgResult arg_get_long(ArgsState *a, const char *fmt, const char **opts, int *optv, Str *arg) { + if (*a->arg && !**a->arg) a->arg++; + if (!*a->arg) return ARG_END; + const char *arg_end = *a->arg + strlen(*a->arg); + if (a->opt_end != arg_end) { + if (a->arg[0][0] != '-' || a->arg[0][1] == '\0') { + *arg = str_from_cstr(*a->arg++); + return ARG_OK; + } + if (a->arg[0][1] == '-') { + return arg_got_long(a, opts, optv, arg); + } + (*a->arg)++; + a->opt_end = arg_end; + } + return arg_got_short(a, fmt, arg); +} + +ArgResult arg_getv(ArgsState *a, const char *fmt, Str *arg, ...) { + /* I think this is a legitimate usecase for VLAs --- they're not + * safe if N depends on user input, but here it very much doesn't! + * Just on the number of arguments passed, which is a compile time + * constant. */ + va_list ap; + int n = 0; + va_start(ap, arg); + while (va_arg(ap, const char *)) { + n++; + (void)va_arg(ap, int); + } + va_end(ap); + if (n > 0) { + const char *opt[n]; + int optv[n]; + va_start(ap, arg); + for (int i = 0; i < n; i++) { + opt[i] = va_arg(ap, const char *); + optv[i] = va_arg(ap, int); + } + va_end(ap); + return arg_get_long(a, fmt, opt, optv, arg); + } else { + return arg_get_long(a, fmt, NULL, NULL, arg); + } +} + +#endif +#endif |