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