1/**
2 * Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
3 * All rights reserved.
4 *
5 * Use of this source code is governed by a MIT-style license that can be found
6 * in the LICENSE file.
7 */
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <assert.h>
12#include <errno.h>
13#include "argparse.h"
14
15#define OPT_UNSET 1
16#define OPT_LONG (1 << 1)
17
18static const char *
19prefix_skip(const char *str, const char *prefix)
20{
21 size_t len = strlen(prefix);
22 return strncmp(str, prefix, len) ? NULL : str + len;
23}
24
25static int
26prefix_cmp(const char *str, const char *prefix)
27{
28 for (;; str++, prefix++)
29 if (!*prefix) {
30 return 0;
31 } else if (*str != *prefix) {
32 return (unsigned char)*prefix - (unsigned char)*str;
33 }
34}
35
36static void
37argparse_error(struct argparse *self, const struct argparse_option *opt,
38 const char *reason, int flags)
39{
40 (void)self;
41 if (flags & OPT_LONG) {
42 fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
43 } else {
44 fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
45 }
46 exit(1);
47}
48
49static int
50argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
51 int flags)
52{
53 const char *s = NULL;
54 if (!opt->value)
55 goto skipped;
56 switch (opt->type) {
57 case ARGPARSE_OPT_BOOLEAN:
58 if (flags & OPT_UNSET) {
59 *(int *)opt->value = *(int *)opt->value - 1;
60 } else {
61 *(int *)opt->value = *(int *)opt->value + 1;
62 }
63 if (*(int *)opt->value < 0) {
64 *(int *)opt->value = 0;
65 }
66 break;
67 case ARGPARSE_OPT_BIT:
68 if (flags & OPT_UNSET) {
69 *(int *)opt->value &= ~opt->data;
70 } else {
71 *(int *)opt->value |= opt->data;
72 }
73 break;
74 case ARGPARSE_OPT_STRING:
75 if (self->optvalue) {
76 *(const char **)opt->value = self->optvalue;
77 self->optvalue = NULL;
78 } else if (self->argc > 1) {
79 self->argc--;
80 *(const char **)opt->value = *++self->argv;
81 } else {
82 argparse_error(self, opt, "requires a value", flags);
83 }
84 break;
85 case ARGPARSE_OPT_INTEGER:
86 errno = 0;
87 if (self->optvalue) {
88 *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
89 self->optvalue = NULL;
90 } else if (self->argc > 1) {
91 self->argc--;
92 *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
93 } else {
94 argparse_error(self, opt, "requires a value", flags);
95 }
96 if (errno)
97 argparse_error(self, opt, strerror(errno), flags);
98 if (s[0] != '\0')
99 argparse_error(self, opt, "expects an integer value", flags);
100 break;
101 case ARGPARSE_OPT_FLOAT:
102 errno = 0;
103 if (self->optvalue) {
104 *(float *)opt->value = strtof(self->optvalue, (char **)&s);
105 self->optvalue = NULL;
106 } else if (self->argc > 1) {
107 self->argc--;
108 *(float *)opt->value = strtof(*++self->argv, (char **)&s);
109 } else {
110 argparse_error(self, opt, "requires a value", flags);
111 }
112 if (errno)
113 argparse_error(self, opt, strerror(errno), flags);
114 if (s[0] != '\0')
115 argparse_error(self, opt, "expects a numerical value", flags);
116 break;
117 default:
118 assert(0);
119 }
120
121skipped:
122 if (opt->callback) {
123 return opt->callback(self, opt);
124 }
125
126 return 0;
127}
128
129static void
130argparse_options_check(const struct argparse_option *options)
131{
132 for (; options->type != ARGPARSE_OPT_END; options++) {
133 switch (options->type) {
134 case ARGPARSE_OPT_END:
135 case ARGPARSE_OPT_BOOLEAN:
136 case ARGPARSE_OPT_BIT:
137 case ARGPARSE_OPT_INTEGER:
138 case ARGPARSE_OPT_FLOAT:
139 case ARGPARSE_OPT_STRING:
140 case ARGPARSE_OPT_GROUP:
141 continue;
142 default:
143 fprintf(stderr, "wrong option type: %d", options->type);
144 break;
145 }
146 }
147}
148
149static int
150argparse_short_opt(struct argparse *self, const struct argparse_option *options)
151{
152 for (; options->type != ARGPARSE_OPT_END; options++) {
153 if (options->short_name == *self->optvalue) {
154 self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
155 return argparse_getvalue(self, options, 0);
156 }
157 }
158 return -2;
159}
160
161static int
162argparse_long_opt(struct argparse *self, const struct argparse_option *options)
163{
164 for (; options->type != ARGPARSE_OPT_END; options++) {
165 const char *rest;
166 int opt_flags = 0;
167 if (!options->long_name)
168 continue;
169
170 rest = prefix_skip(self->argv[0] + 2, options->long_name);
171 if (!rest) {
172 // negation disabled?
173 if (options->flags & OPT_NONEG) {
174 continue;
175 }
176 // only OPT_BOOLEAN/OPT_BIT supports negation
177 if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
178 ARGPARSE_OPT_BIT) {
179 continue;
180 }
181
182 if (prefix_cmp(self->argv[0] + 2, "no-")) {
183 continue;
184 }
185 rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
186 if (!rest)
187 continue;
188 opt_flags |= OPT_UNSET;
189 }
190 if (*rest) {
191 if (*rest != '=')
192 continue;
193 self->optvalue = rest + 1;
194 }
195 return argparse_getvalue(self, options, opt_flags | OPT_LONG);
196 }
197 return -2;
198}
199
200int
201argparse_init(struct argparse *self, struct argparse_option *options,
202 const char *const *usages, int flags)
203{
204 memset(self, 0, sizeof(*self));
205 self->options = options;
206 self->usages = usages;
207 self->flags = flags;
208 self->description = NULL;
209 self->epilog = NULL;
210 return 0;
211}
212
213void
214argparse_describe(struct argparse *self, const char *description,
215 const char *epilog)
216{
217 self->description = description;
218 self->epilog = epilog;
219}
220
221int
222argparse_parse(struct argparse *self, int argc, const char **argv)
223{
224 self->argc = argc - 1;
225 self->argv = argv + 1;
226 self->out = argv;
227
228 argparse_options_check(self->options);
229
230 for (; self->argc; self->argc--, self->argv++) {
231 const char *arg = self->argv[0];
232 if (arg[0] != '-' || !arg[1]) {
233 if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
234 goto end;
235 }
236 // if it's not option or is a single char '-', copy verbatim
237 self->out[self->cpidx++] = self->argv[0];
238 continue;
239 }
240 // short option
241 if (arg[1] != '-') {
242 self->optvalue = arg + 1;
243 switch (argparse_short_opt(self, self->options)) {
244 case -1:
245 break;
246 case -2:
247 goto unknown;
248 }
249 while (self->optvalue) {
250 switch (argparse_short_opt(self, self->options)) {
251 case -1:
252 break;
253 case -2:
254 goto unknown;
255 }
256 }
257 continue;
258 }
259 // if '--' presents
260 if (!arg[2]) {
261 self->argc--;
262 self->argv++;
263 break;
264 }
265 // long option
266 switch (argparse_long_opt(self, self->options)) {
267 case -1:
268 break;
269 case -2:
270 goto unknown;
271 }
272 continue;
273
274unknown:
275 fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
276 argparse_usage(self);
277 exit(1);
278 }
279
280end:
281 memmove(self->out + self->cpidx, self->argv,
282 self->argc * sizeof(*self->out));
283 self->out[self->cpidx + self->argc] = NULL;
284
285 return self->cpidx + self->argc;
286}
287
288void
289argparse_usage(struct argparse *self)
290{
291 if (self->usages) {
292 fprintf(stdout, "Usage: %s\n", *self->usages++);
293 while (*self->usages && **self->usages)
294 fprintf(stdout, " or: %s\n", *self->usages++);
295 } else {
296 fprintf(stdout, "Usage:\n");
297 }
298
299 // print description
300 if (self->description)
301 fprintf(stdout, "%s\n", self->description);
302
303 fputc('\n', stdout);
304
305 const struct argparse_option *options;
306
307 // figure out best width
308 size_t usage_opts_width = 0;
309 size_t len;
310 options = self->options;
311 for (; options->type != ARGPARSE_OPT_END; options++) {
312 len = 0;
313 if ((options)->short_name) {
314 len += 2;
315 }
316 if ((options)->short_name && (options)->long_name) {
317 len += 2; // separator ", "
318 }
319 if ((options)->long_name) {
320 len += strlen((options)->long_name) + 2;
321 }
322 if (options->type == ARGPARSE_OPT_INTEGER) {
323 len += strlen("=<int>");
324 }
325 if (options->type == ARGPARSE_OPT_FLOAT) {
326 len += strlen("=<flt>");
327 } else if (options->type == ARGPARSE_OPT_STRING) {
328 len += strlen("=<str>");
329 }
330 len = (len + 3) - ((len + 3) & 3);
331 if (usage_opts_width < len) {
332 usage_opts_width = len;
333 }
334 }
335 usage_opts_width += 4; // 4 spaces prefix
336
337 options = self->options;
338 for (; options->type != ARGPARSE_OPT_END; options++) {
339 size_t pos = 0;
340 int pad = 0;
341 if (options->type == ARGPARSE_OPT_GROUP) {
342 fputc('\n', stdout);
343 fprintf(stdout, "%s", options->help);
344 fputc('\n', stdout);
345 continue;
346 }
347 pos = fprintf(stdout, " ");
348 if (options->short_name) {
349 pos += fprintf(stdout, "-%c", options->short_name);
350 }
351 if (options->long_name && options->short_name) {
352 pos += fprintf(stdout, ", ");
353 }
354 if (options->long_name) {
355 pos += fprintf(stdout, "--%s", options->long_name);
356 }
357 if (options->type == ARGPARSE_OPT_INTEGER) {
358 pos += fprintf(stdout, "=<int>");
359 } else if (options->type == ARGPARSE_OPT_FLOAT) {
360 pos += fprintf(stdout, "=<flt>");
361 } else if (options->type == ARGPARSE_OPT_STRING) {
362 pos += fprintf(stdout, "=<str>");
363 }
364 if (pos <= usage_opts_width) {
365 pad = usage_opts_width - pos;
366 } else {
367 fputc('\n', stdout);
368 pad = usage_opts_width;
369 }
370 fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
371 }
372
373 // print epilog
374 if (self->epilog)
375 fprintf(stdout, "%s\n", self->epilog);
376}
377
378int
379argparse_help_cb(struct argparse *self, const struct argparse_option *option)
380{
381 (void)option;
382 argparse_usage(self);
383 exit(0);
384}
385