summaryrefslogtreecommitdiff
path: root/src/util/option.c
blob: 2ad20d1e3b7ac094258413436c5aff5be6e92da8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#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;

    if (spec->kind == OPTSPECKIND_WITHARG) {
      *spec->spec_sub.witharg.dst = NULL;
    }
  }

  // option, option argument, or '--'.
  bool *is_option = calloc(argc, 1);

  // 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++) {
    DEBUG("opti = %d\n", opti);

    if (argv[opti][0] != '-') continue;  // skip non-options

    is_option[opti] = true;

    // 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 '--'

    bool skip_next_argument = false;

    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_WITHARG:
          if (argv[opti][j+1] != '\0') {
            fprintf(stderr, "%s: Optie -%c heeft een argument nodig, maar is niet de laatste op rij\n", argv[0], argv[opti][j]);
            exit(1);
          }

          if (opti + 1 < argc) {
            char **dst = spec->spec_sub.witharg.dst;
            if (*dst) free(*dst);  // override any previous value

            const size_t arglen = strlen(argv[opti+1]);
            *dst = malloc(arglen + 1);
            memcpy(*dst, argv[opti+1], arglen + 1);

            skip_next_argument = true;
          } else {
            fprintf(stderr, "%s: Optie -%c heeft een argument nodig\n", argv[0], argv[opti][j]);
            exit(1);
          }
          break;

        case OPTSPECKIND_HELPUSAGE:
          printf(spec->spec_sub.helpusage.usagestr, argv[0]);
          exit(0);

        case OPTSPECKIND_VERSION:
          drukkedoos_print_versie(stdout);
          exit(0);

        case OPTSPECKIND_END: abort(); // unreachable
      }
    }

    if (skip_next_argument) {
      // skip next argument, but mark it as an option
      last_option_index = ++opti;
      is_option[opti] = true;
    }
  }

  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++) {
    if (!is_option[i]) posargs[posi++] = argv[i];
  }

  // move option arguments to the beginning
  int noptargs = 0;
  for (int readi = 1; readi <= last_option_index; readi++) {
    if (is_option[readi]) {
      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);

  free(is_option);

  return argv + 1 + noptargs;
}