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 | |
18 | static const char * |
19 | prefix_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 | |
25 | static int |
26 | prefix_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 | |
36 | static void |
37 | argparse_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 | |
49 | static int |
50 | argparse_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 | |
121 | skipped: |
122 | if (opt->callback) { |
123 | return opt->callback(self, opt); |
124 | } |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static void |
130 | argparse_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 | |
149 | static int |
150 | argparse_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 | |
161 | static int |
162 | argparse_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 | |
200 | int |
201 | argparse_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 | |
213 | void |
214 | argparse_describe(struct argparse *self, const char *description, |
215 | const char *epilog) |
216 | { |
217 | self->description = description; |
218 | self->epilog = epilog; |
219 | } |
220 | |
221 | int |
222 | argparse_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 | |
274 | unknown: |
275 | fprintf(stderr, "error: unknown option `%s`\n" , self->argv[0]); |
276 | argparse_usage(self); |
277 | exit(1); |
278 | } |
279 | |
280 | end: |
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 | |
288 | void |
289 | argparse_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 | |
378 | int |
379 | argparse_help_cb(struct argparse *self, const struct argparse_option *option) |
380 | { |
381 | (void)option; |
382 | argparse_usage(self); |
383 | exit(0); |
384 | } |
385 | |