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>
17 #define ARGPAR_REALLOC(_ptr, _type, _nmemb) \
18 ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
20 #define ARGPAR_CALLOC(_type, _nmemb) \
21 ((_type *) calloc((_nmemb), sizeof(_type)))
23 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
25 #define ARGPAR_ASSERT(_cond) assert(_cond)
30 * Such a structure contains the state of an iterator between calls to
35 * Data provided by the user to argpar_iter_create(); immutable
40 const char * const *argv
;
41 const struct argpar_opt_descr
*descrs
;
45 * Index of the argument to process in the next
46 * argpar_iter_next() call.
50 /* Counter of non-option arguments */
54 * Current character within the current short option group: if
55 * it's not `NULL`, the parser is within a short option group,
56 * therefore it must resume there in the next argpar_iter_next()
59 const char *short_opt_group_ch
;
61 /* Temporary character buffer which only grows */
68 /* Base parsing item */
70 enum argpar_item_type type
;
73 /* Option parsing item */
74 struct argpar_item_opt
{
75 struct argpar_item base
;
77 /* Corresponding descriptor */
78 const struct argpar_opt_descr
*descr
;
80 /* Argument, or `NULL` if none; owned by this */
84 /* Non-option parsing item */
85 struct argpar_item_non_opt
{
86 struct argpar_item base
;
89 * Complete argument, pointing to one of the entries of the
90 * original arguments (`argv`).
95 * Index of this argument amongst all original arguments
98 unsigned int orig_index
;
100 /* Index of this argument amongst other non-option arguments */
101 unsigned int non_opt_index
;
105 struct argpar_error
{
106 /* Original argument index */
107 unsigned int orig_index
;
109 /* Name of unknown option; owned by this */
110 char *unknown_opt_name
;
112 /* Option descriptor */
113 const struct argpar_opt_descr
*opt_descr
;
115 /* `true` if a short option caused the error */
120 enum argpar_item_type
argpar_item_type(const struct argpar_item
* const item
)
127 const struct argpar_opt_descr
*argpar_item_opt_descr(
128 const struct argpar_item
* const item
)
131 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
132 return ((const struct argpar_item_opt
*) item
)->descr
;
136 const char *argpar_item_opt_arg(const struct argpar_item
* const item
)
139 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
140 return ((const struct argpar_item_opt
*) item
)->arg
;
144 const char *argpar_item_non_opt_arg(const struct argpar_item
* const item
)
147 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
148 return ((const struct argpar_item_non_opt
*) item
)->arg
;
152 unsigned int argpar_item_non_opt_orig_index(
153 const struct argpar_item
* const item
)
156 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
157 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
161 unsigned int argpar_item_non_opt_non_opt_index(
162 const struct argpar_item
* const item
)
165 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
166 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
170 void argpar_item_destroy(const struct argpar_item
* const item
)
176 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
177 struct argpar_item_opt
* const opt_item
=
178 (struct argpar_item_opt
*) item
;
190 * Creates and returns an option parsing item for the descriptor `descr`
191 * and having the argument `arg` (copied; may be `NULL`).
193 * Returns `NULL` on memory error.
196 struct argpar_item_opt
*create_opt_item(
197 const struct argpar_opt_descr
* const descr
,
198 const char * const arg
)
200 struct argpar_item_opt
*opt_item
=
201 ARGPAR_ZALLOC(struct argpar_item_opt
);
207 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
208 opt_item
->descr
= descr
;
211 opt_item
->arg
= strdup(arg
);
212 if (!opt_item
->arg
) {
220 argpar_item_destroy(&opt_item
->base
);
228 * Creates and returns a non-option parsing item for the original
229 * argument `arg` having the original index `orig_index` and the
230 * non-option index `non_opt_index`.
232 * Returns `NULL` on memory error.
235 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
236 const unsigned int orig_index
,
237 const unsigned int non_opt_index
)
239 struct argpar_item_non_opt
* const non_opt_item
=
240 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
246 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
247 non_opt_item
->arg
= arg
;
248 non_opt_item
->orig_index
= orig_index
;
249 non_opt_item
->non_opt_index
= non_opt_index
;
256 * If `error` is not `NULL`, sets the error `error` to a new parsing
257 * error object, setting its `unknown_opt_name`, `opt_descr`, and
258 * `is_short` members from the parameters.
260 * `unknown_opt_name` is the unknown option name without any `-` or `--`
261 * prefix: `is_short` controls which type of unknown option it is.
263 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
267 int set_error(struct argpar_error
** const error
,
268 const char * const unknown_opt_name
,
269 const struct argpar_opt_descr
* const opt_descr
,
278 *error
= ARGPAR_ZALLOC(struct argpar_error
);
283 if (unknown_opt_name
) {
284 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
285 strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
286 if (!(*error
)->unknown_opt_name
) {
291 strcpy((*error
)->unknown_opt_name
, "-");
293 strcpy((*error
)->unknown_opt_name
, "--");
296 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
299 (*error
)->opt_descr
= opt_descr
;
300 (*error
)->is_short
= is_short
;
304 argpar_error_destroy(*error
);
312 unsigned int argpar_error_orig_index(const struct argpar_error
* const error
)
314 ARGPAR_ASSERT(error
);
315 return error
->orig_index
;
319 const char *argpar_error_unknown_opt_name(
320 const struct argpar_error
* const error
)
322 ARGPAR_ASSERT(error
);
323 ARGPAR_ASSERT(error
->unknown_opt_name
);
324 return error
->unknown_opt_name
;
328 const struct argpar_opt_descr
*argpar_error_opt_descr(
329 const struct argpar_error
* const error
, bool * const is_short
)
331 ARGPAR_ASSERT(error
);
332 ARGPAR_ASSERT(error
->opt_descr
);
335 *is_short
= error
->is_short
;
338 return error
->opt_descr
;
342 void argpar_error_destroy(const struct argpar_error
* const error
)
345 free(error
->unknown_opt_name
);
346 free((void *) error
);
351 * Finds and returns the _first_ descriptor having the short option name
352 * `short_name` or the long option name `long_name` within the option
353 * descriptors `descrs`.
355 * `short_name` may be `'\0'` to not consider it.
357 * `long_name` may be `NULL` to not consider it.
359 * Returns `NULL` if no descriptor is found.
362 const struct argpar_opt_descr
*find_descr(
363 const struct argpar_opt_descr
* const descrs
,
364 const char short_name
, const char * const long_name
)
366 const struct argpar_opt_descr
*descr
;
368 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
369 if (short_name
&& descr
->short_name
&&
370 short_name
== descr
->short_name
) {
374 if (long_name
&& descr
->long_name
&&
375 strcmp(long_name
, descr
->long_name
) == 0) {
381 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
384 /* Return type of parse_short_opt_group() and parse_long_opt() */
385 enum parse_orig_arg_opt_ret
{
386 PARSE_ORIG_ARG_OPT_RET_OK
,
387 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -1,
388 PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
= -2,
389 PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
= -4,
390 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -5,
394 * Parses the short option group argument `short_opt_group`, starting
395 * where needed depending on the state of `iter`.
397 * On success, sets `*item`.
399 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
403 enum parse_orig_arg_opt_ret
parse_short_opt_group(
404 const char * const short_opt_group
,
405 const char * const next_orig_arg
,
406 const struct argpar_opt_descr
* const descrs
,
407 struct argpar_iter
* const iter
,
408 struct argpar_error
** const error
,
409 struct argpar_item
** const item
)
411 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
412 bool used_next_orig_arg
= false;
413 const char *opt_arg
= NULL
;
414 const struct argpar_opt_descr
*descr
;
415 struct argpar_item_opt
*opt_item
;
417 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
419 if (!iter
->short_opt_group_ch
) {
420 iter
->short_opt_group_ch
= short_opt_group
;
423 /* Find corresponding option descriptor */
424 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
426 const char unknown_opt_name
[] =
427 {*iter
->short_opt_group_ch
, '\0'};
429 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
431 if (set_error(error
, unknown_opt_name
, NULL
, true)) {
432 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
438 if (descr
->with_arg
) {
439 if (iter
->short_opt_group_ch
[1]) {
441 opt_arg
= &iter
->short_opt_group_ch
[1];
444 opt_arg
= next_orig_arg
;
445 used_next_orig_arg
= true;
449 * We accept `-o ''` (empty option argument), but not
450 * `-o` alone if an option argument is expected.
452 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
453 strlen(opt_arg
) == 0)) {
454 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
456 if (set_error(error
, NULL
, descr
, true)) {
457 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
464 /* Create and append option argument */
465 opt_item
= create_opt_item(descr
, opt_arg
);
467 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
471 *item
= &opt_item
->base
;
472 iter
->short_opt_group_ch
++;
474 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
475 /* Option has an argument: no more options */
476 iter
->short_opt_group_ch
= NULL
;
478 if (used_next_orig_arg
) {
488 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
495 * Parses the long option argument `long_opt_arg`.
497 * On success, sets `*item`.
499 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
503 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
504 const char * const next_orig_arg
,
505 const struct argpar_opt_descr
* const descrs
,
506 struct argpar_iter
* const iter
,
507 struct argpar_error
** const error
,
508 struct argpar_item
** const item
)
510 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
511 const struct argpar_opt_descr
*descr
;
512 struct argpar_item_opt
*opt_item
;
513 bool used_next_orig_arg
= false;
515 /* Option's argument, if any */
516 const char *opt_arg
= NULL
;
518 /* Position of first `=`, if any */
522 const char *long_opt_name
= long_opt_arg
;
524 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
526 /* Find the first `=` in original argument */
527 eq_pos
= strchr(long_opt_arg
, '=');
529 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
531 /* Isolate the option name */
532 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
533 iter
->tmp_buf
.size
*= 2;
534 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
535 char, iter
->tmp_buf
.size
);
536 if (!iter
->tmp_buf
.data
) {
537 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
542 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
543 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
544 long_opt_name
= iter
->tmp_buf
.data
;
547 /* Find corresponding option descriptor */
548 descr
= find_descr(descrs
, '\0', long_opt_name
);
550 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
552 if (set_error(error
, long_opt_name
, NULL
, false)) {
553 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
559 /* Find option's argument if any */
560 if (descr
->with_arg
) {
562 /* `--long-opt=arg` style */
563 opt_arg
= eq_pos
+ 1;
565 /* `--long-opt arg` style */
566 if (!next_orig_arg
) {
567 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
569 if (set_error(error
, NULL
, descr
, false)) {
570 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
576 opt_arg
= next_orig_arg
;
577 used_next_orig_arg
= true;
581 * Unexpected `--opt=arg` style for a long option which
582 * doesn't accept an argument.
584 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
;
586 if (set_error(error
, NULL
, descr
, false)) {
587 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
593 /* Create and append option argument */
594 opt_item
= create_opt_item(descr
, opt_arg
);
599 if (used_next_orig_arg
) {
605 *item
= &opt_item
->base
;
609 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
616 * Parses the original argument `orig_arg`.
618 * On success, sets `*item`.
620 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
624 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
625 const char * const next_orig_arg
,
626 const struct argpar_opt_descr
* const descrs
,
627 struct argpar_iter
* const iter
,
628 struct argpar_error
** const error
,
629 struct argpar_item
** const item
)
631 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
633 ARGPAR_ASSERT(orig_arg
[0] == '-');
635 if (orig_arg
[1] == '-') {
637 ret
= parse_long_opt(&orig_arg
[2],
638 next_orig_arg
, descrs
, iter
, error
, item
);
641 ret
= parse_short_opt_group(&orig_arg
[1],
642 next_orig_arg
, descrs
, iter
, error
, item
);
649 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
650 const char * const * const argv
,
651 const struct argpar_opt_descr
* const descrs
)
653 struct argpar_iter
*iter
= ARGPAR_ZALLOC(struct argpar_iter
);
659 iter
->user
.argc
= argc
;
660 iter
->user
.argv
= argv
;
661 iter
->user
.descrs
= descrs
;
662 iter
->tmp_buf
.size
= 128;
663 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
664 if (!iter
->tmp_buf
.data
) {
665 argpar_iter_destroy(iter
);
675 void argpar_iter_destroy(struct argpar_iter
* const iter
)
678 free(iter
->tmp_buf
.data
);
684 enum argpar_iter_next_status
argpar_iter_next(
685 struct argpar_iter
* const iter
,
686 const struct argpar_item
** const item
,
687 const struct argpar_error
** const error
)
689 enum argpar_iter_next_status status
;
690 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
691 const char *orig_arg
;
692 const char *next_orig_arg
;
693 struct argpar_error
** const nc_error
= (struct argpar_error
**) error
;
695 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
701 if (iter
->i
== iter
->user
.argc
) {
702 status
= ARGPAR_ITER_NEXT_STATUS_END
;
706 orig_arg
= iter
->user
.argv
[iter
->i
];
708 iter
->i
< (iter
->user
.argc
- 1) ?
709 iter
->user
.argv
[iter
->i
+ 1] : NULL
;
711 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
712 orig_arg
[0] != '-') {
713 /* Non-option argument */
714 const struct argpar_item_non_opt
* const non_opt_item
=
715 create_non_opt_item(orig_arg
, iter
->i
,
716 iter
->non_opt_index
);
719 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
723 iter
->non_opt_index
++;
725 *item
= &non_opt_item
->base
;
726 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
730 /* Option argument */
731 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
732 next_orig_arg
, iter
->user
.descrs
, iter
, nc_error
,
733 (struct argpar_item
**) item
);
734 switch (parse_orig_arg_opt_ret
) {
735 case PARSE_ORIG_ARG_OPT_RET_OK
:
736 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
738 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
739 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
740 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
742 ARGPAR_ASSERT(*error
);
743 (*nc_error
)->orig_index
= iter
->i
;
746 switch (parse_orig_arg_opt_ret
) {
747 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
748 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
750 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
751 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
;
753 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
754 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
;
761 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
762 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
773 unsigned int argpar_iter_ingested_orig_args(
774 const struct argpar_iter
* const iter
)