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
#include "argparse.h"
Peter W. Draper's avatar
Peter W. Draper committed
11

12
13
#include <assert.h>
#include <errno.h>
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

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

26
27
28
29
30
31
32
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;
    }
33
34
}

35
36
37
38
39
40
41
42
43
44
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);
45
46
}

47
48
49
50
51
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) {
52
    case ARGPARSE_OPT_BOOLEAN:
53
54
55
56
57
58
59
60
61
      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;
62
    case ARGPARSE_OPT_BIT:
63
64
65
66
67
68
      if (flags & OPT_UNSET) {
        *(int *)opt->value &= ~opt->data;
      } else {
        *(int *)opt->value |= opt->data;
      }
      break;
69
    case ARGPARSE_OPT_STRING:
70
71
72
73
74
75
76
77
78
79
      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;
80
    case ARGPARSE_OPT_INTEGER:
81
82
83
84
85
86
87
88
89
90
91
92
93
94
      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;
95
    case ARGPARSE_OPT_FLOAT:
96
97
98
99
100
101
102
103
104
105
106
107
108
109
      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;
110
    default:
111
112
      assert(0);
  }
113
114

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

119
  return 0;
120
121
}

122
123
124
125
126
127
128
129
130
131
132
133
134
135
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;
136
    }
137
  }
138
139
}

140
141
142
143
144
145
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);
146
    }
147
148
  }
  return -2;
149
150
}

151
152
153
154
155
156
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;
157

158
159
160
161
162
163
164
165
166
167
168
    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;
      }
169

170
171
172
173
174
175
176
177
178
179
      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;
180
    }
181
182
183
    return argparse_getvalue(self, options, opt_flags | OPT_LONG);
  }
  return -2;
184
185
}

186
187
188
189
190
191
192
193
194
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;
195
196
}

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

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

208
  argparse_options_check(self->options);
209

210
211
212
213
214
215
216
217
218
219
220
221
222
223
  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)) {
224
        case -1:
225
          break;
226
        case -2:
227
228
229
230
231
232
233
          goto unknown;
      }
      while (self->optvalue) {
        switch (argparse_short_opt(self, self->options)) {
          case -1:
            break;
          case -2:
234
235
            goto unknown;
        }
236
237
238
239
240
241
242
243
      }
      continue;
    }
    // if '--' presents
    if (!arg[2]) {
      self->argc--;
      self->argv++;
      break;
244
    }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
    // 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);
  }
259
260

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

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

267
268
269
270
271
272
273
274
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");
  }
275

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

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

281
  const struct argparse_option *options;
282

283
284
285
286
287
288
289
290
291
292
293
294
295
296
  // 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;
297
    }
298
299
300
301
302
303
304
305
306
307
308
309
310
311
    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
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
356
  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;
357
        }
358
359
360
361
362
363
        printf("%s ", token);
        count += strlen(token);
        if (count > 30) {
          count = 0;
          fprintf(stdout, "\n");
          dangling = 0;
364
        }
365
366
      }
      if (dangling) fprintf(stdout, "\n");
367
      free(str);
368
369
    } else {
      fprintf(stdout, "\n");
370
    }
371
  }
372

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

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