argparse.c 10.3 KB
Newer Older
1
2
3
4
5
6
7
/**
 * Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
 * All rights reserved.
 *
 * Use of this source code is governed by a MIT-style license that can be found
 * in the LICENSE file.
 */
8
9
#include "../config.h"

10
11
12
#include "argparse.h"
#include <assert.h>
#include <errno.h>
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OPT_UNSET 1
18
#define OPT_LONG (1 << 1)
19

20
21
22
static const char *prefix_skip(const char *str, const char *prefix) {
  size_t len = strlen(prefix);
  return strncmp(str, prefix, len) ? NULL : str + len;
23
24
}

25
26
27
28
29
30
31
static int prefix_cmp(const char *str, const char *prefix) {
  for (;; str++, prefix++)
    if (!*prefix) {
      return 0;
    } else if (*str != *prefix) {
      return (unsigned char)*prefix - (unsigned char)*str;
    }
32
33
}

34
35
36
37
38
39
40
41
42
43
static void argparse_error(struct argparse *self,
                           const struct argparse_option *opt,
                           const char *reason, int flags) {
  (void)self;
  if (flags & OPT_LONG) {
    fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
  } else {
    fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
  }
  exit(1);
44
45
}

46
47
48
49
50
static int argparse_getvalue(struct argparse *self,
                             const struct argparse_option *opt, int flags) {
  const char *s = NULL;
  if (!opt->value) goto skipped;
  switch (opt->type) {
51
    case ARGPARSE_OPT_BOOLEAN:
52
53
54
55
56
57
58
59
60
      if (flags & OPT_UNSET) {
        *(int *)opt->value = *(int *)opt->value - 1;
      } else {
        *(int *)opt->value = *(int *)opt->value + 1;
      }
      if (*(int *)opt->value < 0) {
        *(int *)opt->value = 0;
      }
      break;
61
    case ARGPARSE_OPT_BIT:
62
63
64
65
66
67
      if (flags & OPT_UNSET) {
        *(int *)opt->value &= ~opt->data;
      } else {
        *(int *)opt->value |= opt->data;
      }
      break;
68
    case ARGPARSE_OPT_STRING:
69
70
71
72
73
74
75
76
77
78
      if (self->optvalue) {
        *(const char **)opt->value = self->optvalue;
        self->optvalue = NULL;
      } else if (self->argc > 1) {
        self->argc--;
        *(const char **)opt->value = *++self->argv;
      } else {
        argparse_error(self, opt, "requires a value", flags);
      }
      break;
79
    case ARGPARSE_OPT_INTEGER:
80
81
82
83
84
85
86
87
88
89
90
91
92
93
      errno = 0;
      if (self->optvalue) {
        *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
        self->optvalue = NULL;
      } else if (self->argc > 1) {
        self->argc--;
        *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
      } else {
        argparse_error(self, opt, "requires a value", flags);
      }
      if (errno) argparse_error(self, opt, strerror(errno), flags);
      if (s[0] != '\0')
        argparse_error(self, opt, "expects an integer value", flags);
      break;
94
    case ARGPARSE_OPT_FLOAT:
95
96
97
98
99
100
101
102
103
104
105
106
107
108
      errno = 0;
      if (self->optvalue) {
        *(float *)opt->value = strtof(self->optvalue, (char **)&s);
        self->optvalue = NULL;
      } else if (self->argc > 1) {
        self->argc--;
        *(float *)opt->value = strtof(*++self->argv, (char **)&s);
      } else {
        argparse_error(self, opt, "requires a value", flags);
      }
      if (errno) argparse_error(self, opt, strerror(errno), flags);
      if (s[0] != '\0')
        argparse_error(self, opt, "expects a numerical value", flags);
      break;
109
    default:
110
111
      assert(0);
  }
112
113

skipped:
114
115
116
  if (opt->callback) {
    return opt->callback(self, opt);
  }
117

118
  return 0;
119
120
}

