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) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
18 #define ARGPAR_CALLOC(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
20 #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
24 * Force usage of the assertion condition to prevent unused variable warnings
25 * when `assert()` are disabled by the `NDEBUG` definition.
27 #define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
30 #define ARGPAR_ASSERT(_cond) assert(_cond)
36 * Such a structure contains the state of an iterator between calls to
41 * Data provided by the user to argpar_iter_create(); immutable
46 const char *const *argv
;
47 const struct argpar_opt_descr
*descrs
;
51 * Index of the argument to process in the next
52 * argpar_iter_next() call.
56 /* Counter of non-option arguments */
60 * Current character within the current short option group: if
61 * it's not `NULL`, the parser is within a short option group,
62 * therefore it must resume there in the next argpar_iter_next()
65 const char *short_opt_group_ch
;
67 /* Temporary character buffer which only grows */
74 /* Base parsing item */
76 enum argpar_item_type type
;
79 /* Option parsing item */
80 struct argpar_item_opt
{
81 struct argpar_item base
;
83 /* Corresponding descriptor */
84 const struct argpar_opt_descr
*descr
;
86 /* Argument, or `NULL` if none; owned by this */
90 /* Non-option parsing item */
91 struct argpar_item_non_opt
{
92 struct argpar_item base
;
95 * Complete argument, pointing to one of the entries of the
96 * original arguments (`argv`).
101 * Index of this argument amongst all original arguments
104 unsigned int orig_index
;
106 /* Index of this argument amongst other non-option arguments */
107 unsigned int non_opt_index
;
111 struct argpar_error
{
113 enum argpar_error_type type
;
115 /* Original argument index */
116 unsigned int orig_index
;
118 /* Name of unknown option; owned by this */
119 char *unknown_opt_name
;
121 /* Option descriptor */
122 const struct argpar_opt_descr
*opt_descr
;
124 /* `true` if a short option caused the error */
129 enum argpar_item_type
argpar_item_type(const struct argpar_item
*const item
)
136 const struct argpar_opt_descr
*argpar_item_opt_descr(const struct argpar_item
*const item
)
139 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
140 return ((const struct argpar_item_opt
*) item
)->descr
;
144 const char *argpar_item_opt_arg(const struct argpar_item
*const item
)
147 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
148 return ((const struct argpar_item_opt
*) item
)->arg
;
152 const char *argpar_item_non_opt_arg(const struct argpar_item
*const item
)
155 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
156 return ((const struct argpar_item_non_opt
*) item
)->arg
;
160 unsigned int argpar_item_non_opt_orig_index(const struct argpar_item
*const item
)
163 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
164 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
168 unsigned int argpar_item_non_opt_non_opt_index(const struct argpar_item
*const item
)
171 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
172 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
176 void argpar_item_destroy(const struct argpar_item
*const item
)
182 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
183 struct argpar_item_opt
*const opt_item
= (struct argpar_item_opt
*) item
;
195 * Creates and returns an option parsing item for the descriptor `descr`
196 * and having the argument `arg` (copied; may be `NULL`).
198 * Returns `NULL` on memory error.
200 static struct argpar_item_opt
*create_opt_item(const struct argpar_opt_descr
*const descr
,
201 const char *const arg
)
203 struct argpar_item_opt
*opt_item
= ARGPAR_ZALLOC(struct argpar_item_opt
);
209 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
210 opt_item
->descr
= descr
;
213 opt_item
->arg
= strdup(arg
);
214 if (!opt_item
->arg
) {
222 argpar_item_destroy(&opt_item
->base
);
230 * Creates and returns a non-option parsing item for the original
231 * argument `arg` having the original index `orig_index` and the
232 * non-option index `non_opt_index`.
234 * Returns `NULL` on memory error.
236 static struct argpar_item_non_opt
*create_non_opt_item(const char *const arg
,
237 const unsigned int orig_index
,
238 const unsigned int non_opt_index
)
240 struct argpar_item_non_opt
*const non_opt_item
= 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
266 static int set_error(struct argpar_error
**const error
,
267 enum argpar_error_type type
,
268 const char *const unknown_opt_name
,
269 const struct argpar_opt_descr
*const opt_descr
,
278 *error
= ARGPAR_ZALLOC(struct argpar_error
);
283 (*error
)->type
= type
;
285 if (unknown_opt_name
) {
286 (*error
)->unknown_opt_name
=
287 ARGPAR_CALLOC(char, strlen(unknown_opt_name
) + 1 + (is_short
? 1 : 2));
288 if (!(*error
)->unknown_opt_name
) {
293 strcpy((*error
)->unknown_opt_name
, "-");
295 strcpy((*error
)->unknown_opt_name
, "--");
298 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
301 (*error
)->opt_descr
= opt_descr
;
302 (*error
)->is_short
= is_short
;
306 argpar_error_destroy(*error
);
314 enum argpar_error_type
argpar_error_type(const struct argpar_error
*const error
)
316 ARGPAR_ASSERT(error
);
321 unsigned int argpar_error_orig_index(const struct argpar_error
*const error
)
323 ARGPAR_ASSERT(error
);
324 return error
->orig_index
;
328 const char *argpar_error_unknown_opt_name(const struct argpar_error
*const error
)
330 ARGPAR_ASSERT(error
);
331 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_UNKNOWN_OPT
);
332 ARGPAR_ASSERT(error
->unknown_opt_name
);
333 return error
->unknown_opt_name
;
337 const struct argpar_opt_descr
*argpar_error_opt_descr(const struct argpar_error
*const error
,
338 bool *const is_short
)
340 ARGPAR_ASSERT(error
);
341 ARGPAR_ASSERT(error
->type
== ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
||
342 error
->type
== ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
);
343 ARGPAR_ASSERT(error
->opt_descr
);
346 *is_short
= error
->is_short
;
349 return error
->opt_descr
;
353 void argpar_error_destroy(const struct argpar_error
*const error
)
356 free(error
->unknown_opt_name
);
357 free((void *) error
);
362 * Finds and returns the _first_ descriptor having the short option name
363 * `short_name` or the long option name `long_name` within the option
364 * descriptors `descrs`.
366 * `short_name` may be `'\0'` to not consider it.
368 * `long_name` may be `NULL` to not consider it.
370 * Returns `NULL` if no descriptor is found.
372 static const struct argpar_opt_descr
*find_descr(const struct argpar_opt_descr
*const descrs
,
373 const char short_name
,
374 const char *const long_name
)
376 const struct argpar_opt_descr
*descr
;
378 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
379 if (short_name
&& descr
->short_name
&& short_name
== descr
->short_name
) {
383 if (long_name
&& descr
->long_name
&& strcmp(long_name
, descr
->long_name
) == 0) {
389 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
392 /* Return type of parse_short_opt_group() and parse_long_opt() */
393 enum parse_orig_arg_opt_ret
{
394 PARSE_ORIG_ARG_OPT_RET_OK
,
395 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
396 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -2,
400 * Parses the short option group argument `short_opt_group`, starting
401 * where needed depending on the state of `iter`.
403 * On success, sets `*item`.
405 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
408 static enum parse_orig_arg_opt_ret
409 parse_short_opt_group(const char *const short_opt_group
,
410 const char *const next_orig_arg
,
411 const struct argpar_opt_descr
*const descrs
,
412 struct argpar_iter
*const iter
,
413 struct argpar_error
**const error
,
414 struct argpar_item
**const item
)
416 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
417 bool used_next_orig_arg
= false;
418 const char *opt_arg
= NULL
;
419 const struct argpar_opt_descr
*descr
;
420 struct argpar_item_opt
*opt_item
;
422 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
424 if (!iter
->short_opt_group_ch
) {
425 iter
->short_opt_group_ch
= short_opt_group
;
428 /* Find corresponding option descriptor */
429 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
431 const char unknown_opt_name
[] = { *iter
->short_opt_group_ch
, '\0' };
433 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
435 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, unknown_opt_name
, NULL
, true)) {
436 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
442 if (descr
->with_arg
) {
443 if (iter
->short_opt_group_ch
[1]) {
445 opt_arg
= &iter
->short_opt_group_ch
[1];
448 opt_arg
= next_orig_arg
;
449 used_next_orig_arg
= true;
453 * We accept `-o ''` (empty option argument), but not
454 * `-o` alone if an option argument is expected.
456 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] && strlen(opt_arg
) == 0)) {
457 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
459 if (set_error(error
, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
, NULL
, descr
, true)) {
460 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
467 /* Create and append option argument */
468 opt_item
= create_opt_item(descr
, opt_arg
);
470 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
474 *item
= &opt_item
->base
;
475 iter
->short_opt_group_ch
++;
477 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
478 /* Option has an argument: no more options */
479 iter
->short_opt_group_ch
= NULL
;
481 if (used_next_orig_arg
) {
491 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
498 * Parses the long option argument `long_opt_arg`.
500 * On success, sets `*item`.
502 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
505 static enum parse_orig_arg_opt_ret
parse_long_opt(const char *const long_opt_arg
,
506 const char *const next_orig_arg
,
507 const struct argpar_opt_descr
*const descrs
,
508 struct argpar_iter
*const iter
,
509 struct argpar_error
**const error
,
510 struct argpar_item
**const item
)
512 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
513 const struct argpar_opt_descr
*descr
;
514 struct argpar_item_opt
*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 iter
->tmp_buf
.size
*= 2;
537 ARGPAR_REALLOC(iter
->tmp_buf
.data
, char, iter
->tmp_buf
.size
);
538 if (!iter
->tmp_buf
.data
) {
539 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
544 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
545 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
546 long_opt_name
= iter
->tmp_buf
.data
;
549 /* Find corresponding option descriptor */
550 descr
= find_descr(descrs
, '\0', long_opt_name
);
552 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
554 if (set_error(error
, ARGPAR_ERROR_TYPE_UNKNOWN_OPT
, long_opt_name
, NULL
, false)) {
555 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
561 /* Find option's argument if any */
562 if (descr
->with_arg
) {
564 /* `--long-opt=arg` style */
565 opt_arg
= eq_pos
+ 1;
567 /* `--long-opt arg` style */
568 if (!next_orig_arg
) {
569 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
572 ARGPAR_ERROR_TYPE_MISSING_OPT_ARG
,
576 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
582 opt_arg
= next_orig_arg
;
583 used_next_orig_arg
= true;
587 * Unexpected `--opt=arg` style for a long option which
588 * doesn't accept an argument.
590 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
592 if (set_error(error
, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG
, NULL
, descr
, false)) {
593 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
599 /* Create and append option argument */
600 opt_item
= create_opt_item(descr
, opt_arg
);
605 if (used_next_orig_arg
) {
611 *item
= &opt_item
->base
;
615 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
622 * Parses the original argument `orig_arg`.
624 * On success, sets `*item`.
626 * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets
629 static enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char *const orig_arg
,
630 const char *const next_orig_arg
,
631 const struct argpar_opt_descr
*const descrs
,
632 struct argpar_iter
*const iter
,
633 struct argpar_error
**const error
,
634 struct argpar_item
**const item
)
636 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
638 ARGPAR_ASSERT(orig_arg
[0] == '-');
640 if (orig_arg
[1] == '-') {
642 ret
= parse_long_opt(&orig_arg
[2], next_orig_arg
, descrs
, iter
, error
, item
);
645 ret
= parse_short_opt_group(&orig_arg
[1], next_orig_arg
, descrs
, iter
, error
, item
);
652 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
653 const char *const *const argv
,
654 const struct argpar_opt_descr
*const descrs
)
656 struct argpar_iter
*iter
= ARGPAR_ZALLOC(struct argpar_iter
);
662 iter
->user
.argc
= argc
;
663 iter
->user
.argv
= argv
;
664 iter
->user
.descrs
= descrs
;
665 iter
->tmp_buf
.size
= 128;
666 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
667 if (!iter
->tmp_buf
.data
) {
668 argpar_iter_destroy(iter
);
678 void argpar_iter_destroy(struct argpar_iter
*const iter
)
681 free(iter
->tmp_buf
.data
);
687 enum argpar_iter_next_status
argpar_iter_next(struct argpar_iter
*const iter
,
688 const struct argpar_item
**const item
,
689 const struct argpar_error
**const error
)
691 enum argpar_iter_next_status status
;
692 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
693 const char *orig_arg
;
694 const char *next_orig_arg
;
695 struct argpar_error
**const nc_error
= (struct argpar_error
**) error
;
697 ARGPAR_ASSERT(iter
->i
<= iter
->user
.argc
);
703 if (iter
->i
== iter
->user
.argc
) {
704 status
= ARGPAR_ITER_NEXT_STATUS_END
;
708 orig_arg
= iter
->user
.argv
[iter
->i
];
709 next_orig_arg
= iter
->i
< (iter
->user
.argc
- 1) ? iter
->user
.argv
[iter
->i
+ 1] : NULL
;
711 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 || orig_arg
[0] != '-') {
712 /* Non-option argument */
713 const struct argpar_item_non_opt
*const non_opt_item
=
714 create_non_opt_item(orig_arg
, iter
->i
, iter
->non_opt_index
);
717 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
721 iter
->non_opt_index
++;
723 *item
= &non_opt_item
->base
;
724 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
728 /* Option argument */
729 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
734 (struct argpar_item
**) item
);
735 switch (parse_orig_arg_opt_ret
) {
736 case PARSE_ORIG_ARG_OPT_RET_OK
:
737 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
739 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
741 ARGPAR_ASSERT(*error
);
742 (*nc_error
)->orig_index
= iter
->i
;
744 status
= ARGPAR_ITER_NEXT_STATUS_ERROR
;
746 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
747 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
758 unsigned int argpar_iter_ingested_orig_args(const struct argpar_iter
*const iter
)