2 * SPDX-License-Identifier: MIT
4 * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
5 * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
17 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
18 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
20 #define ARGPAR_CALLOC(_type, _nmemb) \
21 ((_type *) calloc((_nmemb), sizeof(_type)))
23 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
25 #define ARGPAR_ASSERT(_cond) assert(_cond)
27 #ifdef __MINGW_PRINTF_FORMAT
28 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
30 # define ARGPAR_PRINTF_FORMAT printf
36 * Such a structure contains the state of an iterator between
37 * calls to argpar_iter_parse_next().
41 * Data provided by the user to argpar_iter_create(); immutable
45 const char * const *argv
;
46 const struct argpar_opt_descr
*descrs
;
49 * Index of the argument to process in the next
50 * argpar_iter_parse_next() call.
54 /* Counter of non-option arguments */
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.
63 const char *short_opt_ch
;
66 /* Base parsing item */
68 enum argpar_item_type type
;
71 /* Option parsing item */
72 struct argpar_item_opt
{
73 struct argpar_item base
;
75 /* Corresponding descriptor */
76 const struct argpar_opt_descr
*descr
;
78 /* Argument, or `NULL` if none; owned by this */
82 /* Non-option parsing item */
83 struct argpar_item_non_opt
{
84 struct argpar_item base
;
87 * Complete argument, pointing to one of the entries of the
88 * original arguments (`argv`).
93 * Index of this argument amongst all original arguments
96 unsigned int orig_index
;
98 /* Index of this argument amongst other non-option arguments */
99 unsigned int non_opt_index
;
102 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
103 char *argpar_vasprintf(const char * const fmt
, va_list args
)
109 va_copy(args2
, args
);
110 len1
= vsnprintf(NULL
, 0, fmt
, args
);
116 str
= malloc(len1
+ 1);
121 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
122 ARGPAR_ASSERT(len1
== len2
);
130 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
131 char *argpar_asprintf(const char * const fmt
, ...)
137 str
= argpar_vasprintf(fmt
, args
);
142 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
143 bool try_append_string_printf(char ** const str
, const char *fmt
, ...)
145 char *new_str
= NULL
;
146 char *addendum
= NULL
;
157 addendum
= argpar_vasprintf(fmt
, args
);
165 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
181 enum argpar_item_type
argpar_item_type(const struct argpar_item
* const item
)
188 const struct argpar_opt_descr
*argpar_item_opt_descr(
189 const struct argpar_item
* const item
)
192 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
193 return ((const struct argpar_item_opt
*) item
)->descr
;
197 const char *argpar_item_opt_arg(const struct argpar_item
* const item
)
200 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
201 return ((const struct argpar_item_opt
*) item
)->arg
;
205 const char *argpar_item_non_opt_arg(const struct argpar_item
* const item
)
208 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
209 return ((const struct argpar_item_non_opt
*) item
)->arg
;
213 unsigned int argpar_item_non_opt_orig_index(
214 const struct argpar_item
* const item
)
217 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
218 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
222 unsigned int argpar_item_non_opt_non_opt_index(
223 const struct argpar_item
* const item
)
226 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
227 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
231 void argpar_item_destroy(const struct argpar_item
* const item
)
237 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
238 struct argpar_item_opt
* const opt_item
=
239 (struct argpar_item_opt
*) item
;
251 bool push_item(struct argpar_item_array
* const array
,
252 const struct argpar_item
* const item
)
256 ARGPAR_ASSERT(array
);
259 if (array
->n_items
== array
->n_alloc
) {
260 const unsigned int new_n_alloc
= array
->n_alloc
* 2;
261 const struct argpar_item
** const new_items
=
262 ARGPAR_REALLOC(array
->items
, const struct argpar_item
*,
269 array
->n_alloc
= new_n_alloc
;
270 array
->items
= new_items
;
273 array
->items
[array
->n_items
] = item
;
282 void destroy_item_array(struct argpar_item_array
* const array
)
287 for (i
= 0; i
< array
->n_items
; i
++) {
288 argpar_item_destroy(array
->items
[i
]);
297 struct argpar_item_array
*create_item_array(void)
299 struct argpar_item_array
*ret
;
300 const int initial_size
= 10;
302 ret
= ARGPAR_ZALLOC(struct argpar_item_array
);
307 ret
->items
= ARGPAR_CALLOC(const struct argpar_item
*, initial_size
);
312 ret
->n_alloc
= initial_size
;
316 destroy_item_array(ret
);
324 struct argpar_item_opt
*create_opt_item(
325 const struct argpar_opt_descr
* const descr
,
326 const char * const arg
)
328 struct argpar_item_opt
*opt_item
=
329 ARGPAR_ZALLOC(struct argpar_item_opt
);
335 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
336 opt_item
->descr
= descr
;
339 opt_item
->arg
= strdup(arg
);
340 if (!opt_item
->arg
) {
348 argpar_item_destroy(&opt_item
->base
);
356 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
357 const unsigned int orig_index
,
358 const unsigned int non_opt_index
)
360 struct argpar_item_non_opt
* const non_opt_item
=
361 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
367 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
368 non_opt_item
->arg
= arg
;
369 non_opt_item
->orig_index
= orig_index
;
370 non_opt_item
->non_opt_index
= non_opt_index
;
377 const struct argpar_opt_descr
*find_descr(
378 const struct argpar_opt_descr
* const descrs
,
379 const char short_name
, const char * const long_name
)
381 const struct argpar_opt_descr
*descr
;
383 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
384 if (short_name
&& descr
->short_name
&&
385 short_name
== descr
->short_name
) {
389 if (long_name
&& descr
->long_name
&&
390 strcmp(long_name
, descr
->long_name
) == 0) {
396 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
399 enum parse_orig_arg_opt_ret
{
400 PARSE_ORIG_ARG_OPT_RET_OK
,
401 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -1,
402 PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
= -2,
403 PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
= -3,
404 PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
= -4,
405 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -5,
409 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
410 const char * const next_orig_arg
,
411 const struct argpar_opt_descr
* const descrs
,
412 struct argpar_iter
* const iter
,
413 char ** const error
, struct argpar_item
** const item
)
415 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
416 bool used_next_orig_arg
= false;
417 const char *opt_arg
= NULL
;
418 const struct argpar_opt_descr
*descr
;
419 struct argpar_item_opt
*opt_item
;
421 if (strlen(short_opts
) == 0) {
422 try_append_string_printf(error
, "Invalid argument");
423 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
;
427 if (!iter
->short_opt_ch
) {
428 iter
->short_opt_ch
= short_opts
;
431 /* Find corresponding option descriptor */
432 descr
= find_descr(descrs
, *iter
->short_opt_ch
, NULL
);
434 try_append_string_printf(error
, "Unknown option `-%c`",
435 *iter
->short_opt_ch
);
436 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
440 if (descr
->with_arg
) {
441 if (iter
->short_opt_ch
[1]) {
443 opt_arg
= &iter
->short_opt_ch
[1];
446 opt_arg
= next_orig_arg
;
447 used_next_orig_arg
= true;
451 * We accept `-o ''` (empty option argument), but not
452 * `-o` alone if an option argument is expected.
454 if (!opt_arg
|| (iter
->short_opt_ch
[1] &&
455 strlen(opt_arg
) == 0)) {
456 try_append_string_printf(error
,
457 "Missing required argument for option `-%c`",
458 *iter
->short_opt_ch
);
459 used_next_orig_arg
= false;
460 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
465 /* Create and append option argument */
466 opt_item
= create_opt_item(descr
, opt_arg
);
468 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
472 *item
= &opt_item
->base
;
473 iter
->short_opt_ch
++;
475 if (descr
->with_arg
|| !*iter
->short_opt_ch
) {
476 /* Option has an argument: no more options */
477 iter
->short_opt_ch
= NULL
;
479 if (used_next_orig_arg
) {
489 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
496 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
497 const char * const next_orig_arg
,
498 const struct argpar_opt_descr
* const descrs
,
499 struct argpar_iter
* const iter
,
500 char ** const error
, struct argpar_item
** const item
)
502 const size_t max_len
= 127;
503 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
504 const struct argpar_opt_descr
*descr
;
505 struct argpar_item_opt
*opt_item
;
506 bool used_next_orig_arg
= false;
508 /* Option's argument, if any */
509 const char *opt_arg
= NULL
;
511 /* Position of first `=`, if any */
514 /* Buffer holding option name when `long_opt_arg` contains `=` */
515 char buf
[max_len
+ 1];
518 const char *long_opt_name
= long_opt_arg
;
520 if (strlen(long_opt_arg
) == 0) {
521 try_append_string_printf(error
, "Invalid argument");
522 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
;
526 /* Find the first `=` in original argument */
527 eq_pos
= strchr(long_opt_arg
, '=');
529 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
531 /* Isolate the option name */
532 if (long_opt_name_size
> max_len
) {
533 try_append_string_printf(error
,
534 "Invalid argument `--%s`", long_opt_arg
);
535 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
;
539 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
540 buf
[long_opt_name_size
] = '\0';
544 /* Find corresponding option descriptor */
545 descr
= find_descr(descrs
, '\0', long_opt_name
);
547 try_append_string_printf(error
, "Unknown option `--%s`",
549 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
553 /* Find option's argument if any */
554 if (descr
->with_arg
) {
556 /* `--long-opt=arg` style */
557 opt_arg
= eq_pos
+ 1;
559 /* `--long-opt arg` style */
560 if (!next_orig_arg
) {
561 try_append_string_printf(error
,
562 "Missing required argument for option `--%s`",
564 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
568 opt_arg
= next_orig_arg
;
569 used_next_orig_arg
= true;
573 * Unexpected `--opt=arg` style for a long option which
574 * doesn't accept an argument.
576 try_append_string_printf(error
,
577 "Unexpected argument for option `--%s`", long_opt_name
);
578 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
;
582 /* Create and append option argument */
583 opt_item
= create_opt_item(descr
, opt_arg
);
588 if (used_next_orig_arg
) {
594 *item
= &opt_item
->base
;
598 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
605 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
606 const char * const next_orig_arg
,
607 const struct argpar_opt_descr
* const descrs
,
608 struct argpar_iter
* const iter
, char ** const error
,
609 struct argpar_item
** const item
)
611 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
613 ARGPAR_ASSERT(orig_arg
[0] == '-');
615 if (orig_arg
[1] == '-') {
617 ret
= parse_long_opt(&orig_arg
[2],
618 next_orig_arg
, descrs
, iter
, error
, item
);
621 ret
= parse_short_opts(&orig_arg
[1],
622 next_orig_arg
, descrs
, iter
, error
, item
);
629 bool try_prepend_while_parsing_arg_to_error(char ** const error
,
630 const unsigned int i
, const char * const arg
)
640 ARGPAR_ASSERT(*error
);
641 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
657 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
658 const char * const * const argv
,
659 const struct argpar_opt_descr
* const descrs
)
661 struct argpar_iter
* const iter
= ARGPAR_ZALLOC(struct argpar_iter
);
669 iter
->descrs
= descrs
;
676 void argpar_iter_destroy(struct argpar_iter
* const iter
)
682 enum argpar_iter_parse_next_status
argpar_iter_parse_next(
683 struct argpar_iter
* const iter
,
684 const struct argpar_item
** const item
, char ** const error
)
686 enum argpar_iter_parse_next_status status
;
687 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
688 const char *orig_arg
;
689 const char *next_orig_arg
;
691 ARGPAR_ASSERT(iter
->i
<= iter
->argc
);
697 if (iter
->i
== iter
->argc
) {
698 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_END
;
702 orig_arg
= iter
->argv
[iter
->i
];
704 iter
->i
< (iter
->argc
- 1) ? iter
->argv
[iter
->i
+ 1] : NULL
;
706 if (orig_arg
[0] != '-') {
707 /* Non-option argument */
708 struct argpar_item_non_opt
* const non_opt_item
=
709 create_non_opt_item(orig_arg
, iter
->i
,
710 iter
->non_opt_index
);
713 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_MEMORY
;
717 iter
->non_opt_index
++;
719 *item
= &non_opt_item
->base
;
720 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
;
724 /* Option argument */
725 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
726 next_orig_arg
, iter
->descrs
, iter
, error
,
727 (struct argpar_item
**) item
);
728 switch (parse_orig_arg_opt_ret
) {
729 case PARSE_ORIG_ARG_OPT_RET_OK
:
730 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
;
732 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
733 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
734 case PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
:
735 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
736 try_prepend_while_parsing_arg_to_error(error
, iter
->i
,
739 switch (parse_orig_arg_opt_ret
) {
740 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
741 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
743 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
744 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_MISSING_OPT_ARG
;
746 case PARSE_ORIG_ARG_OPT_RET_ERROR_INVALID_ARG
:
747 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_INVALID_ARG
;
749 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
750 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
;
757 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
758 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_MEMORY
;
769 unsigned int argpar_iter_get_ingested_orig_args(
770 const struct argpar_iter
* const iter
)
776 struct argpar_parse_ret
argpar_parse(const unsigned int argc
,
777 const char * const * const argv
,
778 const struct argpar_opt_descr
* const descrs
,
779 const bool fail_on_unknown_opt
)
781 struct argpar_parse_ret parse_ret
= { 0 };
782 const struct argpar_item
*item
= NULL
;
783 struct argpar_iter
*iter
= NULL
;
785 parse_ret
.items
= create_item_array();
786 if (!parse_ret
.items
) {
787 parse_ret
.error
= strdup("Failed to create items array.");
788 ARGPAR_ASSERT(parse_ret
.error
);
792 iter
= argpar_iter_create(argc
, argv
, descrs
);
794 parse_ret
.error
= strdup("Failed to create argpar iter.");
795 ARGPAR_ASSERT(parse_ret
.error
);
800 const enum argpar_iter_parse_next_status status
=
801 argpar_iter_parse_next(iter
, &item
, &parse_ret
.error
);
804 case ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_MISSING_OPT_ARG
:
805 case ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_INVALID_ARG
:
806 case ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
:
807 case ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_MEMORY
:
809 case ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
:
810 if (fail_on_unknown_opt
) {
811 parse_ret
.ingested_orig_args
=
812 argpar_iter_get_ingested_orig_args(iter
);
816 free(parse_ret
.error
);
817 parse_ret
.error
= NULL
;
819 case ARGPAR_ITER_PARSE_NEXT_STATUS_END
:
822 ARGPAR_ASSERT(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
);
826 if (!push_item(parse_ret
.items
, item
)) {
834 ARGPAR_ASSERT(!parse_ret
.error
);
835 parse_ret
.ingested_orig_args
= argpar_iter_get_ingested_orig_args(iter
);
839 ARGPAR_ASSERT(parse_ret
.error
);
841 /* That's how we indicate that an error occurred */
842 destroy_item_array(parse_ret
.items
);
843 parse_ret
.items
= NULL
;
846 argpar_iter_destroy(iter
);
847 argpar_item_destroy(item
);
852 void argpar_parse_ret_fini(struct argpar_parse_ret
* const ret
)
855 destroy_item_array(ret
->items
);