121
122
123
124
125
126
127
128
129
130
131
132
133
134
static void argparse_options_check(const struct argparse_option *options) {
  for (; options->type != ARGPARSE_OPT_END; options++) {
    switch (options->type) {
      case ARGPARSE_OPT_END:
      case ARGPARSE_OPT_BOOLEAN:
      case ARGPARSE_OPT_BIT:
      case ARGPARSE_OPT_INTEGER:
      case ARGPARSE_OPT_FLOAT:
      case ARGPARSE_OPT_STRING:
      case ARGPARSE_OPT_GROUP:
        continue;
      default:
        fprintf(stderr, "wrong option type: %d", options->type);
        break;
135
    }
136
  }
137
138
}

139
140
141
142
143
144
static int argparse_short_opt(struct argparse *self,
                              const struct argparse_option *options) {
  for (; options->type != ARGPARSE_OPT_END; options++) {
    if (options->short_name == *self->optvalue) {
      self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
      return argparse_getvalue(self, options, 0);
145
    }
146
147
  }
  return -2;
148
149
}

150
151
152
153
154
155
static int argparse_long_opt(struct argparse *self,
                             const struct argparse_option *options) {
  for (; options->type != ARGPARSE_OPT_END; options++) {
    const char *rest;
    int opt_flags = 0;
    if (!options->long_name) continue;
156

157
158
159
160
161
162
163
164
165
166
167
    rest = prefix_skip(self->argv[0] + 2, options->long_name);
    if (!rest) {
      // negation disabled?
      if (options->flags & OPT_NONEG) {
        continue;
      }
      // only OPT_BOOLEAN/OPT_BIT supports negation
      if (options->type != ARGPARSE_OPT_BOOLEAN &&
          options->type != ARGPARSE_OPT_BIT) {
        continue;
      }
168

169
170
171
172
173
174
175
176
177
178
      if (prefix_cmp(self->argv[0] + 2, "no-")) {
        continue;
      }
      rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
      if (!rest) continue;
      opt_flags |= OPT_UNSET;
    }
    if (*rest) {
      if (*rest != '=') continue;
      self->optvalue = rest + 1;
179
    }
180
181
182
    return argparse_getvalue(self, options, opt_flags | OPT_LONG);
  }
  return -2;
183
184
}

185
186
187
188
189
190
191
192
193
int argparse_init(struct argparse *self, struct argparse_option *options,
                  const char *const *usages, int flags) {
  memset(self, 0, sizeof(*self));
  self->options = options;
  self->usages = usages;
  self->flags = flags;
  self->description = NULL;
  self->epilog = NULL;
  return 0;
194
195
}

196
197
198
199
void argparse_describe(struct argparse *self, const char *description,
                       const char *epilog) {
  self->description = description;
  self->epilog = epilog;
200
201
}

202
203
204
205
int argparse_parse(struct argparse *self, int argc, const char **argv) {
  self->argc = argc - 1;
  self->argv = argv + 1;
  self->out = argv;
206

207
  argparse_options_check(self->options);
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
  for (; self->argc; self->argc--, self->argv++) {
    const char *arg = self->argv[0];
    if (arg[0] != '-' || !arg[1]) {
      if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
        goto end;
      }
      // if it's not option or is a single char '-', copy verbatim
      self->out[self->cpidx++] = self->argv[0];
      continue;
    }
    // short option
    if (arg[1] != '-') {
      self->optvalue = arg + 1;
      switch (argparse_short_opt(self, self->options)) {
223
        case -1:
224
          break;
225
        case -2:
226
227
228
229
230
231
232
          goto unknown;
      }
      while (self->optvalue) {
        switch (argparse_short_opt(self, self->options)) {
          case -1:
            break;
          case -2:
233
234
            goto unknown;
        }
235
236
237
238
239
240
241
242
      }
      continue;
    }
    // if '--' presents
    if (!arg[2]) {
      self->argc--;
      self->argv++;
      break;
243
    }
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    // long option
    switch (argparse_long_opt(self, self->options)) {
      case -1:
        break;
      case -2:
        goto unknown;
    }
    continue;

  unknown:
    fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
    argparse_usage(self);
    exit(1);
  }
258
259

