summary refs log tree commit diff
path: root/args.h
diff options
context:
space:
mode:
Diffstat (limited to 'args.h')
-rw-r--r--args.h138
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