summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Smeding <tom@tomsmeding.com>2024-07-22 23:14:43 +0200
committerTom Smeding <tom@tomsmeding.com>2024-07-22 23:14:43 +0200
commitbd22d0e47d0fb203286b088f048cf1aff1fa93a1 (patch)
tree9cc02c299e142aa80975c188927ea8b99221829a
parent9794094a5b3ac975d661643fc0bde6f6340ceafb (diff)
Nieuw optieontleedsysteem, en migreer weerklank
-rw-r--r--src/util/option.c108
-rw-r--r--src/util/option.h76
-rw-r--r--src/weerklank.c63
3 files changed, 204 insertions, 43 deletions
diff --git a/src/util/option.c b/src/util/option.c
new file mode 100644
index 0000000..ef7a398
--- /dev/null
+++ b/src/util/option.c
@@ -0,0 +1,108 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include "option.h"
+#include "util/versie.h"
+#include "util/debug.h"
+
+
+/* static void debug_argv(int argc, char **argv) {
+ fprintf(stderr, "argv: <");
+ for (int i = 0; i < argc; i++) {
+ if (i != 0) fputc(',', stderr);
+ fprintf(stderr, "'%s'", argv[i]);
+ }
+ fprintf(stderr, ">\n");
+} */
+
+char** option_parse(int argc, char **argv, const struct option_spec *speclist) {
+ // fill table of specs
+ const struct option_spec *spectable[256] = {0};
+ for (const struct option_spec *spec = speclist; spec->kind != OPTSPECKIND_END; spec++) {
+ if (spectable[(uint8_t)spec->optc] != NULL) {
+ fprintf(stderr, "option_parse: dubbele specs\n");
+ abort();
+ }
+ spectable[(uint8_t)spec->optc] = spec;
+ }
+
+ // parse options
+ int last_option_index = 0; // index of last option argument, treating '--' as option arg
+ int npos_before_opts = 0; // number of positional arguments before last_option_index
+ int opti;
+ for (opti = 1; opti < argc; opti++) {
+ if (argv[opti][0] != '-') continue; // skip non-options
+
+ // count an option argument even if it's '--'
+ npos_before_opts += opti - (last_option_index + 1);
+ last_option_index = opti;
+
+ if (strcmp(argv[opti], "--") == 0) break; // but don't try to parse '--'
+
+ for (int j = 1; argv[opti][j]; j++) {
+ const struct option_spec *spec = spectable[(uint8_t)argv[opti][j]];
+ if (!spec) {
+ fprintf(stderr, "%s: Ongeldige optie: -%c\n", argv[0], argv[opti][j]);
+ exit(1);
+ }
+
+ switch (spec->kind) {
+ case OPTSPECKIND_SETBOOL:
+ *spec->spec_sub.setbool.ptr = true;
+ break;
+
+ case OPTSPECKIND_CALL:
+ spec->spec_sub.call.fun();
+ break;
+
+ case OPTSPECKIND_CALLP:
+ spec->spec_sub.callp.fun(spec->spec_sub.callp.data);
+ break;
+
+ case OPTSPECKIND_HELPUSAGE:
+ printf(spec->spec_sub.helpusage.usagestr, argv[0]);
+ exit(0);
+
+ case OPTSPECKIND_VERSION:
+ drukkedoos_print_versie(stdout, argv[0]);
+ exit(0);
+
+ case OPTSPECKIND_END: abort(); // unreachable
+ }
+ }
+ }
+
+ DEBUG("last_option_index=%d npos_before_opts=%d\n", last_option_index, npos_before_opts);
+
+ // debug_argv(argc, argv);
+
+ // collect positional arguments that need to be reshuffled
+ char *posargs[npos_before_opts];
+ for (int i = 1, posi = 0; i < last_option_index; i++) {
+ // don't need to check for '--' here because '--' is an option argument and
+ // this loop doesn't go beyond it
+ if (argv[i][0] == '-') continue;
+
+ posargs[posi++] = argv[i];
+ }
+
+ // move option arguments to the beginning
+ int noptargs = 0;
+ for (int readi = 1; readi <= last_option_index; readi++) {
+ // no need to check for '--' here
+ if (argv[readi][0] == '-') {
+ if (readi > noptargs + 1) argv[noptargs + 1] = argv[readi];
+ noptargs++;
+ }
+ }
+
+ // debug_argv(argc, argv);
+
+ // write positional arguments after the option arguments
+ memcpy(argv + 1 + noptargs, posargs, npos_before_opts * sizeof(char*));
+
+ // debug_argv(argc, argv);
+
+ return argv + 1 + noptargs;
+}
diff --git a/src/util/option.h b/src/util/option.h
new file mode 100644
index 0000000..780a6e9
--- /dev/null
+++ b/src/util/option.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <stdbool.h>
+
+
+enum option_spec_kind {
+ OPTSPECKIND_SETBOOL,
+ OPTSPECKIND_CALL,
+ OPTSPECKIND_CALLP,
+
+ OPTSPECKIND_HELPUSAGE,
+ OPTSPECKIND_VERSION,
+
+ OPTSPECKIND_END, // end of spec list (rest of spec entry ignored)
+};
+
+// For usage info, see option_parse() below.
+struct option_spec {
+ char optc; // short option character
+ // TODO: long options?
+
+ // From here, the fields should be filled in using the OPTION_* macros.
+ enum option_spec_kind kind;
+ union {
+ struct { bool *ptr; } setbool;
+ struct { void (*fun)(void); } call;
+ struct { void (*fun)(void*); void *data; } callp;
+ struct { const char *usagestr; } helpusage;
+ struct {} version;
+ } spec_sub;
+};
+
+// Takes a bool* and stores 'true' there if the flag is found.
+#define OPTION_SETBOOL(boolptr) \
+ OPTSPECKIND_SETBOOL, {.setbool={.ptr=(boolptr)}}
+
+// Takes a void(*)(void) and calls it every time the flag is found.
+#define OPTION_CALL(funptr) \
+ OPTSPECKIND_CALL, {.call={.fun=(funptr)}}
+
+// Takes a void(*)(void*) and a 'void *data', and calls the function with 'data'
+// every time the flag is found.
+#define OPTION_CALLP(funptr, dataptr) \
+ OPTSPECKIND_CALLP, {.callp={.fun=(funptr), .data=(dataptr)}}
+
+// Takes a printf format string with a single %s, which is substituted (by
+// printf) for argv[0]. Prints the formatted string and exits with code 0.
+#define OPTION_HELPUSAGE(usagestring) \
+ OPTSPECKIND_HELPUSAGE, {.helpusage={.usagestr=(usagestring)}}
+
+// Prints version of the tool and exits with code 0.
+#define OPTION_VERSION() \
+ OPTSPECKIND_VERSION, {.version={}}
+
+// This must be the final entry in the list of option specs; contrary to the
+// other OPTION_* macros, it is an _entire_ 'struct option_spec'.
+#define OPTION_SPEC_END {'\0', OPTSPECKIND_END, {}}
+
+// The spec must not contain duplicate entries for the same short option
+// character.
+// Reshuffles positional arguments to the end of the argument list; returns a
+// pointer to the first positional argument after reshuffling.
+//
+// The specs array is most conveniently declared as a C array:
+//
+// const struct option_spec[] = {
+// {'h', OPTION_HELPUSAGE("Gebruik: %s <args>\n")},
+// {'V', OPTION_VERSION()},
+// OPTION_SPEC_END
+// }
+//
+// The individual option specs should be initialised with a brace-initialiser
+// containing the short option character followed by one of the OPTION_* macros,
+// as shown in the example above. See the comments above the macros for their
+// meaning. End the list with OPTION_SPEC_END.
+char** option_parse(int argc, char **argv, const struct option_spec *specs);
diff --git a/src/weerklank.c b/src/weerklank.c
index ad03389..18b8b59 100644
--- a/src/weerklank.c
+++ b/src/weerklank.c
@@ -1,22 +1,17 @@
#include <stdio.h>
-#include <stdlib.h>
#include <stdbool.h>
-#include <string.h>
#include <ctype.h>
-#include <unistd.h>
-#include "util/versie.h"
-
-static void usage(FILE *f) {
- fprintf(f,
- "Gebruik: weerklank [-nehV] [touwtje]...\n"
- "\n"
- "Exporteer de touwtjes.\n"
- "\n"
- " -n Voeg geen nieuwe regel toe\n"
- " -e Ontleed terugslagontsnappingen\n"
- " -h Toon deze hulptekst\n"
- " -V Toon versienummer\n");
-}
+#include "util/option.h"
+
+static const char *usage_string =
+ "Gebruik: %s [-nehV] [touwtje]...\n"
+ "\n"
+ "Exporteer de touwtjes.\n"
+ "\n"
+ " -n Voeg geen nieuwe regel toe\n"
+ " -e Ontleed terugslagontsnappingen\n"
+ " -h Toon deze hulptekst\n"
+ " -V Toon versienummer\n";
struct options {
bool nonewline;
@@ -25,33 +20,15 @@ struct options {
// Returns pointer to argument array containing the file names
static char** parse_options(int argc, char **argv, struct options *opts) {
- int opt;
- while ((opt = getopt(argc, argv, "nehV")) != -1) {
- switch (opt) {
- case 'n':
- opts->nonewline = true;
- break;
-
- case 'e':
- opts->unescape = true;
- break;
-
- case 'h':
- usage(stdout);
- exit(0);
-
- case 'V':
- drukkedoos_print_versie(stdout, "weerklank");
- exit(0);
-
- case '?':
- fprintf(stderr, "weerklank: Ongeldige optie: -%c\n", optopt);
- usage(stderr);
- exit(1);
- }
- }
-
- return argv + optind;
+ const struct option_spec spec[] = {
+ {'n', OPTION_SETBOOL(&opts->nonewline)},
+ {'e', OPTION_SETBOOL(&opts->unescape)},
+ {'h', OPTION_HELPUSAGE(usage_string)},
+ {'V', OPTION_VERSION()},
+ OPTION_SPEC_END
+ };
+
+ return option_parse(argc, argv, spec);
}
static const char lowercase_escapes[] = "\a\b\0\0\e\f\0\0\0\0\0\0\0\n\0\0\0\r\0\t\0\v\0\0\0\0";