2 * SPDX-License-Identifier: MIT
3 * SPDX-FileCopyrightText: 2019-2024 Philippe Proulx <pproulx@efficios.com>
4 * SPDX-FileCopyrightText: 2020-2024 Simon Marchi <simon.marchi@efficios.com>
15 * If argpar is used in some shared library, we don't want said library
16 * to export its symbols, so mark them as "hidden".
18 * On Windows, symbols are local unless explicitly exported; see
19 * <https://gcc.gnu.org/wiki/Visibility>.
21 #if defined(_WIN32) || defined(__CYGWIN__)
22 # define ARGPAR_HIDDEN
24 # define ARGPAR_HIDDEN __attribute__((visibility("hidden")))
27 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
29 #define ARGPAR_CALLOC(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
31 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
35 * Force usage of the assertion condition to prevent unused variable
36 * warnings when `assert()` are disabled by the `NDEBUG` definition.
38 # define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
41 # define ARGPAR_ASSERT(_cond) assert(_cond)
47 * Such a structure contains the state of an iterator between calls to
53 * Data provided by the user to argpar_iter_create(); immutable
59 const char * const *argv
;
60 const argpar_opt_descr_t
*descrs
;
64 * Index of the argument to process in the next
65 * argpar_iter_next() call.
69 /* Counter of non-option arguments */
73 * Current character within the current short option group: if
74 * it's not `NULL`, the parser is within a short option group,
75 * therefore it must resume there in the next argpar_iter_next()
78 const char *short_opt_group_ch
;
80 /* Temporary character buffer which only grows */
88 /* Base parsing item */
91 argpar_item_type_t type
;
94 /* Option parsing item */
95 typedef struct argpar_item_opt
99 /* Corresponding descriptor */
100 const argpar_opt_descr_t
*descr
;
102 /* Argument, or `NULL` if none; owned by this */
106 /* Non-option parsing item */
107 typedef struct argpar_item_non_opt
112 * Complete argument, pointing to one of the entries of the
113 * original arguments (`argv`).
118 * Index of this argument amongst all original arguments
121 unsigned int orig_index
;
123 /* Index of this argument amongst other non-option arguments */
124 unsigned int non_opt_index
;
125 } argpar_item_non_opt_t
;
131 argpar_error_type_t type
;
133 /* Original argument index */
134 unsigned int orig_index
;
136 /* Name of unknown option; owned by this */
137 char *unknown_opt_name
;
139 /* Option descriptor */
140 const argpar_opt_descr_t
*opt_descr
;
142 /* `true` if a short option caused the error */
146 ARGPAR_HIDDEN argpar_item_type_t
argpar_item_type(const argpar_item_t
* const item
)
152 ARGPAR_HIDDEN
const argpar_opt_descr_t
*argpar_item_opt_descr(const argpar_item_t
* const item
)
155 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
156 return ((const argpar_item_opt_t
*) item
)->descr
;
159 ARGPAR_HIDDEN
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
;
166 ARGPAR_HIDDEN
const char *argpar_item_non_opt_arg(const argpar_item_t
* const item
)
169 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
170 return ((const argpar_item_non_opt_t
*) item
)->arg
;
173 ARGPAR_HIDDEN
unsigned int argpar_item_non_opt_orig_index(const argpar_item_t
* const item
)
176 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
177 return ((const argpar_item_non_opt_t
*) item
)->orig_index
;
180 ARGPAR_HIDDEN
unsigned int argpar_item_non_opt_non_opt_index(const argpar_item_t
* const item
)
183 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
184 return ((const argpar_item_non_opt_t
*) item
)->non_opt_index
;
187 ARGPAR_HIDDEN
void argpar_item_destroy(const argpar_item_t
* const item
)
193 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
194 argpar_item_opt_t
* const opt_item
= (argpar_item_opt_t
*) item
;
206 * Creates and returns an option parsing item for the descriptor `descr`
207 * and having the argument `arg` (copied; may be `NULL`).
209 * Returns `NULL` on memory error.
211 static argpar_item_opt_t
*create_opt_item(const argpar_opt_descr_t
* const descr
,
212 const char * const arg
)
214 argpar_item_opt_t
*opt_item
= ARGPAR_ZALLOC(argpar_item_opt_t
);
220 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
221 opt_item
->descr
= descr
;
224 opt_item
->arg
= strdup(arg
);
225 if (!opt_item
->arg
) {
233 argpar_item_destroy(&opt_item
->base
);
241 * Creates and returns a non-option parsing item for the original
242 * argument `arg` having the original index `orig_index` and the
243 * non-option index `non_opt_index`.
245 * Returns `NULL` on memory error.
247 static argpar_item_non_opt_t
*create_non_opt_item(const char * const arg
,
248 const unsigned int orig_index
,
249 const unsigned int non_opt_index
)
251 argpar_item_non_opt_t
* const non_opt_item
= ARGPAR_ZALLOC(argpar_item_non_opt_t
);
257 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
258 non_opt_item
->arg
= arg
;
259 non_opt_item
->orig_index
= orig_index
;
260 non_opt_item
->non_opt_index
= non_opt_index
;
267 * If `error` is not `NULL`, sets the error `error` to a new parsing
268 * error object, setting its `unknown_opt_name`, `opt_descr`, and
269 * `is_short` members from the parameters.
271 * `unknown_opt_name` is the unknown option name without any `-` or `--`
272 * prefix: `is_short` controls which type of unknown option it is.
274 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
277 static int set_error(argpar_error_t
** const error
, argpar_error_type_t type
,
278 const char * const unknown_opt_name
,
279 const argpar_opt_descr_t
* const opt_descr
, const bool is_short
)
287 *error
= ARGPAR_ZALLOC(argpar_error_t
);
292 (*error
)->type
= type
;
294 if (unknown_opt_name
) {
295 (*error
)->unknown_opt_name
=
296 ARGPAR_CALLOC(char, strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
297 if (!(*error
)->unknown_opt_name
) {
302 strcpy((*error
)->unknown_opt_name
, "-");
304 strcpy((*error
)->unknown_opt_name
, "--");
307 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
310 (*error
)->opt_descr
= opt_descr
;
311 (*error
)->is_short
= is_short
;
315 argpar_error_destroy(*error
);
322 ARGPAR_HIDDEN argpar_error_type_t
argpar_error_type(const argpar_error_t
* const error
)
324 ARGPAR_ASSERT(error
);
328 ARGPAR_HIDDEN
unsigned int argpar_error_orig_index(const argpar_error_t
* const error
)
330 ARGPAR_ASSERT(error
);
331 return error
->orig_index
;
334 ARGPAR_HIDDEN
const char *argpar_error_unknown_opt_name(const argpar_error_t
* 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
;
342 ARGPAR_HIDDEN
const argpar_opt_descr_t
*argpar_error_opt_descr(const argpar_error_t
* const error
,
343 bool * const is_short
)
345 ARGPAR_ASSERT(error
);
346 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
347 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
348 ARGPAR_ASSERT(error
->opt_descr
);
351 *is_short
= error
->is_short
;
354 return error
->opt_descr
;
357 ARGPAR_HIDDEN
void argpar_error_destroy(const argpar_error_t
* const error
)
360 free(error
->unknown_opt_name
);
361 free((void *) error
);
366 * Finds and returns the _first_ descriptor having the short option name
367 * `short_name` or the long option name `long_name` within the option
368 * descriptors `descrs`.
370 * `short_name` may be `'\0'` to not consider it.
372 * `long_name` may be `NULL` to not consider it.
374 * Returns `NULL` if no descriptor is found.
376 static const argpar_opt_descr_t
*find_descr(const argpar_opt_descr_t
* const descrs
,
377 const char short_name
, const char * const long_name
)
379 const argpar_opt_descr_t
*descr
;
381 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
382 if (short_name
&& descr
->short_name
&& short_name
== descr
->short_name
) {
386 if (long_name
&& descr
->long_name
&& strcmp(long_name
, descr
->long_name
) == 0) {
392 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
395 /* Return type of parse_short_opt_group() and parse_long_opt() */
396 typedef enum parse_orig_arg_opt_ret
398 PARSE_ORIG_ARG_OPT_RET_OK
,
399 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
400 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
401 } parse_orig_arg_opt_ret_t
;
404 * Parses the short option group argument `short_opt_group`, starting
405 * where needed depending on the state of `iter`.
407 * On success, sets `*item`.
409 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
412 static parse_orig_arg_opt_ret_t
413 parse_short_opt_group(const char * const short_opt_group
, const char * const next_orig_arg
,
414 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
415 argpar_error_t
** const error
, argpar_item_t
** const item
)
417 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
418 bool used_next_orig_arg
= false;
419 const char *opt_arg
= NULL
;
420 const argpar_opt_descr_t
*descr
;
421 argpar_item_opt_t
*opt_item
;
423 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
425 if (!iter
->short_opt_group_ch
) {
426 iter
->short_opt_group_ch
= short_opt_group
;
429 /* Find corresponding option descriptor */
430 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
432 const char unknown_opt_name
[] = {*iter
->short_opt_group_ch
, '\0'};
434 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
436 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, unknown_opt_name
, NULL
, true)) {
437 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
443 if (descr
->with_arg
) {
444 if (iter
->short_opt_group_ch
[1]) {
446 opt_arg
= &iter
->short_opt_group_ch
[1];
449 opt_arg
= next_orig_arg
;
450 used_next_orig_arg
= true;
454 * We accept `-o ''` (empty option argument), but not
455 * `-o` alone if an option argument is expected.
457 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] && strlen(opt_arg
) == 0)) {
458 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
460 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
, NULL
, descr
, true)) {
461 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
468 /* Create and append option argument */
469 opt_item
= create_opt_item(descr
, opt_arg
);
471 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
475 *item
= &opt_item
->base
;
476 iter
->short_opt_group_ch
++;
478 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
479 /* Option has an argument: no more options */
480 iter
->short_opt_group_ch
= NULL
;
482 if (used_next_orig_arg
) {
492 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
499 * Parses the long option argument `long_opt_arg`.
501 * On success, sets `*item`.
503 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
506 static parse_orig_arg_opt_ret_t
507 parse_long_opt(const char * const long_opt_arg
, const char * const next_orig_arg
,
508 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
509 argpar_error_t
** const error
, argpar_item_t
** const item
)
511 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
512 const argpar_opt_descr_t
*descr
;
513 argpar_item_opt_t
*opt_item
;
514 bool used_next_orig_arg
= false;
516 /* Option's argument, if any */
517 const char *opt_arg
= NULL
;
519 /* Position of first `=`, if any */
523 const char *long_opt_name
= long_opt_arg
;
525 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
527 /* Find the first `=` in original argument */
528 eq_pos
= strchr(long_opt_arg
, '=');
530 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
532 /* Isolate the option name */
533 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
534 const size_t new_size
= iter
->tmp_buf
.size
* 2;
535 char * const new_data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
, char, new_size
);
538 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
542 iter
->tmp_buf
.size
= new_size
;
543 iter
->tmp_buf
.data
= new_data
;
546 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
547 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
548 long_opt_name
= iter
->tmp_buf
.data
;
551 /* Find corresponding option descriptor */
552 descr
= find_descr(descrs
, '\0', long_opt_name
);
554 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
556 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, long_opt_name
, NULL
, false)) {
557 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
563 /* Find option's argument if any */
564 if (descr
->with_arg
) {
566 /* `--long-opt=arg` style */
567 opt_arg
= eq_pos
+ 1;
569 /* `--long-opt arg` style */
570 if (!next_orig_arg
) {
571 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
573 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
, NULL
, descr
, false)) {
574 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
580 opt_arg
= next_orig_arg
;
581 used_next_orig_arg
= true;
585 * Unexpected `--opt=arg` style for a long option which
586 * doesn't accept an argument.
588 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
590 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
, NULL
, descr
, false)) {
591 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
597 /* Create and append option argument */
598 opt_item
= create_opt_item(descr
, opt_arg
);
603 if (used_next_orig_arg
) {
609 *item
= &opt_item
->base
;
613 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
620 * Parses the original argument `orig_arg`.
622 * On success, sets `*item`.
624 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
627 static parse_orig_arg_opt_ret_t
628 parse_orig_arg_opt(const char * const orig_arg
, const char * const next_orig_arg
,
629 const argpar_opt_descr_t
* const descrs
, argpar_iter_t
* const iter
,
630 argpar_error_t
** const error
, argpar_item_t
** const item
)
632 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
634 ARGPAR_ASSERT(orig_arg
[0] == '-');
636 if (orig_arg
[1] == '-') {
638 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
, error
, item
);
641 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
, iter
, error
, item
);
647 ARGPAR_HIDDEN argpar_iter_t
*argpar_iter_create(const unsigned int argc
,
648 const char * const * const argv
,
649 const argpar_opt_descr_t
* const descrs
)
651 argpar_iter_t
*iter
= ARGPAR_ZALLOC(argpar_iter_t
);
657 iter
->user
.argc
= argc
;
658 iter
->user
.argv
= argv
;
659 iter
->user
.descrs
= descrs
;
660 iter
->tmp_buf
.size
= 128;
661 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
662 if (!iter
->tmp_buf
.data
) {
663 argpar_iter_destroy(iter
);
672 ARGPAR_HIDDEN
void argpar_iter_destroy(argpar_iter_t
* const iter
)
675 free(iter
->tmp_buf
.data
);
680 ARGPAR_HIDDEN argpar_iter_next_status_t
argpar_iter_next(argpar_iter_t
* const iter
,
681 const argpar_item_t
** const item
,
682 const argpar_error_t
** const error
)
684 argpar_iter_next_status_t status
;
685 parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret
;
686 const char *orig_arg
;
687 const char *next_orig_arg
;
688 argpar_error_t
** const nc_error
= (argpar_error_t
**) error
;
690 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
696 if (iter
->i
== iter
->user
.argc
) {
697 status
= ARGPAR_ITER_NEXT_STATUS_END
;
701 orig_arg
= iter
->user
.argv
[iter
->i
];
702 next_orig_arg
= iter
->i
< (iter
->user
.argc
- 1) ? iter
->user
.argv
[iter
->i
+ 1] : NULL
;
704 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 || orig_arg
[0] != '-') {
705 /* Non-option argument */
706 const argpar_item_non_opt_t
* const non_opt_item
=
707 create_non_opt_item(orig_arg
, iter
->i
, iter
->non_opt_index
);
710 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
714 iter
->non_opt_index
++;
716 *item
= &non_opt_item
->base
;
717 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
721 /* Option argument */
722 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
, next_orig_arg
, iter
->user
.descrs
, iter
,
723 nc_error
, (argpar_item_t
**) item
);
724 switch (parse_orig_arg_opt_ret
) {
725 case PARSE_ORIG_ARG_OPT_RET_OK
:
726 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
728 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
730 ARGPAR_ASSERT(*error
);
731 (*nc_error
)->orig_index
= iter
->i
;
733 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
735 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
736 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
746 ARGPAR_HIDDEN
unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t
* const iter
)