From bd22d0e47d0fb203286b088f048cf1aff1fa93a1 Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Mon, 22 Jul 2024 23:14:43 +0200 Subject: Nieuw optieontleedsysteem, en migreer weerklank --- src/util/option.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util/option.h | 76 ++++++++++++++++++++++++++++++++++++++ src/weerklank.c | 63 ++++++++++--------------------- 3 files changed, 204 insertions(+), 43 deletions(-) create mode 100644 src/util/option.c create mode 100644 src/util/option.h 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 +#include +#include +#include +#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 + + +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 \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 -#include #include -#include #include -#include -#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"; -- cgit v1.2.3-70-g09d2