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) ((_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)
21 #define ARGPAR_ASSERT(_cond) assert(_cond)
23 #ifdef __MINGW_PRINTF_FORMAT
24 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
26 # define ARGPAR_PRINTF_FORMAT printf
32 * Such a structure contains the state of an iterator between
33 * calls to argpar_iter_parse_next().
37 * Data provided by the user to argpar_iter_create(); immutable
41 const char * const *argv
;
42 const struct argpar_opt_descr
*descrs
;
45 * Index of the argument to process in the next
46 * argpar_iter_parse_next() call.
50 /* Counter of non-option arguments */
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.
59 const char *short_opt_ch
;
62 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
63 char *argpar_vasprintf(const char *fmt
, va_list args
)
71 len1
= vsnprintf(NULL
, 0, fmt
, args
);
77 str
= malloc(len1
+ 1);
82 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
84 ARGPAR_ASSERT(len1
== len2
);
92 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
93 char *argpar_asprintf(const char *fmt
, ...)
99 str
= argpar_vasprintf(fmt
, args
);
105 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
106 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
108 char *new_str
= NULL
;
116 addendum
= argpar_vasprintf(fmt
, args
);
124 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
142 void argpar_item_destroy(const struct argpar_item
* const item
)
148 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
149 struct argpar_item_opt
* const opt_item
=
150 (struct argpar_item_opt
*) item
;
152 free((void *) opt_item
->arg
);
162 bool push_item(struct argpar_item_array
* const array
,
163 struct argpar_item
* const item
)
167 ARGPAR_ASSERT(array
);
170 if (array
->n_items
== array
->n_alloc
) {
171 unsigned int new_n_alloc
= array
->n_alloc
* 2;
172 struct argpar_item
**new_items
;
174 new_items
= argpar_realloc(array
->items
,
175 struct argpar_item
*, new_n_alloc
);
181 array
->n_alloc
= new_n_alloc
;
182 array
->items
= new_items
;
185 array
->items
[array
->n_items
] = item
;
195 void destroy_item_array(struct argpar_item_array
* const array
)
200 for (i
= 0; i
< array
->n_items
; i
++) {
201 argpar_item_destroy(array
->items
[i
]);
210 struct argpar_item_array
*new_item_array(void)
212 struct argpar_item_array
*ret
;
213 const int initial_size
= 10;
215 ret
= argpar_zalloc(struct argpar_item_array
);
220 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
225 ret
->n_alloc
= initial_size
;
230 destroy_item_array(ret
);
238 struct argpar_item_opt
*create_opt_item(
239 const struct argpar_opt_descr
* const descr
,
240 const char * const arg
)
242 struct argpar_item_opt
*opt_item
=
243 argpar_zalloc(struct argpar_item_opt
);
249 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
250 opt_item
->descr
= descr
;
253 opt_item
->arg
= strdup(arg
);
254 if (!opt_item
->arg
) {
262 argpar_item_destroy(&opt_item
->base
);
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
)
274 struct argpar_item_non_opt
* const non_opt_item
=
275 argpar_zalloc(struct argpar_item_non_opt
);
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
;
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
)
295 const struct argpar_opt_descr
*descr
;
297 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
298 if (short_name
&& descr
->short_name
&&
299 short_name
== descr
->short_name
) {
303 if (long_name
&& descr
->long_name
&&
304 strcmp(long_name
, descr
->long_name
) == 0) {
310 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
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,
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
)
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
;
332 if (strlen(short_opts
) == 0) {
333 argpar_string_append_printf(error
, "Invalid argument");
337 if (!iter
->short_opt_ch
) {
338 iter
->short_opt_ch
= short_opts
;
341 /* Find corresponding option descriptor */
342 descr
= find_descr(descrs
, *iter
->short_opt_ch
, NULL
);
344 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
345 argpar_string_append_printf(error
,
346 "Unknown option `-%c`", *iter
->short_opt_ch
);
350 if (descr
->with_arg
) {
351 if (iter
->short_opt_ch
[1]) {
353 opt_arg
= &iter
->short_opt_ch
[1];
356 opt_arg
= next_orig_arg
;
357 used_next_orig_arg
= true;
361 * We accept `-o ''` (empty option argument), but not
362 * `-o` alone if an option argument is expected.
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;
373 /* Create and append option argument */
374 opt_item
= create_opt_item(descr
, opt_arg
);
379 *item
= &opt_item
->base
;
380 iter
->short_opt_ch
++;
382 if (descr
->with_arg
|| !*iter
->short_opt_ch
) {
383 /* Option has an argument: no more options */
384 iter
->short_opt_ch
= NULL
;
386 if (used_next_orig_arg
) {
396 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
397 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
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
)
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;
417 /* Option's argument, if any */
418 const char *opt_arg
= NULL
;
420 /* Position of first `=`, if any */
423 /* Buffer holding option name when `long_opt_arg` contains `=` */
424 char buf
[max_len
+ 1];
427 const char *long_opt_name
= long_opt_arg
;
429 if (strlen(long_opt_arg
) == 0) {
430 argpar_string_append_printf(error
, "Invalid argument");
434 /* Find the first `=` in original argument */
435 eq_pos
= strchr(long_opt_arg
, '=');
437 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
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
);
446 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
447 buf
[long_opt_name_size
] = '\0';
451 /* Find corresponding option descriptor */
452 descr
= find_descr(descrs
, '\0', long_opt_name
);
454 argpar_string_append_printf(error
,
455 "Unknown option `--%s`", long_opt_name
);
456 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
460 /* Find option's argument if any */
461 if (descr
->with_arg
) {
463 /* `--long-opt=arg` style */
464 opt_arg
= eq_pos
+ 1;
466 /* `--long-opt arg` style */
467 if (!next_orig_arg
) {
468 argpar_string_append_printf(error
,
469 "Missing required argument for option `--%s`",
474 opt_arg
= next_orig_arg
;
475 used_next_orig_arg
= true;
479 * Unexpected `--opt=arg` style for a long option which
480 * doesn't accept an argument.
482 argpar_string_append_printf(error
,
483 "Unexpected argument for option `--%s`", long_opt_name
);
487 /* Create and append option argument */
488 opt_item
= create_opt_item(descr
, opt_arg
);
493 if (used_next_orig_arg
) {
499 *item
= &opt_item
->base
;
503 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
504 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
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
,
517 struct argpar_item
** const item
)
519 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
521 ARGPAR_ASSERT(orig_arg
[0] == '-');
523 if (orig_arg
[1] == '-') {
525 ret
= parse_long_opt(&orig_arg
[2],
526 next_orig_arg
, descrs
, iter
, error
, item
);
529 ret
= parse_short_opts(&orig_arg
[1],
530 next_orig_arg
, descrs
, iter
, error
, item
);
537 bool prepend_while_parsing_arg_to_error(char **error
,
538 const unsigned int i
, const char * const arg
)
543 ARGPAR_ASSERT(error
);
544 ARGPAR_ASSERT(*error
);
546 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
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
)
566 struct argpar_iter
* const iter
= argpar_zalloc(struct argpar_iter
);
574 iter
->descrs
= descrs
;
581 void argpar_iter_destroy(struct argpar_iter
* const iter
)
587 enum argpar_iter_parse_next_status
argpar_iter_parse_next(
588 struct argpar_iter
* const iter
,
589 const struct argpar_item
** const item
,
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
;
597 ARGPAR_ASSERT(iter
->i
<= iter
->argc
);
600 if (iter
->i
== iter
->argc
) {
601 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_END
;
605 orig_arg
= iter
->argv
[iter
->i
];
607 iter
->i
< (iter
->argc
- 1) ? iter
->argv
[iter
->i
+ 1] : NULL
;
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
);
616 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
;
620 iter
->non_opt_index
++;
622 *item
= &non_opt_item
->base
;
623 status
= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
;
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
;
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
;
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
;
652 unsigned int argpar_iter_get_ingested_orig_args(
653 const struct argpar_iter
* const iter
)
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
)
664 struct argpar_parse_ret parse_ret
= { 0 };
665 const struct argpar_item
*item
= NULL
;
666 struct argpar_iter
*iter
= NULL
;
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
);
675 iter
= argpar_iter_create(argc
, argv
, descrs
);
677 parse_ret
.error
= strdup("Failed to create argpar iter.");
678 ARGPAR_ASSERT(parse_ret
.error
);
683 enum argpar_iter_parse_next_status status
;
685 status
= argpar_iter_parse_next(iter
, &item
, &parse_ret
.error
);
686 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
) {
688 } else if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
) {
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
);
697 free(parse_ret
.error
);
698 parse_ret
.error
= NULL
;
702 ARGPAR_ASSERT(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
);
704 if (!push_item(parse_ret
.items
, (void *) item
)) {
711 ARGPAR_ASSERT(!parse_ret
.error
);
712 parse_ret
.ingested_orig_args
=
713 argpar_iter_get_ingested_orig_args(iter
);
717 ARGPAR_ASSERT(parse_ret
.error
);
719 /* That's how we indicate that an error occurred */
720 destroy_item_array(parse_ret
.items
);
721 parse_ret
.items
= NULL
;
724 argpar_iter_destroy(iter
);
725 argpar_item_destroy(item
);
730 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
734 destroy_item_array(ret
->items
);