argpar.h: `BABELTRACE_ARGPAR_H` -> `ARGPAR_ARGPAR_H`
[argpar.git] / argpar / argpar.c
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
5 * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
6 */
7
8 #include <assert.h>
9 #include <stdarg.h>
10 #include <stdbool.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include "argpar.h"
16
17 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
18 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
19
20 #define ARGPAR_CALLOC(_type, _nmemb) \
21 ((_type *) calloc((_nmemb), sizeof(_type)))
22
23 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
24
25 #define ARGPAR_ASSERT(_cond) assert(_cond)
26
27 #ifdef __MINGW_PRINTF_FORMAT
28 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
29 #else
30 # define ARGPAR_PRINTF_FORMAT printf
31 #endif
32
33 /*
34 * An argpar iterator.
35 *
36 * Such a structure contains the state of an iterator between
37 * calls to argpar_iter_parse_next().
38 */
39 struct argpar_iter {
40 /*
41 * Data provided by the user to argpar_iter_create(); immutable
42 * afterwards.
43 */
44 unsigned int argc;
45 const char * const *argv;
46 const struct argpar_opt_descr *descrs;
47
48 /*
49 * Index of the argument to process in the next
50 * argpar_iter_parse_next() call.
51 */
52 unsigned int i;
53
54 /* Counter of non-option arguments */
55 int non_opt_index;
56
57 /*
58 * Current character of the current short option group: if it's
59 * not `NULL`, the parser is in within a short option group,
60 * therefore it must resume there in the next
61 * argpar_iter_parse_next() call.
62 */
63 const char *short_opt_ch;
64 };
65
66 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
67 char *argpar_vasprintf(const char * const fmt, va_list args)
68 {
69 int len1, len2;
70 char *str;
71 va_list args2;
72
73 va_copy(args2, args);
74 len1 = vsnprintf(NULL, 0, fmt, args);
75 if (len1 < 0) {
76 str = NULL;
77 goto end;
78 }
79
80 str = malloc(len1 + 1);
81 if (!str) {
82 goto end;
83 }
84
85 len2 = vsnprintf(str, len1 + 1, fmt, args2);
86 ARGPAR_ASSERT(len1 == len2);
87
88 end:
89 va_end(args2);
90 return str;
91 }
92
93
94 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
95 char *argpar_asprintf(const char * const fmt, ...)
96 {
97 va_list args;
98 char *str;
99
100 va_start(args, fmt);
101 str = argpar_vasprintf(fmt, args);
102 va_end(args);
103 return str;
104 }
105
106 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
107 bool append_string_printf(char ** const str, const char *fmt, ...)
108 {
109 char *new_str = NULL;
110 char *addendum;
111 bool success;
112 va_list args;
113
114 ARGPAR_ASSERT(str);
115 va_start(args, fmt);
116 addendum = argpar_vasprintf(fmt, args);
117 va_end(args);
118
119 if (!addendum) {
120 success = false;
121 goto end;
122 }
123
124 new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum);
125 if (!new_str) {
126 success = false;
127 goto end;
128 }
129
130 free(*str);
131 *str = new_str;
132 success = true;
133
134 end:
135 free(addendum);
136 return success;
137 }
138
139 ARGPAR_HIDDEN
140 void argpar_item_destroy(const struct argpar_item * const item)
141 {
142 if (!item) {
143 goto end;
144 }
145
146 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
147 struct argpar_item_opt * const opt_item =
148 (struct argpar_item_opt *) item;
149
150 free((void *) opt_item->arg);
151 }
152
153 free((void *) item);
154
155 end:
156 return;
157 }
158
159 static
160 bool push_item(struct argpar_item_array * const array,
161 const struct argpar_item * const item)
162 {
163 bool success;
164
165 ARGPAR_ASSERT(array);
166 ARGPAR_ASSERT(item);
167
168 if (array->n_items == array->n_alloc) {
169 const unsigned int new_n_alloc = array->n_alloc * 2;
170 const struct argpar_item ** const new_items =
171 ARGPAR_REALLOC(array->items, const struct argpar_item *,
172 new_n_alloc);
173 if (!new_items) {
174 success = false;
175 goto end;
176 }
177
178 array->n_alloc = new_n_alloc;
179 array->items = new_items;
180 }
181
182 array->items[array->n_items] = item;
183 array->n_items++;
184 success = true;
185
186 end:
187 return success;
188 }
189
190 static
191 void destroy_item_array(struct argpar_item_array * const array)
192 {
193 if (array) {
194 unsigned int i;
195
196 for (i = 0; i < array->n_items; i++) {
197 argpar_item_destroy(array->items[i]);
198 }
199
200 free(array->items);
201 free(array);
202 }
203 }
204
205 static
206 struct argpar_item_array *create_item_array(void)
207 {
208 struct argpar_item_array *ret;
209 const int initial_size = 10;
210
211 ret = ARGPAR_ZALLOC(struct argpar_item_array);
212 if (!ret) {
213 goto end;
214 }
215
216 ret->items = ARGPAR_CALLOC(const struct argpar_item *, initial_size);
217 if (!ret->items) {
218 goto error;
219 }
220
221 ret->n_alloc = initial_size;
222 goto end;
223
224 error:
225 destroy_item_array(ret);
226 ret = NULL;
227
228 end:
229 return ret;
230 }
231
232 static
233 struct argpar_item_opt *create_opt_item(
234 const struct argpar_opt_descr * const descr,
235 const char * const arg)
236 {
237 struct argpar_item_opt *opt_item =
238 ARGPAR_ZALLOC(struct argpar_item_opt);
239
240 if (!opt_item) {
241 goto end;
242 }
243
244 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
245 opt_item->descr = descr;
246
247 if (arg) {
248 opt_item->arg = strdup(arg);
249 if (!opt_item->arg) {
250 goto error;
251 }
252 }
253
254 goto end;
255
256 error:
257 argpar_item_destroy(&opt_item->base);
258 opt_item = NULL;
259
260 end:
261 return opt_item;
262 }
263
264 static
265 struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
266 const unsigned int orig_index,
267 const unsigned int non_opt_index)
268 {
269 struct argpar_item_non_opt * const non_opt_item =
270 ARGPAR_ZALLOC(struct argpar_item_non_opt);
271
272 if (!non_opt_item) {
273 goto end;
274 }
275
276 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
277 non_opt_item->arg = arg;
278 non_opt_item->orig_index = orig_index;
279 non_opt_item->non_opt_index = non_opt_index;
280
281 end:
282 return non_opt_item;
283 }
284
285 static
286 const struct argpar_opt_descr *find_descr(
287 const struct argpar_opt_descr * const descrs,
288 const char short_name, const char * const long_name)
289 {
290 const struct argpar_opt_descr *descr;
291
292 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
293 if (short_name && descr->short_name &&
294 short_name == descr->short_name) {
295 goto end;
296 }
297
298 if (long_name && descr->long_name &&
299 strcmp(long_name, descr->long_name) == 0) {
300 goto end;
301 }
302 }
303
304 end:
305 return !descr->short_name && !descr->long_name ? NULL : descr;
306 }
307
308 enum parse_orig_arg_opt_ret {
309 PARSE_ORIG_ARG_OPT_RET_OK,
310 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
311 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
312 };
313
314 static
315 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
316 const char * const next_orig_arg,
317 const struct argpar_opt_descr * const descrs,
318 struct argpar_iter * const iter,
319 char ** const error, struct argpar_item ** const item)
320 {
321 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
322 bool used_next_orig_arg = false;
323 const char *opt_arg = NULL;
324 const struct argpar_opt_descr *descr;
325 struct argpar_item_opt *opt_item;
326
327 if (strlen(short_opts) == 0) {
328 append_string_printf(error, "Invalid argument");
329 goto error;
330 }
331
332 if (!iter->short_opt_ch) {
333 iter->short_opt_ch = short_opts;
334 }
335
336 /* Find corresponding option descriptor */
337 descr = find_descr(descrs, *iter->short_opt_ch, NULL);
338 if (!descr) {
339 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
340 append_string_printf(error, "Unknown option `-%c`",
341 *iter->short_opt_ch);
342 goto error;
343 }
344
345 if (descr->with_arg) {
346 if (iter->short_opt_ch[1]) {
347 /* `-oarg` form */
348 opt_arg = &iter->short_opt_ch[1];
349 } else {
350 /* `-o arg` form */
351 opt_arg = next_orig_arg;
352 used_next_orig_arg = true;
353 }
354
355 /*
356 * We accept `-o ''` (empty option argument), but not
357 * `-o` alone if an option argument is expected.
358 */
359 if (!opt_arg || (iter->short_opt_ch[1] &&
360 strlen(opt_arg) == 0)) {
361 append_string_printf(error,
362 "Missing required argument for option `-%c`",
363 *iter->short_opt_ch);
364 used_next_orig_arg = false;
365 goto error;
366 }
367 }
368
369 /* Create and append option argument */
370 opt_item = create_opt_item(descr, opt_arg);
371 if (!opt_item) {
372 goto error;
373 }
374
375 *item = &opt_item->base;
376 iter->short_opt_ch++;
377
378 if (descr->with_arg || !*iter->short_opt_ch) {
379 /* Option has an argument: no more options */
380 iter->short_opt_ch = NULL;
381
382 if (used_next_orig_arg) {
383 iter->i += 2;
384 } else {
385 iter->i++;
386 }
387 }
388
389 goto end;
390
391 error:
392 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
393 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
394 }
395
396 end:
397 return ret;
398 }
399
400 static
401 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
402 const char * const next_orig_arg,
403 const struct argpar_opt_descr * const descrs,
404 struct argpar_iter * const iter,
405 char ** const error, struct argpar_item ** const item)
406 {
407 const size_t max_len = 127;
408 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
409 const struct argpar_opt_descr *descr;
410 struct argpar_item_opt *opt_item;
411 bool used_next_orig_arg = false;
412
413 /* Option's argument, if any */
414 const char *opt_arg = NULL;
415
416 /* Position of first `=`, if any */
417 const char *eq_pos;
418
419 /* Buffer holding option name when `long_opt_arg` contains `=` */
420 char buf[max_len + 1];
421
422 /* Option name */
423 const char *long_opt_name = long_opt_arg;
424
425 if (strlen(long_opt_arg) == 0) {
426 append_string_printf(error, "Invalid argument");
427 goto error;
428 }
429
430 /* Find the first `=` in original argument */
431 eq_pos = strchr(long_opt_arg, '=');
432 if (eq_pos) {
433 const size_t long_opt_name_size = eq_pos - long_opt_arg;
434
435 /* Isolate the option name */
436 if (long_opt_name_size > max_len) {
437 append_string_printf(error, "Invalid argument `--%s`",
438 long_opt_arg);
439 goto error;
440 }
441
442 memcpy(buf, long_opt_arg, long_opt_name_size);
443 buf[long_opt_name_size] = '\0';
444 long_opt_name = buf;
445 }
446
447 /* Find corresponding option descriptor */
448 descr = find_descr(descrs, '\0', long_opt_name);
449 if (!descr) {
450 append_string_printf(error, "Unknown option `--%s`",
451 long_opt_name);
452 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
453 goto error;
454 }
455
456 /* Find option's argument if any */
457 if (descr->with_arg) {
458 if (eq_pos) {
459 /* `--long-opt=arg` style */
460 opt_arg = eq_pos + 1;
461 } else {
462 /* `--long-opt arg` style */
463 if (!next_orig_arg) {
464 append_string_printf(error,
465 "Missing required argument for option `--%s`",
466 long_opt_name);
467 goto error;
468 }
469
470 opt_arg = next_orig_arg;
471 used_next_orig_arg = true;
472 }
473 } else if (eq_pos) {
474 /*
475 * Unexpected `--opt=arg` style for a long option which
476 * doesn't accept an argument.
477 */
478 append_string_printf(error,
479 "Unexpected argument for option `--%s`", long_opt_name);
480 goto error;
481 }
482
483 /* Create and append option argument */
484 opt_item = create_opt_item(descr, opt_arg);
485 if (!opt_item) {
486 goto error;
487 }
488
489 if (used_next_orig_arg) {
490 iter->i += 2;
491 } else {
492 iter->i++;
493 }
494
495 *item = &opt_item->base;
496 goto end;
497
498 error:
499 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
500 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
501 }
502
503 end:
504 return ret;
505 }
506
507 static
508 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
509 const char * const next_orig_arg,
510 const struct argpar_opt_descr * const descrs,
511 struct argpar_iter * const iter, char ** const error,
512 struct argpar_item ** const item)
513 {
514 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
515
516 ARGPAR_ASSERT(orig_arg[0] == '-');
517
518 if (orig_arg[1] == '-') {
519 /* Long option */
520 ret = parse_long_opt(&orig_arg[2],
521 next_orig_arg, descrs, iter, error, item);
522 } else {
523 /* Short option */
524 ret = parse_short_opts(&orig_arg[1],
525 next_orig_arg, descrs, iter, error, item);
526 }
527
528 return ret;
529 }
530
531 static
532 bool prepend_while_parsing_arg_to_error(char ** const error,
533 const unsigned int i, const char * const arg)
534 {
535 char *new_error;
536 bool success;
537
538 ARGPAR_ASSERT(error);
539 ARGPAR_ASSERT(*error);
540 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
541 i + 1, arg, *error);
542 if (!new_error) {
543 success = false;
544 goto end;
545 }
546
547 free(*error);
548 *error = new_error;
549 success = true;
550
551 end:
552 return success;
553 }
554
555 ARGPAR_HIDDEN
556 struct argpar_iter *argpar_iter_create(const unsigned int argc,
557 const char * const * const argv,
558 const struct argpar_opt_descr * const descrs)
559 {
560 struct argpar_iter * const iter = ARGPAR_ZALLOC(struct argpar_iter);
561
562 if (!iter) {
563 goto end;
564 }
565
566 iter->argc = argc;
567 iter->argv = argv;
568 iter->descrs = descrs;
569
570 end:
571 return iter;
572 }
573
574 ARGPAR_HIDDEN
575 void argpar_iter_destroy(struct argpar_iter * const iter)
576 {
577 free(iter);
578 }
579
580 ARGPAR_HIDDEN
581 enum argpar_iter_parse_next_status argpar_iter_parse_next(
582 struct argpar_iter * const iter,
583 const struct argpar_item ** const item, char ** const error)
584 {
585 enum argpar_iter_parse_next_status status;
586 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
587 const char *orig_arg;
588 const char *next_orig_arg;
589
590 ARGPAR_ASSERT(iter->i <= iter->argc);
591 *error = NULL;
592
593 if (iter->i == iter->argc) {
594 status = ARGPAR_ITER_PARSE_NEXT_STATUS_END;
595 goto end;
596 }
597
598 orig_arg = iter->argv[iter->i];
599 next_orig_arg =
600 iter->i < (iter->argc - 1) ? iter->argv[iter->i + 1] : NULL;
601
602 if (orig_arg[0] != '-') {
603 /* Non-option argument */
604 struct argpar_item_non_opt * const non_opt_item =
605 create_non_opt_item(orig_arg, iter->i,
606 iter->non_opt_index);
607
608 if (!non_opt_item) {
609 status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR;
610 goto end;
611 }
612
613 iter->non_opt_index++;
614 iter->i++;
615 *item = &non_opt_item->base;
616 status = ARGPAR_ITER_PARSE_NEXT_STATUS_OK;
617 goto end;
618 }
619
620 /* Option argument */
621 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
622 next_orig_arg, iter->descrs, iter, error,
623 (struct argpar_item **) item);
624 switch (parse_orig_arg_opt_ret) {
625 case PARSE_ORIG_ARG_OPT_RET_OK:
626 status = ARGPAR_ITER_PARSE_NEXT_STATUS_OK;
627 break;
628 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
629 prepend_while_parsing_arg_to_error(error, iter->i, orig_arg);
630 status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT;
631 break;
632 case PARSE_ORIG_ARG_OPT_RET_ERROR:
633 prepend_while_parsing_arg_to_error(error, iter->i, orig_arg);
634 status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR;
635 break;
636 default:
637 abort();
638 }
639
640 end:
641 return status;
642 }
643
644 ARGPAR_HIDDEN
645 unsigned int argpar_iter_get_ingested_orig_args(
646 const struct argpar_iter * const iter)
647 {
648 return iter->i;
649 }
650
651 ARGPAR_HIDDEN
652 struct argpar_parse_ret argpar_parse(const unsigned int argc,
653 const char * const * const argv,
654 const struct argpar_opt_descr * const descrs,
655 const bool fail_on_unknown_opt)
656 {
657 struct argpar_parse_ret parse_ret = { 0 };
658 const struct argpar_item *item = NULL;
659 struct argpar_iter *iter = NULL;
660
661 parse_ret.items = create_item_array();
662 if (!parse_ret.items) {
663 parse_ret.error = strdup("Failed to create items array.");
664 ARGPAR_ASSERT(parse_ret.error);
665 goto error;
666 }
667
668 iter = argpar_iter_create(argc, argv, descrs);
669 if (!iter) {
670 parse_ret.error = strdup("Failed to create argpar iter.");
671 ARGPAR_ASSERT(parse_ret.error);
672 goto error;
673 }
674
675 while (true) {
676 const enum argpar_iter_parse_next_status status =
677 argpar_iter_parse_next(iter, &item, &parse_ret.error);
678
679 if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR) {
680 goto error;
681 } else if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_END) {
682 break;
683 } else if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
684 if (fail_on_unknown_opt) {
685 parse_ret.ingested_orig_args =
686 argpar_iter_get_ingested_orig_args(iter);
687 goto error;
688 }
689
690 free(parse_ret.error);
691 parse_ret.error = NULL;
692 break;
693 }
694
695 ARGPAR_ASSERT(status == ARGPAR_ITER_PARSE_NEXT_STATUS_OK);
696
697 if (!push_item(parse_ret.items, item)) {
698 goto error;
699 }
700
701 item = NULL;
702 }
703
704 ARGPAR_ASSERT(!parse_ret.error);
705 parse_ret.ingested_orig_args = argpar_iter_get_ingested_orig_args(iter);
706 goto end;
707
708 error:
709 ARGPAR_ASSERT(parse_ret.error);
710
711 /* That's how we indicate that an error occurred */
712 destroy_item_array(parse_ret.items);
713 parse_ret.items = NULL;
714
715 end:
716 argpar_iter_destroy(iter);
717 argpar_item_destroy(item);
718 return parse_ret;
719 }
720
721 ARGPAR_HIDDEN
722 void argpar_parse_ret_fini(struct argpar_parse_ret * const ret)
723 {
724 ARGPAR_ASSERT(ret);
725 destroy_item_array(ret->items);
726 ret->items = NULL;
727 free(ret->error);
728 ret->error = NULL;
729 }
This page took 0.044499 seconds and 4 git commands to generate.