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 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
67 char *argpar_vasprintf(const char * const fmt
, va_list args
)
74 len1
= vsnprintf(NULL
, 0, fmt
, args
);
80 str
= malloc(len1
+ 1);
85 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
86 ARGPAR_ASSERT(len1
== len2
);
94 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
95 char *argpar_asprintf(const char * const fmt
, ...)
101 str
= argpar_vasprintf(fmt
, args
);
106 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
107 bool append_string_printf(char ** const str
, const char *fmt
, ...)
109 char *new_str
= NULL
;
116 addendum
= argpar_vasprintf(fmt
, args
);
124 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
140 void argpar_item_destroy(const struct argpar_item
* const item
)
146 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
147 struct argpar_item_opt
* const opt_item
=
148 (struct argpar_item_opt
*) item
;
150 free((void *) opt_item
->arg
);
160 bool push_item(struct argpar_item_array
* const array
,
161 const struct argpar_item
* const item
)
165 ARGPAR_ASSERT(array
);
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
*,
178 array
->n_alloc
= new_n_alloc
;
179 array
->items
= new_items
;
182 array
->items
[array
->n_items
] = item
;
191 void destroy_item_array(struct argpar_item_array
* const array
)
196 for (i
= 0; i
< array
->n_items
; i
++) {
197 argpar_item_destroy(array
->items
[i
]);
206 struct argpar_item_array
*create_item_array(void)
208 struct argpar_item_array
*ret
;
209 const int initial_size
= 10;
211 ret
= ARGPAR_ZALLOC(struct argpar_item_array
);
216 ret
->items
= ARGPAR_CALLOC(const struct argpar_item
*, initial_size
);
221 ret
->n_alloc
= initial_size
;
225 destroy_item_array(ret
);
233 struct argpar_item_opt
*create_opt_item(
234 const struct argpar_opt_descr
* const descr
,
235 const char * const arg
)
237 struct argpar_item_opt
*opt_item
=
238 ARGPAR_ZALLOC(struct argpar_item_opt
);
244 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
245 opt_item
->descr
= descr
;
248 opt_item
->arg
= strdup(arg
);
249 if (!opt_item
->arg
) {
257 argpar_item_destroy(&opt_item
->base
);
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
)
269 struct argpar_item_non_opt
* const non_opt_item
=
270 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
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
;
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
)
290 const struct argpar_opt_descr
*descr
;
292 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
293 if (short_name
&& descr
->short_name
&&
294 short_name
== descr
->short_name
) {
298 if (long_name
&& descr
->long_name
&&
299 strcmp(long_name
, descr
->long_name
) == 0) {
305 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
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,
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
)
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
;
327 if (strlen(short_opts
) == 0) {
328 append_string_printf(error
, "Invalid argument");
332 if (!iter
->short_opt_ch
) {
333 iter
->short_opt_ch
= short_opts
;
336 /* Find corresponding option descriptor */
337 descr
= find_descr(descrs
, *iter
->short_opt_ch
, NULL
);
339 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
340 append_string_printf(error
, "Unknown option `-%c`",
341 *iter
->short_opt_ch
);
345 if (descr
->with_arg
) {
346 if (iter
->short_opt_ch
[1]) {
348 opt_arg
= &iter
->short_opt_ch
[1];
351 opt_arg
= next_orig_arg
;
352 used_next_orig_arg
= true;
356 * We accept `-o ''` (empty option argument), but not
357 * `-o` alone if an option argument is expected.
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;
369 /* Create and append option argument */
370 opt_item
= create_opt_item(descr
, opt_arg
);
375 *item
= &opt_item
->base
;
376 iter
->short_opt_ch
++;
378 if (descr
->with_arg
|| !*iter
->short_opt_ch
) {
379 /* Option has an argument: no more options */
380 iter
->short_opt_ch
= NULL
;
382 if (used_next_orig_arg
) {
392 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
393 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
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
)
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;
413 /* Option's argument, if any */
414 const char *opt_arg
= NULL
;
416 /* Position of first `=`, if any */
419 /* Buffer holding option name when `long_opt_arg` contains `=` */
420 char buf
[max_len
+ 1];
423 const char *long_opt_name
= long_opt_arg
;
425 if (strlen(long_opt_arg
) == 0) {
426 append_string_printf(error
, "Invalid argument");
430 /* Find the first `=` in original argument */
431 eq_pos
= strchr(long_opt_arg
, '=');
433 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
435 /* Isolate the option name */
436 if (long_opt_name_size
> max_len
) {
437 append_string_printf(error
, "Invalid argument `--%s`",
442 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
443 buf
[long_opt_name_size
] = '\0';
447 /* Find corresponding option descriptor */
448 descr
= find_descr(descrs
, '\0', long_opt_name
);
450 append_string_printf(error
, "Unknown option `--%s`",
452 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
456 /* Find option's argument if any */
457 if (descr
->with_arg
) {
459 /* `--long-opt=arg` style */
460 opt_arg
= eq_pos
+ 1;
462 /* `--long-opt arg` style */
463 if (!next_orig_arg
) {
464 append_string_printf(error
,
465 "Missing required argument for option `--%s`",
470 opt_arg
= next_orig_arg
;
471 used_next_orig_arg
= true;
475 * Unexpected `--opt=arg` style for a long option which
476 * doesn't accept an argument.
478 append_string_printf(error
,
479 "Unexpected argument for option `--%s`", long_opt_name
);
483 /* Create and append option argument */
484 opt_item
= create_opt_item(descr
, opt_arg
);
489 if (used_next_orig_arg
) {
495 *item
= &opt_item
->base
;
499 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
500 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
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
)
514 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
516 ARGPAR_ASSERT(orig_arg
[0] == '-');
518 if (orig_arg
[1] == '-') {
520 ret
= parse_long_opt(&orig_arg
[2],
521 next_orig_arg
, descrs
, iter
, error
, item
);
524 ret
= parse_short_opts(&orig_arg
[1],
525 next_orig_arg
, descrs
, iter
, error
, item
);
532 bool prepend_while_parsing_arg_to_error(char ** const error
,
533 const unsigned int i
, const char * const arg
)
538 ARGPAR_ASSERT(error
);
539 ARGPAR_ASSERT(*error
);
540 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
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
)
560 struct argpar_iter
* const iter
= ARGPAR_ZALLOC(struct argpar_iter
);
568 iter
->descrs
= descrs
;
575 void argpar_iter_destroy(struct argpar_iter
* const iter
)
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
)
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
;
590 ARGPAR_ASSERT(iter
->i
<= iter
->argc
);
593 if (iter
->i
== iter
->argc
) {
594 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_END
;
598 orig_arg
= iter
->argv
[iter
->i
];
600 iter
->i
< (iter
->argc
- 1) ? iter
->argv
[iter
->i
+ 1] : NULL
;
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
);
609 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
;
613 iter
->non_opt_index
++;
615 *item
= &non_opt_item
->base
;
616 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
;
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
;
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
;
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
;
645 unsigned int argpar_iter_get_ingested_orig_args(
646 const struct argpar_iter
* const iter
)
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
)
657 struct argpar_parse_ret parse_ret
= { 0 };
658 const struct argpar_item
*item
= NULL
;
659 struct argpar_iter
*iter
= NULL
;
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
);
668 iter
= argpar_iter_create(argc
, argv
, descrs
);
670 parse_ret
.error
= strdup("Failed to create argpar iter.");
671 ARGPAR_ASSERT(parse_ret
.error
);
676 const enum argpar_iter_parse_next_status status
=
677 argpar_iter_parse_next(iter
, &item
, &parse_ret
.error
);
679 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
) {
681 } else if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
) {
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
);
690 free(parse_ret
.error
);
691 parse_ret
.error
= NULL
;
695 ARGPAR_ASSERT(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
);
697 if (!push_item(parse_ret
.items
, item
)) {
704 ARGPAR_ASSERT(!parse_ret
.error
);
705 parse_ret
.ingested_orig_args
= argpar_iter_get_ingested_orig_args(iter
);
709 ARGPAR_ASSERT(parse_ret
.error
);
711 /* That's how we indicate that an error occurred */
712 destroy_item_array(parse_ret
.items
);
713 parse_ret
.items
= NULL
;
716 argpar_iter_destroy(iter
);
717 argpar_item_destroy(item
);
722 void argpar_parse_ret_fini(struct argpar_parse_ret
* const ret
)
725 destroy_item_array(ret
->items
);