end:
260
261
  memmove(self->out + self->cpidx, self->argv, self->argc * sizeof(*self->out));
  self->out[self->cpidx + self->argc] = NULL;
262

263
  return self->cpidx + self->argc;
264
265
}

266
267
268
269
270
271
272
273
void argparse_usage(struct argparse *self) {
  if (self->usages) {
    fprintf(stdout, "Usage: %s\n", *self->usages++);
    while (*self->usages && **self->usages)
      fprintf(stdout, "   or: %s\n", *self->usages++);
  } else {
    fprintf(stdout, "Usage:\n");
  }
274

275
276
  // print description
  if (self->description) fprintf(stdout, "%s\n", self->description);
277

278
  fputc('\n', stdout);
279

280
  const struct argparse_option *options;
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
  // figure out best width
  size_t usage_opts_width = 0;
  size_t len;
  options = self->options;
  for (; options->type != ARGPARSE_OPT_END; options++) {
    len = 0;
    if ((options)->short_name) {
      len += 2;
    }
    if ((options)->short_name && (options)->long_name) {
      len += 2;  // separator ", "
    }
    if ((options)->long_name) {
      len += strlen((options)->long_name) + 2;
296
    }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
    if (options->type == ARGPARSE_OPT_INTEGER) {
      len += strlen("=<int>");
    }
    if (options->type == ARGPARSE_OPT_FLOAT) {
      len += strlen("=<flt>");
    } else if (options->type == ARGPARSE_OPT_STRING) {
      len += strlen("=<str>");
    }
    len = (len + 3) - ((len + 3) & 3);
    if (usage_opts_width < len) {
      usage_opts_width = len;
    }
  }
  usage_opts_width += 4;  // 4 spaces prefix
311

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
  options = self->options;
  for (; options->type != ARGPARSE_OPT_END; options++) {
    size_t pos = 0;
    int pad = 0;
    if (options->type == ARGPARSE_OPT_GROUP) {
      fputc('\n', stdout);
      fprintf(stdout, "%s", options->help);
      fputc('\n', stdout);
      continue;
    }
    pos = fprintf(stdout, "    ");
    if (options->short_name) {
      pos += fprintf(stdout, "-%c", options->short_name);
    }
    if (options->long_name && options->short_name) {
      pos += fprintf(stdout, ", ");
    }
    if (options->long_name) {
      pos += fprintf(stdout, "--%s", options->long_name);
    }
    if (options->type == ARGPARSE_OPT_INTEGER) {
      pos += fprintf(stdout, "=<int>");
    }
    if (options->type == ARGPARSE_OPT_FLOAT) {
      pos += fprintf(stdout, "=<flt>");
    } else if (options->type == ARGPARSE_OPT_STRING) {
      pos += fprintf(stdout, "=<str>");
    }
    if (pos <= usage_opts_width) {
      pad = usage_opts_width - pos;
    } else {
      fputc('\n', stdout);
      pad = usage_opts_width;
    }
    if (options->help != NULL && strlen(options->help) > 0) {
      char *str = strdup(options->help);
      char *token = strtok(str, " ");
      fprintf(stdout, "%*s%s ", pad + 2, "", token);
      int count = strlen(token);
      int dangling = 1;
      while ((token = strtok(NULL, " ")) != NULL) {
        if (count == 0) {
          fprintf(stdout, "%*s", (int)pos + pad + 2, "");
          dangling = 1;
356
        }
357
358
359
360
361
362
        printf("%s ", token);
        count += strlen(token);
        if (count > 30) {
          count = 0;
          fprintf(stdout, "\n");
          dangling = 0;
363
        }
364
365
      }
      if (dangling) fprintf(stdout, "\n");
366
      free(str);
367
368
    } else {
      fprintf(stdout, "\n");
369
    }
370
  }
371

372
373
  // print epilog
  if (self->epilog) fprintf(stdout, "%s\n", self->epilog);
374
375
}

376
377
378
379
380
int argparse_help_cb(struct argparse *self,
                     const struct argparse_option *option) {
  (void)option;
  argparse_usage(self);
  exit(0);
381
}