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>
16 * If argpar is used in some shared library, we don't want said library
17 * to export its symbols, so mark them as "hidden".
19 * On Windows, symbols are local unless explicitly exported; see
20 * <https://gcc.gnu.org/wiki/Visibility>.
22 #if defined(_WIN32) || defined(__CYGWIN__)
23 # define ARGPAR_HIDDEN
25 # define ARGPAR_HIDDEN __attribute__((visibility("hidden")))
28 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
29 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
31 #define ARGPAR_CALLOC(_type, _nmemb) \
32 ((_type *) calloc((_nmemb), sizeof(_type)))
34 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
38 * Force usage of the assertion condition to prevent unused variable
39 * warnings when `assert()` are disabled by the `NDEBUG` definition.
41 # define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
44 # define ARGPAR_ASSERT(_cond) assert(_cond)
50 * Such a structure contains the state of an iterator between calls to
55 * Data provided by the user to argpar_iter_create(); immutable
60 const char * const *argv
;
61 const argpar_opt_descr_t
*descrs
;
65 * Index of the argument to process in the next
66 * argpar_iter_next() call.
70 /* Counter of non-option arguments */
74 * Current character within the current short option group: if
75 * it's not `NULL`, the parser is within a short option group,
76 * therefore it must resume there in the next argpar_iter_next()
79 const char *short_opt_group_ch
;
81 /* Temporary character buffer which only grows */
88 /* Base parsing item */
90 argpar_item_type_t type
;
93 /* Option parsing item */
94 typedef struct argpar_item_opt
{
97 /* Corresponding descriptor */
98 const argpar_opt_descr_t
*descr
;
100 /* Argument, or `NULL` if none; owned by this */
104 /* Non-option parsing item */
105 typedef struct argpar_item_non_opt
{
109 * Complete argument, pointing to one of the entries of the
110 * original arguments (`argv`).
115 * Index of this argument amongst all original arguments
118 unsigned int orig_index
;
120 /* Index of this argument amongst other non-option arguments */
121 unsigned int non_opt_index
;
122 } argpar_item_non_opt_t
;
125 struct argpar_error
{
127 argpar_error_type_t type
;
129 /* Original argument index */
130 unsigned int orig_index
;
132 /* Name of unknown option; owned by this */
133 char *unknown_opt_name
;
135 /* Option descriptor */
136 const argpar_opt_descr_t
*opt_descr
;
138 /* `true` if a short option caused the error */
143 argpar_item_type_t
argpar_item_type(const argpar_item_t
* const item
)
150 const argpar_opt_descr_t
*argpar_item_opt_descr(
151 const argpar_item_t
* const item
)
154 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
155 return ((const argpar_item_opt_t
*) item
)->descr
;
159 const char *argpar_item_opt_arg(const argpar_item_t
* const item
)
162 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
163 return ((const argpar_item_opt_t
*) item
)->arg
;
167 const char *argpar_item_non_opt_arg(const argpar_item_t
* const item
)
170 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
171 return ((const argpar_item_non_opt_t
*) item
)->arg
;
175 unsigned int argpar_item_non_opt_orig_index(
176 const argpar_item_t
* const item
)
179 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
180 return ((const argpar_item_non_opt_t
*) item
)->orig_index
;
184 unsigned int argpar_item_non_opt_non_opt_index(
185 const argpar_item_t
* const item
)
188 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
189 return ((const argpar_item_non_opt_t
*) item
)->non_opt_index
;
193 void argpar_item_destroy(const argpar_item_t
* const item
)
199 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
200 argpar_item_opt_t
* const opt_item
= (argpar_item_opt_t
*) item
;
212 * Creates and returns an option parsing item for the descriptor `descr`
213 * and having the argument `arg` (copied; may be `NULL`).
215 * Returns `NULL` on memory error.
218 argpar_item_opt_t
*create_opt_item(const argpar_opt_descr_t
* const descr
,
219 const char * const arg
)
221 argpar_item_opt_t
*opt_item
= ARGPAR_ZALLOC(argpar_item_opt_t
);
227 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
228 opt_item
->descr
= descr
;
231 opt_item
->arg
= strdup(arg
);
232 if (!opt_item
->arg
) {
240 argpar_item_destroy(&opt_item
->base
);
248 * Creates and returns a non-option parsing item for the original
249 * argument `arg` having the original index `orig_index` and the
250 * non-option index `non_opt_index`.
252 * Returns `NULL` on memory error.
255 argpar_item_non_opt_t
*create_non_opt_item(const char * const arg
,
256 const unsigned int orig_index
,
257 const unsigned int non_opt_index
)
259 argpar_item_non_opt_t
* const non_opt_item
=
260 ARGPAR_ZALLOC(argpar_item_non_opt_t
);
266 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
267 non_opt_item
->arg
= arg
;
268 non_opt_item
->orig_index
= orig_index
;
269 non_opt_item
->non_opt_index
= non_opt_index
;
276 * If `error` is not `NULL`, sets the error `error` to a new parsing
277 * error object, setting its `unknown_opt_name`, `opt_descr`, and
278 * `is_short` members from the parameters.
280 * `unknown_opt_name` is the unknown option name without any `-` or `--`
281 * prefix: `is_short` controls which type of unknown option it is.
283 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
287 int set_error(argpar_error_t
** const error
, argpar_error_type_t type
,
288 const char * const unknown_opt_name
,
289 const argpar_opt_descr_t
* const opt_descr
, const bool is_short
)
297 *error
= ARGPAR_ZALLOC(argpar_error_t
);
302 (*error
)->type
= type
;
304 if (unknown_opt_name
) {
305 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
306 strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
307 if (!(*error
)->unknown_opt_name
) {
312 strcpy((*error
)->unknown_opt_name
, "-");
314 strcpy((*error
)->unknown_opt_name
, "--");
317 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
320 (*error
)->opt_descr
= opt_descr
;
321 (*error
)->is_short
= is_short
;
325 argpar_error_destroy(*error
);
333 argpar_error_type_t
argpar_error_type(
334 const argpar_error_t
* const error
)
336 ARGPAR_ASSERT(error
);
341 unsigned int argpar_error_orig_index(const argpar_error_t
* const error
)
343 ARGPAR_ASSERT(error
);
344 return error
->orig_index
;
348 const char *argpar_error_unknown_opt_name(
349 const argpar_error_t
* const error
)
351 ARGPAR_ASSERT(error
);
352 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
353 ARGPAR_ASSERT(error
->unknown_opt_name
);
354 return error
->unknown_opt_name
;
358 const argpar_opt_descr_t
*argpar_error_opt_descr(
359 const argpar_error_t
* const error
, bool * const is_short
)
361 ARGPAR_ASSERT(error
);
362 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
363 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
364 ARGPAR_ASSERT(error
->opt_descr
);
367 *is_short
= error
->is_short
;
370 return error
->opt_descr
;
374 void argpar_error_destroy(const argpar_error_t
* const error
)
377 free(error
->unknown_opt_name
);
378 free((void *) error
);
383 * Finds and returns the _first_ descriptor having the short option name
384 * `short_name` or the long option name `long_name` within the option
385 * descriptors `descrs`.
387 * `short_name` may be `'\0'` to not consider it.
389 * `long_name` may be `NULL` to not consider it.
391 * Returns `NULL` if no descriptor is found.
394 const argpar_opt_descr_t
*find_descr(const argpar_opt_descr_t
* const descrs
,
395 const char short_name
, const char * const long_name
)
397 const argpar_opt_descr_t
*descr
;
399 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
400 if (short_name
&& descr
->short_name
&&
401 short_name
== descr
->short_name
) {
405 if (long_name
&& descr
->long_name
&&
406 strcmp(long_name
, descr
->long_name
) == 0) {
412 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
415 /* Return type of parse_short_opt_group() and parse_long_opt() */
416 typedef enum parse_orig_arg_opt_ret
{
417 PARSE_ORIG_ARG_OPT_RET_OK
,
418 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
419 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
420 } parse_orig_arg_opt_ret_t
;
423 * Parses the short option group argument `short_opt_group`, starting
424 * where needed depending on the state of `iter`.
426 * On success, sets `*item`.
428 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
432 parse_orig_arg_opt_ret_t
parse_short_opt_group(
433 const char * const short_opt_group
,
434 const char * const next_orig_arg
,
435 const argpar_opt_descr_t
* const descrs
,
436 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
437 argpar_item_t
** const item
)
439 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
440 bool used_next_orig_arg
= false;
441 const char *opt_arg
= NULL
;
442 const argpar_opt_descr_t
*descr
;
443 argpar_item_opt_t
*opt_item
;
445 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
447 if (!iter
->short_opt_group_ch
) {
448 iter
->short_opt_group_ch
= short_opt_group
;
451 /* Find corresponding option descriptor */
452 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
454 const char unknown_opt_name
[] =
455 {*iter
->short_opt_group_ch
, '\0'};
457 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
459 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
460 unknown_opt_name
, NULL
, true)) {
461 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
467 if (descr
->with_arg
) {
468 if (iter
->short_opt_group_ch
[1]) {
470 opt_arg
= &iter
->short_opt_group_ch
[1];
473 opt_arg
= next_orig_arg
;
474 used_next_orig_arg
= true;
478 * We accept `-o ''` (empty option argument), but not
479 * `-o` alone if an option argument is expected.
481 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
482 strlen(opt_arg
) == 0)) {
483 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
485 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
486 NULL
, descr
, true)) {
487 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
494 /* Create and append option argument */
495 opt_item
= create_opt_item(descr
, opt_arg
);
497 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
501 *item
= &opt_item
->base
;
502 iter
->short_opt_group_ch
++;
504 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
505 /* Option has an argument: no more options */
506 iter
->short_opt_group_ch
= NULL
;
508 if (used_next_orig_arg
) {
518 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
525 * Parses the long option argument `long_opt_arg`.
527 * On success, sets `*item`.
529 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
533 parse_orig_arg_opt_ret_t
parse_long_opt(const char * const long_opt_arg
,
534 const char * const next_orig_arg
,
535 const argpar_opt_descr_t
* const descrs
,
536 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
537 argpar_item_t
** const item
)
539 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
540 const argpar_opt_descr_t
*descr
;
541 argpar_item_opt_t
*opt_item
;
542 bool used_next_orig_arg
= false;
544 /* Option's argument, if any */
545 const char *opt_arg
= NULL
;
547 /* Position of first `=`, if any */
551 const char *long_opt_name
= long_opt_arg
;
553 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
555 /* Find the first `=` in original argument */
556 eq_pos
= strchr(long_opt_arg
, '=');
558 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
560 /* Isolate the option name */
561 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
562 iter
->tmp_buf
.size
*= 2;
563 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
564 char, iter
->tmp_buf
.size
);
565 if (!iter
->tmp_buf
.data
) {
566 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
571 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
572 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
573 long_opt_name
= iter
->tmp_buf
.data
;
576 /* Find corresponding option descriptor */
577 descr
= find_descr(descrs
, '\0', long_opt_name
);
579 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
581 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
582 long_opt_name
, NULL
, false)) {
583 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
589 /* Find option's argument if any */
590 if (descr
->with_arg
) {
592 /* `--long-opt=arg` style */
593 opt_arg
= eq_pos
+ 1;
595 /* `--long-opt arg` style */
596 if (!next_orig_arg
) {
597 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
599 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
600 NULL
, descr
, false)) {
601 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
607 opt_arg
= next_orig_arg
;
608 used_next_orig_arg
= true;
612 * Unexpected `--opt=arg` style for a long option which
613 * doesn't accept an argument.
615 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
617 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
,
618 NULL
, descr
, false)) {
619 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
625 /* Create and append option argument */
626 opt_item
= create_opt_item(descr
, opt_arg
);
631 if (used_next_orig_arg
) {
637 *item
= &opt_item
->base
;
641 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
648 * Parses the original argument `orig_arg`.
650 * On success, sets `*item`.
652 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
656 parse_orig_arg_opt_ret_t
parse_orig_arg_opt(const char * const orig_arg
,
657 const char * const next_orig_arg
,
658 const argpar_opt_descr_t
* const descrs
,
659 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
660 argpar_item_t
** const item
)
662 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
664 ARGPAR_ASSERT(orig_arg
[0] == '-');
666 if (orig_arg
[1] == '-') {
668 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
,
672 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
,
680 argpar_iter_t
*argpar_iter_create(const unsigned int argc
,
681 const char * const * const argv
,
682 const argpar_opt_descr_t
* const descrs
)
684 argpar_iter_t
*iter
= ARGPAR_ZALLOC(argpar_iter_t
);
690 iter
->user
.argc
= argc
;
691 iter
->user
.argv
= argv
;
692 iter
->user
.descrs
= descrs
;
693 iter
->tmp_buf
.size
= 128;
694 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
695 if (!iter
->tmp_buf
.data
) {
696 argpar_iter_destroy(iter
);
706 void argpar_iter_destroy(argpar_iter_t
* const iter
)
709 free(iter
->tmp_buf
.data
);
715 argpar_iter_next_status_t
argpar_iter_next(argpar_iter_t
* const iter
,
716 const argpar_item_t
** const item
,
717 const argpar_error_t
** const error
)
719 argpar_iter_next_status_t status
;
720 parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret
;
721 const char *orig_arg
;
722 const char *next_orig_arg
;
723 argpar_error_t
** const nc_error
= (argpar_error_t
**) error
;
725 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
731 if (iter
->i
== iter
->user
.argc
) {
732 status
= ARGPAR_ITER_NEXT_STATUS_END
;
736 orig_arg
= iter
->user
.argv
[iter
->i
];
738 iter
->i
< (iter
->user
.argc
- 1) ?
739 iter
->user
.argv
[iter
->i
+ 1] : NULL
;
741 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
742 orig_arg
[0] != '-') {
743 /* Non-option argument */
744 const argpar_item_non_opt_t
* const non_opt_item
=
745 create_non_opt_item(orig_arg
, iter
->i
,
746 iter
->non_opt_index
);
749 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
753 iter
->non_opt_index
++;
755 *item
= &non_opt_item
->base
;
756 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
760 /* Option argument */
761 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
762 next_orig_arg
, iter
->user
.descrs
, iter
, nc_error
,
763 (argpar_item_t
**) item
);
764 switch (parse_orig_arg_opt_ret
) {
765 case PARSE_ORIG_ARG_OPT_RET_OK
:
766 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
768 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
770 ARGPAR_ASSERT(*error
);
771 (*nc_error
)->orig_index
= iter
->i
;
773 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
775 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
776 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
787 unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t
* const iter
)