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>
15 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
16 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
18 #define ARGPAR_CALLOC(_type, _nmemb) \
19 ((_type *) calloc((_nmemb), sizeof(_type)))
21 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
25 * Force usage of the assertion condition to prevent unused variable
26 * warnings when `assert()` are disabled by the `NDEBUG` definition.
28 # define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
31 # define ARGPAR_ASSERT(_cond) assert(_cond)
37 * Such a structure contains the state of an iterator between calls to
42 * Data provided by the user to argpar_iter_create(); immutable
47 const char * const *argv
;
48 const argpar_opt_descr_t
*descrs
;
52 * Index of the argument to process in the next
53 * argpar_iter_next() call.
57 /* Counter of non-option arguments */
61 * Current character within the current short option group: if
62 * it's not `NULL`, the parser is within a short option group,
63 * therefore it must resume there in the next argpar_iter_next()
66 const char *short_opt_group_ch
;
68 /* Temporary character buffer which only grows */
75 /* Base parsing item */
77 argpar_item_type_t type
;
80 /* Option parsing item */
81 typedef struct argpar_item_opt
{
84 /* Corresponding descriptor */
85 const argpar_opt_descr_t
*descr
;
87 /* Argument, or `NULL` if none; owned by this */
91 /* Non-option parsing item */
92 typedef struct argpar_item_non_opt
{
96 * Complete argument, pointing to one of the entries of the
97 * original arguments (`argv`).
102 * Index of this argument amongst all original arguments
105 unsigned int orig_index
;
107 /* Index of this argument amongst other non-option arguments */
108 unsigned int non_opt_index
;
109 } argpar_item_non_opt_t
;
112 struct argpar_error
{
114 argpar_error_type_t type
;
116 /* Original argument index */
117 unsigned int orig_index
;
119 /* Name of unknown option; owned by this */
120 char *unknown_opt_name
;
122 /* Option descriptor */
123 const argpar_opt_descr_t
*opt_descr
;
125 /* `true` if a short option caused the error */
130 argpar_item_type_t
argpar_item_type(const argpar_item_t
* const item
)
137 const argpar_opt_descr_t
*argpar_item_opt_descr(
138 const argpar_item_t
* const item
)
141 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
142 return ((const argpar_item_opt_t
*) item
)->descr
;
146 const char *argpar_item_opt_arg(const argpar_item_t
* const item
)
149 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
150 return ((const argpar_item_opt_t
*) item
)->arg
;
154 const char *argpar_item_non_opt_arg(const argpar_item_t
* const item
)
157 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
158 return ((const argpar_item_non_opt_t
*) item
)->arg
;
162 unsigned int argpar_item_non_opt_orig_index(
163 const argpar_item_t
* const item
)
166 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
167 return ((const argpar_item_non_opt_t
*) item
)->orig_index
;
171 unsigned int argpar_item_non_opt_non_opt_index(
172 const argpar_item_t
* const item
)
175 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
176 return ((const argpar_item_non_opt_t
*) item
)->non_opt_index
;
180 void argpar_item_destroy(const argpar_item_t
* const item
)
186 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
187 argpar_item_opt_t
* const opt_item
= (argpar_item_opt_t
*) item
;
199 * Creates and returns an option parsing item for the descriptor `descr`
200 * and having the argument `arg` (copied; may be `NULL`).
202 * Returns `NULL` on memory error.
205 argpar_item_opt_t
*create_opt_item(const argpar_opt_descr_t
* const descr
,
206 const char * const arg
)
208 argpar_item_opt_t
*opt_item
= ARGPAR_ZALLOC(argpar_item_opt_t
);
214 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
215 opt_item
->descr
= descr
;
218 opt_item
->arg
= strdup(arg
);
219 if (!opt_item
->arg
) {
227 argpar_item_destroy(&opt_item
->base
);
235 * Creates and returns a non-option parsing item for the original
236 * argument `arg` having the original index `orig_index` and the
237 * non-option index `non_opt_index`.
239 * Returns `NULL` on memory error.
242 argpar_item_non_opt_t
*create_non_opt_item(const char * const arg
,
243 const unsigned int orig_index
,
244 const unsigned int non_opt_index
)
246 argpar_item_non_opt_t
* const non_opt_item
=
247 ARGPAR_ZALLOC(argpar_item_non_opt_t
);
253 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
254 non_opt_item
->arg
= arg
;
255 non_opt_item
->orig_index
= orig_index
;
256 non_opt_item
->non_opt_index
= non_opt_index
;
263 * If `error` is not `NULL`, sets the error `error` to a new parsing
264 * error object, setting its `unknown_opt_name`, `opt_descr`, and
265 * `is_short` members from the parameters.
267 * `unknown_opt_name` is the unknown option name without any `-` or `--`
268 * prefix: `is_short` controls which type of unknown option it is.
270 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
274 int set_error(argpar_error_t
** const error
, argpar_error_type_t type
,
275 const char * const unknown_opt_name
,
276 const argpar_opt_descr_t
* const opt_descr
, const bool is_short
)
284 *error
= ARGPAR_ZALLOC(argpar_error_t
);
289 (*error
)->type
= type
;
291 if (unknown_opt_name
) {
292 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
293 strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
294 if (!(*error
)->unknown_opt_name
) {
299 strcpy((*error
)->unknown_opt_name
, "-");
301 strcpy((*error
)->unknown_opt_name
, "--");
304 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
307 (*error
)->opt_descr
= opt_descr
;
308 (*error
)->is_short
= is_short
;
312 argpar_error_destroy(*error
);
320 argpar_error_type_t
argpar_error_type(
321 const argpar_error_t
* const error
)
323 ARGPAR_ASSERT(error
);
328 unsigned int argpar_error_orig_index(const argpar_error_t
* const error
)
330 ARGPAR_ASSERT(error
);
331 return error
->orig_index
;
335 const char *argpar_error_unknown_opt_name(
336 const argpar_error_t
* const error
)
338 ARGPAR_ASSERT(error
);
339 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
340 ARGPAR_ASSERT(error
->unknown_opt_name
);
341 return error
->unknown_opt_name
;
345 const argpar_opt_descr_t
*argpar_error_opt_descr(
346 const argpar_error_t
* const error
, bool * const is_short
)
348 ARGPAR_ASSERT(error
);
349 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
350 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
351 ARGPAR_ASSERT(error
->opt_descr
);
354 *is_short
= error
->is_short
;
357 return error
->opt_descr
;
361 void argpar_error_destroy(const argpar_error_t
* const error
)
364 free(error
->unknown_opt_name
);
365 free((void *) error
);
370 * Finds and returns the _first_ descriptor having the short option name
371 * `short_name` or the long option name `long_name` within the option
372 * descriptors `descrs`.
374 * `short_name` may be `'\0'` to not consider it.
376 * `long_name` may be `NULL` to not consider it.
378 * Returns `NULL` if no descriptor is found.
381 const argpar_opt_descr_t
*find_descr(const argpar_opt_descr_t
* const descrs
,
382 const char short_name
, const char * const long_name
)
384 const argpar_opt_descr_t
*descr
;
386 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
387 if (short_name
&& descr
->short_name
&&
388 short_name
== descr
->short_name
) {
392 if (long_name
&& descr
->long_name
&&
393 strcmp(long_name
, descr
->long_name
) == 0) {
399 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
402 /* Return type of parse_short_opt_group() and parse_long_opt() */
403 typedef enum parse_orig_arg_opt_ret
{
404 PARSE_ORIG_ARG_OPT_RET_OK
,
405 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
406 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
407 } parse_orig_arg_opt_ret_t
;
410 * Parses the short option group argument `short_opt_group`, starting
411 * where needed depending on the state of `iter`.
413 * On success, sets `*item`.
415 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
419 parse_orig_arg_opt_ret_t
parse_short_opt_group(
420 const char * const short_opt_group
,
421 const char * const next_orig_arg
,
422 const argpar_opt_descr_t
* const descrs
,
423 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
424 argpar_item_t
** const item
)
426 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
427 bool used_next_orig_arg
= false;
428 const char *opt_arg
= NULL
;
429 const argpar_opt_descr_t
*descr
;
430 argpar_item_opt_t
*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 parse_orig_arg_opt_ret_t
parse_long_opt(const char * const long_opt_arg
,
521 const char * const next_orig_arg
,
522 const argpar_opt_descr_t
* const descrs
,
523 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
524 argpar_item_t
** const item
)
526 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
527 const argpar_opt_descr_t
*descr
;
528 argpar_item_opt_t
*opt_item
;
529 bool used_next_orig_arg
= false;
531 /* Option's argument, if any */
532 const char *opt_arg
= NULL
;
534 /* Position of first `=`, if any */
538 const char *long_opt_name
= long_opt_arg
;
540 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
542 /* Find the first `=` in original argument */
543 eq_pos
= strchr(long_opt_arg
, '=');
545 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
547 /* Isolate the option name */
548 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
549 iter
->tmp_buf
.size
*= 2;
550 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
551 char, iter
->tmp_buf
.size
);
552 if (!iter
->tmp_buf
.data
) {
553 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
558 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
559 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
560 long_opt_name
= iter
->tmp_buf
.data
;
563 /* Find corresponding option descriptor */
564 descr
= find_descr(descrs
, '\0', long_opt_name
);
566 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
568 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
569 long_opt_name
, NULL
, false)) {
570 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
576 /* Find option's argument if any */
577 if (descr
->with_arg
) {
579 /* `--long-opt=arg` style */
580 opt_arg
= eq_pos
+ 1;
582 /* `--long-opt arg` style */
583 if (!next_orig_arg
) {
584 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
586 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
587 NULL
, descr
, false)) {
588 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
594 opt_arg
= next_orig_arg
;
595 used_next_orig_arg
= true;
599 * Unexpected `--opt=arg` style for a long option which
600 * doesn't accept an argument.
602 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
604 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
,
605 NULL
, descr
, false)) {
606 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
612 /* Create and append option argument */
613 opt_item
= create_opt_item(descr
, opt_arg
);
618 if (used_next_orig_arg
) {
624 *item
= &opt_item
->base
;
628 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
635 * Parses the original argument `orig_arg`.
637 * On success, sets `*item`.
639 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
643 parse_orig_arg_opt_ret_t
parse_orig_arg_opt(const char * const orig_arg
,
644 const char * const next_orig_arg
,
645 const argpar_opt_descr_t
* const descrs
,
646 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
647 argpar_item_t
** const item
)
649 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
651 ARGPAR_ASSERT(orig_arg
[0] == '-');
653 if (orig_arg
[1] == '-') {
655 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
,
659 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
,
667 argpar_iter_t
*argpar_iter_create(const unsigned int argc
,
668 const char * const * const argv
,
669 const argpar_opt_descr_t
* const descrs
)
671 argpar_iter_t
*iter
= ARGPAR_ZALLOC(argpar_iter_t
);
677 iter
->user
.argc
= argc
;
678 iter
->user
.argv
= argv
;
679 iter
->user
.descrs
= descrs
;
680 iter
->tmp_buf
.size
= 128;
681 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
682 if (!iter
->tmp_buf
.data
) {
683 argpar_iter_destroy(iter
);
693 void argpar_iter_destroy(argpar_iter_t
* const iter
)
696 free(iter
->tmp_buf
.data
);
702 argpar_iter_next_status_t
argpar_iter_next(argpar_iter_t
* const iter
,
703 const argpar_item_t
** const item
,
704 const argpar_error_t
** const error
)
706 argpar_iter_next_status_t status
;
707 parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret
;
708 const char *orig_arg
;
709 const char *next_orig_arg
;
710 argpar_error_t
** const nc_error
= (argpar_error_t
**) error
;
712 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
718 if (iter
->i
== iter
->user
.argc
) {
719 status
= ARGPAR_ITER_NEXT_STATUS_END
;
723 orig_arg
= iter
->user
.argv
[iter
->i
];
725 iter
->i
< (iter
->user
.argc
- 1) ?
726 iter
->user
.argv
[iter
->i
+ 1] : NULL
;
728 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
729 orig_arg
[0] != '-') {
730 /* Non-option argument */
731 const argpar_item_non_opt_t
* const non_opt_item
=
732 create_non_opt_item(orig_arg
, iter
->i
,
733 iter
->non_opt_index
);
736 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
740 iter
->non_opt_index
++;
742 *item
= &non_opt_item
->base
;
743 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
747 /* Option argument */
748 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
749 next_orig_arg
, iter
->user
.descrs
, iter
, nc_error
,
750 (argpar_item_t
**) item
);
751 switch (parse_orig_arg_opt_ret
) {
752 case PARSE_ORIG_ARG_OPT_RET_OK
:
753 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
755 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
757 ARGPAR_ASSERT(*error
);
758 (*nc_error
)->orig_index
= iter
->i
;
760 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
762 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
763 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
774 unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t
* const iter
)