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) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
30 #define ARGPAR_CALLOC(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
32 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
36 * Force usage of the assertion condition to prevent unused variable
37 * warnings when `assert()` are disabled by the `NDEBUG` definition.
39 # define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
42 # define ARGPAR_ASSERT(_cond) assert(_cond)
48 * Such a structure contains the state of an iterator between calls to
54 * 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 */
89 /* Base parsing item */
92 argpar_item_type_t type
;
95 /* Option parsing item */
96 typedef struct argpar_item_opt
100 /* Corresponding descriptor */
101 const argpar_opt_descr_t
*descr
;
103 /* Argument, or `NULL` if none; owned by this */
107 /* Non-option parsing item */
108 typedef struct argpar_item_non_opt
113 * Complete argument, pointing to one of the entries of the
114 * original arguments (`argv`).
119 * Index of this argument amongst all original arguments
122 unsigned int orig_index
;
124 /* Index of this argument amongst other non-option arguments */
125 unsigned int non_opt_index
;
126 } argpar_item_non_opt_t
;
132 argpar_error_type_t type
;
134 /* Original argument index */
135 unsigned int orig_index
;
137 /* Name of unknown option; owned by this */
138 char *unknown_opt_name
;
140 /* Option descriptor */
141 const argpar_opt_descr_t
*opt_descr
;
143 /* `true` if a short option caused the error */
147 ARGPAR_HIDDEN argpar_item_type_t
argpar_item_type(const argpar_item_t
* const item
)
153 ARGPAR_HIDDEN
const argpar_opt_descr_t
*argpar_item_opt_descr(const argpar_item_t
* const item
)
156 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
157 return ((const argpar_item_opt_t
*) item
)->descr
;
160 ARGPAR_HIDDEN
const char *argpar_item_opt_arg(const argpar_item_t
* const item
)
163 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
164 return ((const argpar_item_opt_t
*) item
)->arg
;
167 ARGPAR_HIDDEN
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
;
174 ARGPAR_HIDDEN
unsigned int argpar_item_non_opt_orig_index(const argpar_item_t
* const item
)
177 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
178 return ((const argpar_item_non_opt_t
*) item
)->orig_index
;
181 ARGPAR_HIDDEN
unsigned int argpar_item_non_opt_non_opt_index(const argpar_item_t
* const item
)
184 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
185 return ((const argpar_item_non_opt_t
*) item
)->non_opt_index
;
188 ARGPAR_HIDDEN
void argpar_item_destroy(const argpar_item_t
* const item
)
194 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
195 argpar_item_opt_t
* const opt_item
= (argpar_item_opt_t
*) item
;
207 * Creates and returns an option parsing item for the descriptor `descr`
208 * and having the argument `arg` (copied; may be `NULL`).
210 * Returns `NULL` on memory error.
212 static argpar_item_opt_t
*create_opt_item(const argpar_opt_descr_t
* const descr
,
213 const char * const arg
)
215 argpar_item_opt_t
*opt_item
= ARGPAR_ZALLOC(argpar_item_opt_t
);
221 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
222 opt_item
->descr
= descr
;
225 opt_item
->arg
= strdup(arg
);
226 if (!opt_item
->arg
) {
234 argpar_item_destroy(&opt_item
->base
);
242 * Creates and returns a non-option parsing item for the original
243 * argument `arg` having the original index `orig_index` and the
244 * non-option index `non_opt_index`.
246 * Returns `NULL` on memory error.
248 static argpar_item_non_opt_t
*create_non_opt_item(const char * const arg
,
249 const unsigned int orig_index
,
250 const unsigned int non_opt_index
)
252 argpar_item_non_opt_t
* const non_opt_item
= ARGPAR_ZALLOC(argpar_item_non_opt_t
);
258 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
259 non_opt_item
->arg
= arg
;
260 non_opt_item
->orig_index
= orig_index
;
261 non_opt_item
->non_opt_index
= non_opt_index
;
268 * If `error` is not `NULL`, sets the error `error` to a new parsing
269 * error object, setting its `unknown_opt_name`, `opt_descr`, and
270 * `is_short` members from the parameters.
272 * `unknown_opt_name` is the unknown option name without any `-` or `--`
273 * prefix: `is_short` controls which type of unknown option it is.
275 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
278 static int set_error(argpar_error_t
** const error
, argpar_error_type_t type
,
279 const char * const unknown_opt_name
,
280 const argpar_opt_descr_t
* const opt_descr
, const bool is_short
)
288 *error
= ARGPAR_ZALLOC(argpar_error_t
);
293 (*error
)->type
= type
;
295 if (unknown_opt_name
) {
296 (*error
)->unknown_opt_name
=
297 ARGPAR_CALLOC(char, strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
298 if (!(*error
)->unknown_opt_name
) {
303 strcpy((*error
)->unknown_opt_name
, "-");
305 strcpy((*error
)->unknown_opt_name
, "--");
308 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
311 (*error
)->opt_descr
= opt_descr
;
312 (*error
)->is_short
= is_short
;
316 argpar_error_destroy(*error
);
323 ARGPAR_HIDDEN argpar_error_type_t
argpar_error_type(const argpar_error_t
* const error
)
325 ARGPAR_ASSERT(error
);
329 ARGPAR_HIDDEN
unsigned int argpar_error_orig_index(const argpar_error_t
* const error
)
331 ARGPAR_ASSERT(error
);
332 return error
->orig_index
;
335 ARGPAR_HIDDEN
const char *argpar_error_unknown_opt_name(const argpar_error_t
* const error
)
337 ARGPAR_ASSERT(error
);
338 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
339 ARGPAR_ASSERT(error
->unknown_opt_name
);
340 return error
->unknown_opt_name
;
343 ARGPAR_HIDDEN
const argpar_opt_descr_t
*argpar_error_opt_descr(const argpar_error_t
* const error
,
344 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
;
358 ARGPAR_HIDDEN
void argpar_error_destroy(const argpar_error_t
* const error
)
361 free(error
->unknown_opt_name
);
362 free((void *) error
);
367 * Finds and returns the _first_ descriptor having the short option name
368 * `short_name` or the long option name `long_name` within the option
369 * descriptors `descrs`.
371 * `short_name` may be `'\0'` to not consider it.
373 * `long_name` may be `NULL` to not consider it.
375 * Returns `NULL` if no descriptor is found.
377 static const argpar_opt_descr_t
*find_descr(const argpar_opt_descr_t
* const descrs
,
378 const char short_name
, const char * const long_name
)
380 const argpar_opt_descr_t
*descr
;
382 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
383 if (short_name
&& descr
->short_name
&& short_name
== descr
->short_name
) {
387 if (long_name
&& descr
->long_name
&& strcmp(long_name
, descr
->long_name
) == 0) {
393 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
396 /* Return type of parse_short_opt_group() and parse_long_opt() */
397 typedef enum parse_orig_arg_opt_ret
399 PARSE_ORIG_ARG_OPT_RET_OK
,
400 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
401 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
402 } parse_orig_arg_opt_ret_t
;
405 * Parses the short option group argument `short_opt_group`, starting
406 * where needed depending on the state of `iter`.
408 * On success, sets `*item`.
410 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
413 static parse_orig_arg_opt_ret_t
414 parse_short_opt_group(const char * const short_opt_group
, const char * const next_orig_arg
,
415 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
416 argpar_error_t
** const error
, argpar_item_t
** const item
)
418 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
419 bool used_next_orig_arg
= false;
420 const char *opt_arg
= NULL
;
421 const argpar_opt_descr_t
*descr
;
422 argpar_item_opt_t
*opt_item
;
424 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
426 if (!iter
->short_opt_group_ch
) {
427 iter
->short_opt_group_ch
= short_opt_group
;
430 /* Find corresponding option descriptor */
431 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
433 const char unknown_opt_name
[] = {*iter
->short_opt_group_ch
, '\0'};
435 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
437 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, unknown_opt_name
, NULL
, true)) {
438 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
444 if (descr
->with_arg
) {
445 if (iter
->short_opt_group_ch
[1]) {
447 opt_arg
= &iter
->short_opt_group_ch
[1];
450 opt_arg
= next_orig_arg
;
451 used_next_orig_arg
= true;
455 * We accept `-o ''` (empty option argument), but not
456 * `-o` alone if an option argument is expected.
458 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] && strlen(opt_arg
) == 0)) {
459 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
461 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
, NULL
, descr
, true)) {
462 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
469 /* Create and append option argument */
470 opt_item
= create_opt_item(descr
, opt_arg
);
472 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
476 *item
= &opt_item
->base
;
477 iter
->short_opt_group_ch
++;
479 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
480 /* Option has an argument: no more options */
481 iter
->short_opt_group_ch
= NULL
;
483 if (used_next_orig_arg
) {
493 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
500 * Parses the long option argument `long_opt_arg`.
502 * On success, sets `*item`.
504 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
507 static parse_orig_arg_opt_ret_t
508 parse_long_opt(const char * const long_opt_arg
, const char * const next_orig_arg
,
509 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
510 argpar_error_t
** const error
, argpar_item_t
** const item
)
512 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
513 const argpar_opt_descr_t
*descr
;
514 argpar_item_opt_t
*opt_item
;
515 bool used_next_orig_arg
= false;
517 /* Option's argument, if any */
518 const char *opt_arg
= NULL
;
520 /* Position of first `=`, if any */
524 const char *long_opt_name
= long_opt_arg
;
526 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
528 /* Find the first `=` in original argument */
529 eq_pos
= strchr(long_opt_arg
, '=');
531 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
533 /* Isolate the option name */
534 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
535 const size_t new_size
= iter
->tmp_buf
.size
* 2;
536 char * const new_data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
, char, new_size
);
539 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
543 iter
->tmp_buf
.size
= new_size
;
544 iter
->tmp_buf
.data
= new_data
;
547 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
548 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
549 long_opt_name
= iter
->tmp_buf
.data
;
552 /* Find corresponding option descriptor */
553 descr
= find_descr(descrs
, '\0', long_opt_name
);
555 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
557 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, long_opt_name
, NULL
, false)) {
558 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
564 /* Find option's argument if any */
565 if (descr
->with_arg
) {
567 /* `--long-opt=arg` style */
568 opt_arg
= eq_pos
+ 1;
570 /* `--long-opt arg` style */
571 if (!next_orig_arg
) {
572 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
574 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
, NULL
, descr
, false)) {
575 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
581 opt_arg
= next_orig_arg
;
582 used_next_orig_arg
= true;
586 * Unexpected `--opt=arg` style for a long option which
587 * doesn't accept an argument.
589 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
591 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
, NULL
, descr
, false)) {
592 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
598 /* Create and append option argument */
599 opt_item
= create_opt_item(descr
, opt_arg
);
604 if (used_next_orig_arg
) {
610 *item
= &opt_item
->base
;
614 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
621 * Parses the original argument `orig_arg`.
623 * On success, sets `*item`.
625 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
628 static parse_orig_arg_opt_ret_t
629 parse_orig_arg_opt(const char * const orig_arg
, const char * const next_orig_arg
,
630 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
631 argpar_error_t
** const error
, argpar_item_t
** const item
)
633 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
635 ARGPAR_ASSERT(orig_arg
[0] == '-');
637 if (orig_arg
[1] == '-') {
639 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
, error
, item
);
642 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
, iter
, error
, item
);
648 ARGPAR_HIDDEN argpar_iter_t
*argpar_iter_create(const unsigned int argc
,
649 const char * const * const argv
,
650 const argpar_opt_descr_t
* const descrs
)
652 argpar_iter_t
*iter
= ARGPAR_ZALLOC(argpar_iter_t
);
658 iter
->user
.argc
= argc
;
659 iter
->user
.argv
= argv
;
660 iter
->user
.descrs
= descrs
;
661 iter
->tmp_buf
.size
= 128;
662 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
663 if (!iter
->tmp_buf
.data
) {
664 argpar_iter_destroy(iter
);
673 ARGPAR_HIDDEN
void argpar_iter_destroy(argpar_iter_t
* const iter
)
676 free(iter
->tmp_buf
.data
);
681 ARGPAR_HIDDEN argpar_iter_next_status_t
argpar_iter_next(argpar_iter_t
* const iter
,
682 const argpar_item_t
** const item
,
683 const argpar_error_t
** const error
)
685 argpar_iter_next_status_t status
;
686 parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret
;
687 const char *orig_arg
;
688 const char *next_orig_arg
;
689 argpar_error_t
** const nc_error
= (argpar_error_t
**) error
;
691 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
697 if (iter
->i
== iter
->user
.argc
) {
698 status
= ARGPAR_ITER_NEXT_STATUS_END
;
702 orig_arg
= iter
->user
.argv
[iter
->i
];
703 next_orig_arg
= iter
->i
< (iter
->user
.argc
- 1) ? iter
->user
.argv
[iter
->i
+ 1] : NULL
;
705 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 || orig_arg
[0] != '-') {
706 /* Non-option argument */
707 const argpar_item_non_opt_t
* const non_opt_item
=
708 create_non_opt_item(orig_arg
, iter
->i
, iter
->non_opt_index
);
711 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
715 iter
->non_opt_index
++;
717 *item
= &non_opt_item
->base
;
718 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
722 /* Option argument */
723 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
, next_orig_arg
, iter
->user
.descrs
, iter
,
724 nc_error
, (argpar_item_t
**) item
);
725 switch (parse_orig_arg_opt_ret
) {
726 case PARSE_ORIG_ARG_OPT_RET_OK
:
727 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
729 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
731 ARGPAR_ASSERT(*error
);
732 (*nc_error
)->orig_index
= iter
->i
;
734 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
736 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
737 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
747 ARGPAR_HIDDEN
unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t
* const iter
)