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 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
17 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
19 #define ARGPAR_CALLOC(_type, _nmemb) \
20 ((_type *) calloc((_nmemb), sizeof(_type)))
22 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
26 * Force usage of the assertion condition to prevent unused variable
27 * warnings when `assert()` are disabled by the `NDEBUG` definition.
29 # define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
32 # define ARGPAR_ASSERT(_cond) assert(_cond)
38 * Such a structure contains the state of an iterator between calls to
43 * Data provided by the user to argpar_iter_create(); immutable
48 const char * const *argv
;
49 const argpar_opt_descr_t
*descrs
;
53 * Index of the argument to process in the next
54 * argpar_iter_next() call.
58 /* Counter of non-option arguments */
62 * Current character within the current short option group: if
63 * it's not `NULL`, the parser is within a short option group,
64 * therefore it must resume there in the next argpar_iter_next()
67 const char *short_opt_group_ch
;
69 /* Temporary character buffer which only grows */
76 /* Base parsing item */
78 argpar_item_type_t type
;
81 /* Option parsing item */
82 typedef struct argpar_item_opt
{
85 /* Corresponding descriptor */
86 const argpar_opt_descr_t
*descr
;
88 /* Argument, or `NULL` if none; owned by this */
92 /* Non-option parsing item */
93 typedef struct argpar_item_non_opt
{
97 * Complete argument, pointing to one of the entries of the
98 * original arguments (`argv`).
103 * Index of this argument amongst all original arguments
106 unsigned int orig_index
;
108 /* Index of this argument amongst other non-option arguments */
109 unsigned int non_opt_index
;
110 } argpar_item_non_opt_t
;
113 struct argpar_error
{
115 argpar_error_type_t type
;
117 /* Original argument index */
118 unsigned int orig_index
;
120 /* Name of unknown option; owned by this */
121 char *unknown_opt_name
;
123 /* Option descriptor */
124 const argpar_opt_descr_t
*opt_descr
;
126 /* `true` if a short option caused the error */
131 argpar_item_type_t
argpar_item_type(const argpar_item_t
* const item
)
138 const argpar_opt_descr_t
*argpar_item_opt_descr(
139 const argpar_item_t
* const item
)
142 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
143 return ((const argpar_item_opt_t
*) item
)->descr
;
147 const char *argpar_item_opt_arg(const argpar_item_t
* const item
)
150 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
151 return ((const argpar_item_opt_t
*) item
)->arg
;
155 const char *argpar_item_non_opt_arg(const argpar_item_t
* const item
)
158 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
159 return ((const argpar_item_non_opt_t
*) item
)->arg
;
163 unsigned int argpar_item_non_opt_orig_index(
164 const argpar_item_t
* const item
)
167 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
168 return ((const argpar_item_non_opt_t
*) item
)->orig_index
;
172 unsigned int argpar_item_non_opt_non_opt_index(
173 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
)->non_opt_index
;
181 void argpar_item_destroy(const argpar_item_t
* const item
)
187 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
188 argpar_item_opt_t
* const opt_item
= (argpar_item_opt_t
*) item
;
200 * Creates and returns an option parsing item for the descriptor `descr`
201 * and having the argument `arg` (copied; may be `NULL`).
203 * Returns `NULL` on memory error.
206 argpar_item_opt_t
*create_opt_item(const argpar_opt_descr_t
* const descr
,
207 const char * const arg
)
209 argpar_item_opt_t
*opt_item
= ARGPAR_ZALLOC(argpar_item_opt_t
);
215 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
216 opt_item
->descr
= descr
;
219 opt_item
->arg
= strdup(arg
);
220 if (!opt_item
->arg
) {
228 argpar_item_destroy(&opt_item
->base
);
236 * Creates and returns a non-option parsing item for the original
237 * argument `arg` having the original index `orig_index` and the
238 * non-option index `non_opt_index`.
240 * Returns `NULL` on memory error.
243 argpar_item_non_opt_t
*create_non_opt_item(const char * const arg
,
244 const unsigned int orig_index
,
245 const unsigned int non_opt_index
)
247 argpar_item_non_opt_t
* const non_opt_item
=
248 ARGPAR_ZALLOC(argpar_item_non_opt_t
);
254 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
255 non_opt_item
->arg
= arg
;
256 non_opt_item
->orig_index
= orig_index
;
257 non_opt_item
->non_opt_index
= non_opt_index
;
264 * If `error` is not `NULL`, sets the error `error` to a new parsing
265 * error object, setting its `unknown_opt_name`, `opt_descr`, and
266 * `is_short` members from the parameters.
268 * `unknown_opt_name` is the unknown option name without any `-` or `--`
269 * prefix: `is_short` controls which type of unknown option it is.
271 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
275 int set_error(argpar_error_t
** const error
, argpar_error_type_t type
,
276 const char * const unknown_opt_name
,
277 const argpar_opt_descr_t
* const opt_descr
, const bool is_short
)
285 *error
= ARGPAR_ZALLOC(argpar_error_t
);
290 (*error
)->type
= type
;
292 if (unknown_opt_name
) {
293 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
294 strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
295 if (!(*error
)->unknown_opt_name
) {
300 strcpy((*error
)->unknown_opt_name
, "-");
302 strcpy((*error
)->unknown_opt_name
, "--");
305 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
308 (*error
)->opt_descr
= opt_descr
;
309 (*error
)->is_short
= is_short
;
313 argpar_error_destroy(*error
);
321 argpar_error_type_t
argpar_error_type(
322 const argpar_error_t
* const error
)
324 ARGPAR_ASSERT(error
);
329 unsigned int argpar_error_orig_index(const argpar_error_t
* const error
)
331 ARGPAR_ASSERT(error
);
332 return error
->orig_index
;
336 const char *argpar_error_unknown_opt_name(
337 const argpar_error_t
* const error
)
339 ARGPAR_ASSERT(error
);
340 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
341 ARGPAR_ASSERT(error
->unknown_opt_name
);
342 return error
->unknown_opt_name
;
346 const argpar_opt_descr_t
*argpar_error_opt_descr(
347 const argpar_error_t
* const error
, bool * const is_short
)
349 ARGPAR_ASSERT(error
);
350 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
351 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
352 ARGPAR_ASSERT(error
->opt_descr
);
355 *is_short
= error
->is_short
;
358 return error
->opt_descr
;
362 void argpar_error_destroy(const argpar_error_t
* const error
)
365 free(error
->unknown_opt_name
);
366 free((void *) error
);
371 * Finds and returns the _first_ descriptor having the short option name
372 * `short_name` or the long option name `long_name` within the option
373 * descriptors `descrs`.
375 * `short_name` may be `'\0'` to not consider it.
377 * `long_name` may be `NULL` to not consider it.
379 * Returns `NULL` if no descriptor is found.
382 const argpar_opt_descr_t
*find_descr(const argpar_opt_descr_t
* const descrs
,
383 const char short_name
, const char * const long_name
)
385 const argpar_opt_descr_t
*descr
;
387 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
388 if (short_name
&& descr
->short_name
&&
389 short_name
== descr
->short_name
) {
393 if (long_name
&& descr
->long_name
&&
394 strcmp(long_name
, descr
->long_name
) == 0) {
400 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
403 /* Return type of parse_short_opt_group() and parse_long_opt() */
404 typedef enum parse_orig_arg_opt_ret
{
405 PARSE_ORIG_ARG_OPT_RET_OK
,
406 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
407 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
408 } parse_orig_arg_opt_ret_t
;
411 * Parses the short option group argument `short_opt_group`, starting
412 * where needed depending on the state of `iter`.
414 * On success, sets `*item`.
416 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
420 parse_orig_arg_opt_ret_t
parse_short_opt_group(
421 const char * const short_opt_group
,
422 const char * const next_orig_arg
,
423 const argpar_opt_descr_t
* const descrs
,
424 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
425 argpar_item_t
** const item
)
427 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
428 bool used_next_orig_arg
= false;
429 const char *opt_arg
= NULL
;
430 const argpar_opt_descr_t
*descr
;
431 argpar_item_opt_t
*opt_item
;
433 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
435 if (!iter
->short_opt_group_ch
) {
436 iter
->short_opt_group_ch
= short_opt_group
;
439 /* Find corresponding option descriptor */
440 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
442 const char unknown_opt_name
[] =
443 {*iter
->short_opt_group_ch
, '\0'};
445 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
447 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
,
448 unknown_opt_name
, NULL
, true)) {
449 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
455 if (descr
->with_arg
) {
456 if (iter
->short_opt_group_ch
[1]) {
458 opt_arg
= &iter
->short_opt_group_ch
[1];
461 opt_arg
= next_orig_arg
;
462 used_next_orig_arg
= true;
466 * We accept `-o ''` (empty option argument), but not
467 * `-o` alone if an option argument is expected.
469 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
470 strlen(opt_arg
) == 0)) {
471 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
473 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
474 NULL
, descr
, true)) {
475 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
482 /* Create and append option argument */
483 opt_item
= create_opt_item(descr
, opt_arg
);
485 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
489 *item
= &opt_item
->base
;
490 iter
->short_opt_group_ch
++;
492 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
493 /* Option has an argument: no more options */
494 iter
->short_opt_group_ch
= NULL
;
496 if (used_next_orig_arg
) {
506 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
513 * Parses the long option argument `long_opt_arg`.
515 * On success, sets `*item`.
517 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
521 parse_orig_arg_opt_ret_t
parse_long_opt(const char * const long_opt_arg
,
522 const char * const next_orig_arg
,
523 const argpar_opt_descr_t
* const descrs
,
524 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
525 argpar_item_t
** const item
)
527 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
528 const argpar_opt_descr_t
*descr
;
529 argpar_item_opt_t
*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 parse_orig_arg_opt_ret_t
parse_orig_arg_opt(const char * const orig_arg
,
645 const char * const next_orig_arg
,
646 const argpar_opt_descr_t
* const descrs
,
647 argpar_iter_t
* const iter
, argpar_error_t
** const error
,
648 argpar_item_t
** const item
)
650 parse_orig_arg_opt_ret_t ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
652 ARGPAR_ASSERT(orig_arg
[0] == '-');
654 if (orig_arg
[1] == '-') {
656 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
,
660 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
,
668 argpar_iter_t
*argpar_iter_create(const unsigned int argc
,
669 const char * const * const argv
,
670 const argpar_opt_descr_t
* const descrs
)
672 argpar_iter_t
*iter
= ARGPAR_ZALLOC(argpar_iter_t
);
678 iter
->user
.argc
= argc
;
679 iter
->user
.argv
= argv
;
680 iter
->user
.descrs
= descrs
;
681 iter
->tmp_buf
.size
= 128;
682 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
683 if (!iter
->tmp_buf
.data
) {
684 argpar_iter_destroy(iter
);
694 void argpar_iter_destroy(argpar_iter_t
* const iter
)
697 free(iter
->tmp_buf
.data
);
703 argpar_iter_next_status_t
argpar_iter_next(argpar_iter_t
* const iter
,
704 const argpar_item_t
** const item
,
705 const argpar_error_t
** const error
)
707 argpar_iter_next_status_t status
;
708 parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret
;
709 const char *orig_arg
;
710 const char *next_orig_arg
;
711 argpar_error_t
** const nc_error
= (argpar_error_t
**) error
;
713 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
719 if (iter
->i
== iter
->user
.argc
) {
720 status
= ARGPAR_ITER_NEXT_STATUS_END
;
724 orig_arg
= iter
->user
.argv
[iter
->i
];
726 iter
->i
< (iter
->user
.argc
- 1) ?
727 iter
->user
.argv
[iter
->i
+ 1] : NULL
;
729 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
730 orig_arg
[0] != '-') {
731 /* Non-option argument */
732 const argpar_item_non_opt_t
* const non_opt_item
=
733 create_non_opt_item(orig_arg
, iter
->i
,
734 iter
->non_opt_index
);
737 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
741 iter
->non_opt_index
++;
743 *item
= &non_opt_item
->base
;
744 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
748 /* Option argument */
749 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
750 next_orig_arg
, iter
->user
.descrs
, iter
, nc_error
,
751 (argpar_item_t
**) item
);
752 switch (parse_orig_arg_opt_ret
) {
753 case PARSE_ORIG_ARG_OPT_RET_OK
:
754 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
756 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
758 ARGPAR_ASSERT(*error
);
759 (*nc_error
)->orig_index
= iter
->i
;
761 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
763 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
764 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
775 unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t
* const iter
)