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)
30 * Such a structure contains the state of an iterator between calls to
35 * Data provided by the user to argpar_iter_create(); immutable
40 const char * const *argv
;
41 const struct argpar_opt_descr
*descrs
;
45 * Index of the argument to process in the next
46 * argpar_iter_next() call.
50 /* Counter of non-option arguments */
54 * Current character within the current short option group: if
55 * it's not `NULL`, the parser is within a short option group,
56 * therefore it must resume there in the next argpar_iter_next()
59 const char *short_opt_group_ch
;
61 /* Temporary character buffer which only grows */
68 /* Base parsing item */
70 enum argpar_item_type type
;
73 /* Option parsing item */
74 struct argpar_item_opt
{
75 struct argpar_item base
;
77 /* Corresponding descriptor */
78 const struct argpar_opt_descr
*descr
;
80 /* Argument, or `NULL` if none; owned by this */
84 /* Non-option parsing item */
85 struct argpar_item_non_opt
{
86 struct argpar_item base
;
89 * Complete argument, pointing to one of the entries of the
90 * original arguments (`argv`).
95 * Index of this argument amongst all original arguments
98 unsigned int orig_index
;
100 /* Index of this argument amongst other non-option arguments */
101 unsigned int non_opt_index
;
105 struct argpar_error
{
107 enum argpar_error_type type
;
109 /* Original argument index */
110 unsigned int orig_index
;
112 /* Name of unknown option; owned by this */
113 char *unknown_opt_name
;
115 /* Option descriptor */
116 const struct argpar_opt_descr
*opt_descr
;
118 /* `true` if a short option caused the error */
123 enum argpar_item_type
argpar_item_type(const struct argpar_item
* const item
)
130 const struct argpar_opt_descr
*argpar_item_opt_descr(
131 const struct argpar_item
* const item
)
134 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
135 return ((const struct argpar_item_opt
*) item
)->descr
;
139 const char *argpar_item_opt_arg(const struct argpar_item
* const item
)
142 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
143 return ((const struct argpar_item_opt
*) item
)->arg
;
147 const char *argpar_item_non_opt_arg(const struct argpar_item
* const item
)
150 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
151 return ((const struct argpar_item_non_opt
*) item
)->arg
;
155 unsigned int argpar_item_non_opt_orig_index(
156 const struct argpar_item
* const item
)
159 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
160 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
164 unsigned int argpar_item_non_opt_non_opt_index(
165 const struct argpar_item
* const item
)
168 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
169 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
173 void argpar_item_destroy(const struct argpar_item
* const item
)
179 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
180 struct argpar_item_opt
* const opt_item
=
181 (struct argpar_item_opt
*) item
;
193 * Creates and returns an option parsing item for the descriptor `descr`
194 * and having the argument `arg` (copied; may be `NULL`).
196 * Returns `NULL` on memory error.
199 struct argpar_item_opt
*create_opt_item(
200 const struct argpar_opt_descr
* const descr
,
201 const char * const arg
)
203 struct argpar_item_opt
*opt_item
=
204 ARGPAR_ZALLOC(struct argpar_item_opt
);
210 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
211 opt_item
->descr
= descr
;
214 opt_item
->arg
= strdup(arg
);
215 if (!opt_item
->arg
) {
223 argpar_item_destroy(&opt_item
->base
);
231 * Creates and returns a non-option parsing item for the original
232 * argument `arg` having the original index `orig_index` and the
233 * non-option index `non_opt_index`.
235 * Returns `NULL` on memory error.
238 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
239 const unsigned int orig_index
,
240 const unsigned int non_opt_index
)
242 struct argpar_item_non_opt
* const non_opt_item
=
243 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
249 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
250 non_opt_item
->arg
= arg
;
251 non_opt_item
->orig_index
= orig_index
;
252 non_opt_item
->non_opt_index
= non_opt_index
;
259 * If `error` is not `NULL`, sets the error `error` to a new parsing
260 * error object, setting its `unknown_opt_name`, `opt_descr`, and
261 * `is_short` members from the parameters.
263 * `unknown_opt_name` is the unknown option name without any `-` or `--`
264 * prefix: `is_short` controls which type of unknown option it is.
266 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
270 int set_error(struct argpar_error
** const error
,
271 enum argpar_error_type type
,
272 const char * const unknown_opt_name
,
273 const struct argpar_opt_descr
* const opt_descr
,
282 *error
= ARGPAR_ZALLOC(struct argpar_error
);
287 (*error
)->type
= type
;
289 if (unknown_opt_name
) {
290 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
291 strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
292 if (!(*error
)->unknown_opt_name
) {
297 strcpy((*error
)->unknown_opt_name
, "-");
299 strcpy((*error
)->unknown_opt_name
, "--");
302 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
305 (*error
)->opt_descr
= opt_descr
;
306 (*error
)->is_short
= is_short
;
310 argpar_error_destroy(*error
);
318 enum argpar_error_type
argpar_error_type(
319 const struct argpar_error
* const error
)
321 ARGPAR_ASSERT(error
);
326 unsigned int argpar_error_orig_index(const struct argpar_error
* const error
)
328 ARGPAR_ASSERT(error
);
329 return error
->orig_index
;
333 const char *argpar_error_unknown_opt_name(
334 const struct argpar_error
* const error
)
336 ARGPAR_ASSERT(error
);
337 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
338 ARGPAR_ASSERT(error
->unknown_opt_name
);
339 return error
->unknown_opt_name
;
343 const struct argpar_opt_descr
*argpar_error_opt_descr(
344 const struct argpar_error
* const error
, bool * const is_short
)
346 ARGPAR_ASSERT(error
);
347 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
348 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
349 ARGPAR_ASSERT(error
->opt_descr
);
352 *is_short
= error
->is_short
;
355 return error
->opt_descr
;
359 void argpar_error_destroy(const struct argpar_error
* const error
)
362 free(error
->unknown_opt_name
);
363 free((void *) error
);
368 * Finds and returns the _first_ descriptor having the short option name
369 * `short_name` or the long option name `long_name` within the option
370 * descriptors `descrs`.
372 * `short_name` may be `'\0'` to not consider it.
374 * `long_name` may be `NULL` to not consider it.
376 * Returns `NULL` if no descriptor is found.
379 const struct argpar_opt_descr
*find_descr(
380 const struct argpar_opt_descr
* const descrs
,
381 const char short_name
, const char * const long_name
)
383 const struct argpar_opt_descr
*descr
;
385 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
386 if (short_name
&& descr
->short_name
&&
387 short_name
== descr
->short_name
) {
391 if (long_name
&& descr
->long_name
&&
392 strcmp(long_name
, descr
->long_name
) == 0) {
398 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
401 /* Return type of parse_short_opt_group() and parse_long_opt() */
402 enum parse_orig_arg_opt_ret
{
403 PARSE_ORIG_ARG_OPT_RET_OK
,
404 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
405 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
409 * Parses the short option group argument `short_opt_group`, starting
410 * where needed depending on the state of `iter`.
412 * On success, sets `*item`.
414 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
418 enum parse_orig_arg_opt_ret
parse_short_opt_group(
419 const char * const short_opt_group
,
420 const char * const next_orig_arg
,
421 const struct argpar_opt_descr
* const descrs
,
422 struct argpar_iter
* const iter
,
423 struct argpar_error
** const error
,
424 struct argpar_item
** const item
)
426 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
427 bool used_next_orig_arg
= false;
428 const char *opt_arg
= NULL
;
429 const struct argpar_opt_descr
*descr
;
430 struct argpar_item_opt
*opt_item
;
432 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
434 if (!iter
->short_opt_group_ch
) {
435 iter
->short_opt_group_ch
= short_opt_group
;
438 /* Find corresponding option descriptor */
439 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
441 const char unknown_opt_name
[] =
442 {*iter
->short_opt_group_ch
, '\0'};
444 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
446 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
447 unknown_opt_name
, NULL
, true)) {
448 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
454 if (descr
->with_arg
) {
455 if (iter
->short_opt_group_ch
[1]) {
457 opt_arg
= &iter
->short_opt_group_ch
[1];
460 opt_arg
= next_orig_arg
;
461 used_next_orig_arg
= true;
465 * We accept `-o ''` (empty option argument), but not
466 * `-o` alone if an option argument is expected.
468 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
469 strlen(opt_arg
) == 0)) {
470 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
472 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
473 NULL
, descr
, true)) {
474 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
481 /* Create and append option argument */
482 opt_item
= create_opt_item(descr
, opt_arg
);
484 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
488 *item
= &opt_item
->base
;
489 iter
->short_opt_group_ch
++;
491 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
492 /* Option has an argument: no more options */
493 iter
->short_opt_group_ch
= NULL
;
495 if (used_next_orig_arg
) {
505 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
512 * Parses the long option argument `long_opt_arg`.
514 * On success, sets `*item`.
516 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
520 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
521 const char * const next_orig_arg
,
522 const struct argpar_opt_descr
* const descrs
,
523 struct argpar_iter
* const iter
,
524 struct argpar_error
** const error
,
525 struct argpar_item
** const item
)
527 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
528 const struct argpar_opt_descr
*descr
;
529 struct argpar_item_opt
*opt_item
;
530 bool used_next_orig_arg
= false;
532 /* Option's argument, if any */
533 const char *opt_arg
= NULL
;
535 /* Position of first `=`, if any */
539 const char *long_opt_name
= long_opt_arg
;
541 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
543 /* Find the first `=` in original argument */
544 eq_pos
= strchr(long_opt_arg
, '=');
546 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
548 /* Isolate the option name */
549 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
550 iter
->tmp_buf
.size
*= 2;
551 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
552 char, iter
->tmp_buf
.size
);
553 if (!iter
->tmp_buf
.data
) {
554 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
559 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
560 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
561 long_opt_name
= iter
->tmp_buf
.data
;
564 /* Find corresponding option descriptor */
565 descr
= find_descr(descrs
, '\0', long_opt_name
);
567 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
569 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
570 long_opt_name
, NULL
, false)) {
571 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
577 /* Find option's argument if any */
578 if (descr
->with_arg
) {
580 /* `--long-opt=arg` style */
581 opt_arg
= eq_pos
+ 1;
583 /* `--long-opt arg` style */
584 if (!next_orig_arg
) {
585 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
587 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
588 NULL
, descr
, false)) {
589 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
595 opt_arg
= next_orig_arg
;
596 used_next_orig_arg
= true;
600 * Unexpected `--opt=arg` style for a long option which
601 * doesn't accept an argument.
603 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
605 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
,
606 NULL
, descr
, false)) {
607 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
613 /* Create and append option argument */
614 opt_item
= create_opt_item(descr
, opt_arg
);
619 if (used_next_orig_arg
) {
625 *item
= &opt_item
->base
;
629 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
636 * Parses the original argument `orig_arg`.
638 * On success, sets `*item`.
640 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
644 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
645 const char * const next_orig_arg
,
646 const struct argpar_opt_descr
* const descrs
,
647 struct argpar_iter
* const iter
,
648 struct argpar_error
** const error
,
649 struct argpar_item
** const item
)
651 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
653 ARGPAR_ASSERT(orig_arg
[0] == '-');
655 if (orig_arg
[1] == '-') {
657 ret
= parse_long_opt(&orig_arg
[2],
658 next_orig_arg
, descrs
, iter
, error
, item
);
661 ret
= parse_short_opt_group(&orig_arg
[1],
662 next_orig_arg
, descrs
, iter
, error
, item
);
669 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
670 const char * const * const argv
,
671 const struct argpar_opt_descr
* const descrs
)
673 struct argpar_iter
*iter
= ARGPAR_ZALLOC(struct argpar_iter
);
679 iter
->user
.argc
= argc
;
680 iter
->user
.argv
= argv
;
681 iter
->user
.descrs
= descrs
;
682 iter
->tmp_buf
.size
= 128;
683 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
684 if (!iter
->tmp_buf
.data
) {
685 argpar_iter_destroy(iter
);
695 void argpar_iter_destroy(struct argpar_iter
* const iter
)
698 free(iter
->tmp_buf
.data
);
704 enum argpar_iter_next_status
argpar_iter_next(
705 struct argpar_iter
* const iter
,
706 const struct argpar_item
** const item
,
707 const struct argpar_error
** const error
)
709 enum argpar_iter_next_status status
;
710 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
711 const char *orig_arg
;
712 const char *next_orig_arg
;
713 struct argpar_error
** const nc_error
= (struct argpar_error
**) error
;
715 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
721 if (iter
->i
== iter
->user
.argc
) {
722 status
= ARGPAR_ITER_NEXT_STATUS_END
;
726 orig_arg
= iter
->user
.argv
[iter
->i
];
728 iter
->i
< (iter
->user
.argc
- 1) ?
729 iter
->user
.argv
[iter
->i
+ 1] : NULL
;
731 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
732 orig_arg
[0] != '-') {
733 /* Non-option argument */
734 const struct argpar_item_non_opt
* const non_opt_item
=
735 create_non_opt_item(orig_arg
, iter
->i
,
736 iter
->non_opt_index
);
739 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
743 iter
->non_opt_index
++;
745 *item
= &non_opt_item
->base
;
746 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
750 /* Option argument */
751 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
752 next_orig_arg
, iter
->user
.descrs
, iter
, nc_error
,
753 (struct argpar_item
**) item
);
754 switch (parse_orig_arg_opt_ret
) {
755 case PARSE_ORIG_ARG_OPT_RET_OK
:
756 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
758 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
760 ARGPAR_ASSERT(*error
);
761 (*nc_error
)->orig_index
= iter
->i
;
763 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
765 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
766 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
777 unsigned int argpar_iter_ingested_orig_args(
778 const struct argpar_iter
* const iter
)