aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-03-10 14:32:54 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-03-10 14:37:36 +0200
commit059c0c9af70f3d55b4ed18ce537d433a21a176bd (patch)
tree9b6da3bd0f3a84f36b35863e66223ea8b331012a /lib
parent9ce9968745dc64fafc800d7ede0fb13bedc6f1db (diff)
Add optparse.h
Diffstat (limited to 'lib')
-rw-r--r--lib/optparse/optparse.c156
1 files changed, 156 insertions, 0 deletions
diff --git a/lib/optparse/optparse.c b/lib/optparse/optparse.c
new file mode 100644
index 0000000..eaacc52
--- /dev/null
+++ b/lib/optparse/optparse.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "macros.h"
+#include "mbstring.h"
+#include "optparse.h"
+
+#define OPT_MSG_INVALID "invalid option"
+#define OPT_MSG_MISSING "option requires an argument"
+#define OPT_MSG_TOOMANY "option takes no arguments"
+
+#define IS_SHORTOPT(s) ((s)[0] == '-' && (s)[1] != '-' && (s)[1] != '\0')
+#define IS_LONGOPT(s) ((s)[0] == '-' && (s)[1] == '-' && (s)[2] != '\0')
+
+#define error(st, msg, x) \
+ _Generic((x), const char *: error_s, rune: error_r)((st), (msg), (x))
+
+static bool is_a_match(const char *, const char *);
+static rune error_r(struct optparse *, const char *, rune);
+static rune error_s(struct optparse *, const char *, const char *);
+static rune shortopt(struct optparse *, const struct op_option *, size_t);
+
+void
+optparse_init(struct optparse *st, char **argv)
+{
+ st->argv = argv;
+ st->optind = argv[0] != nullptr;
+ st->subopt = 0;
+ st->optarg = (struct u8view){};
+ st->errmsg[0] = '\0';
+}
+
+rune
+optparse(struct optparse *st, const struct op_option *opts, size_t nopts)
+{
+ st->errmsg[0] = '\0';
+ st->optarg = (struct u8view){};
+
+ const char *opt = st->argv[st->optind];
+ if (opt == nullptr)
+ return 0;
+ if (streq(opt, "--")) {
+ st->optind++;
+ return 0;
+ }
+ if (IS_SHORTOPT(opt))
+ return shortopt(st, opts, nopts);
+ if (!IS_LONGOPT(opt))
+ return 0;
+
+ st->subopt = 0;
+ st->optind++;
+ opt += 2; /* Skip ‘--’ */
+
+ for (size_t i = 0; i < nopts; i++) {
+ struct op_option o = opts[i];
+ if (!is_a_match(opt, o.longopt))
+ continue;
+ switch (o.argtype) {
+ case OPT_NONE:
+ if (strchr(opt, '=') != nullptr)
+ return error(st, OPT_MSG_TOOMANY, opt);
+ break;
+ case OPT_REQ: {
+ const char *p = strchr(opt, '=');
+ if (p == nullptr) {
+ if (st->argv[st->optind] == nullptr)
+ return error(st, OPT_MSG_MISSING, opt);
+ st->optarg.p = st->argv[st->optind++];
+ } else
+ st->optarg.p = p + 1;
+ st->optarg.len = strlen(st->optarg.p);
+ } break;
+ case OPT_OPT: {
+ const char *p = strchr(opt, '=');
+ if (p == nullptr)
+ st->optarg = (struct u8view){};
+ else {
+ st->optarg.p = p + 1;
+ st->optarg.len = strlen(st->optarg.p);
+ }
+ } break;
+ }
+ return o.shortopt;
+ }
+
+ return error(st, OPT_MSG_INVALID, opt);
+}
+
+rune
+shortopt(struct optparse *st, const struct op_option *opts, size_t nopts)
+{
+ rune ch;
+ const char *opt = st->argv[st->optind];
+ st->subopt += u8tor(&ch, opt + st->subopt + 1);
+ if (ch == '\0') {
+ st->subopt = 0;
+ st->optind++;
+ return optparse(st, opts, nopts);
+ }
+ for (size_t i = 0; i < nopts; i++) {
+ if (opts[i].shortopt != ch)
+ continue;
+ if (opts[i].argtype == OPT_NONE)
+ goto out;
+ if (opt[st->subopt + 1] != '\0') {
+ st->optarg.p = opt + st->subopt + 1;
+ st->optarg.len = strlen(st->optarg.p);
+ st->subopt = 0;
+ st->optind++;
+ goto out;
+ }
+ if (opts[i].argtype == OPT_OPT) {
+ st->optarg = (struct u8view){};
+ goto out;
+ }
+ if (st->argv[st->optind + 1] == nullptr) {
+ st->optarg = (struct u8view){};
+ return error(st, OPT_MSG_MISSING, ch);
+ }
+ st->optarg.p = st->argv[st->optind + 1];
+ st->optarg.len = strlen(st->optarg.p);
+ st->optind += 2;
+ goto out;
+ }
+ return error(st, OPT_MSG_INVALID, ch);
+out:
+ return ch;
+}
+
+bool
+is_a_match(const char *u, const char *t)
+{
+ for (; *u && *t && *u != '='; u++, t++) {
+ if (*u != *t)
+ return false;
+ }
+ return *t == '\0' && (*u == '\0' || *u == '=');
+}
+
+rune
+error_s(struct optparse *st, const char *msg, const char *s)
+{
+ const char *p = strchr(s, '=');
+ snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg,
+ (int)(p ? p - s : strlen(s)), s);
+ return -1;
+}
+
+rune
+error_r(struct optparse *st, const char *msg, rune ch)
+{
+ char buf[U8_LEN_MAX + 1] = {};
+ rtou8(buf, ch, sizeof(buf));
+ return error_s(st, msg, buf);
+}