diff options
| author | Tom Smeding <tom@tomsmeding.com> | 2024-07-22 23:14:43 +0200 | 
|---|---|---|
| committer | Tom Smeding <tom@tomsmeding.com> | 2024-07-22 23:14:43 +0200 | 
| commit | bd22d0e47d0fb203286b088f048cf1aff1fa93a1 (patch) | |
| tree | 9cc02c299e142aa80975c188927ea8b99221829a | |
| parent | 9794094a5b3ac975d661643fc0bde6f6340ceafb (diff) | |
Nieuw optieontleedsysteem, en migreer weerklank
| -rw-r--r-- | src/util/option.c | 108 | ||||
| -rw-r--r-- | src/util/option.h | 76 | ||||
| -rw-r--r-- | src/weerklank.c | 59 | 
3 files changed, 202 insertions, 41 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" +#include "util/option.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"); -} +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); -    } -  } +  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 argv + optind; +  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";  | 
