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)
27 #ifdef __MINGW_PRINTF_FORMAT
28 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
30 # define ARGPAR_PRINTF_FORMAT printf
36 * Such a structure contains the state of an iterator between calls to
41 * Data provided by the user to argpar_iter_create(); immutable
45 const char * const *argv
;
46 const struct argpar_opt_descr
*descrs
;
49 * Index of the argument to process in the next
50 * argpar_iter_next() call.
54 /* Counter of non-option arguments */
58 * Current character within the current short option group: if
59 * it's not `NULL`, the parser is within a short option group,
60 * therefore it must resume there in the next argpar_iter_next()
63 const char *short_opt_group_ch
;
65 /* Temporary character buffer which only grows */
72 /* Base parsing item */
74 enum argpar_item_type type
;
77 /* Option parsing item */
78 struct argpar_item_opt
{
79 struct argpar_item base
;
81 /* Corresponding descriptor */
82 const struct argpar_opt_descr
*descr
;
84 /* Argument, or `NULL` if none; owned by this */
88 /* Non-option parsing item */
89 struct argpar_item_non_opt
{
90 struct argpar_item base
;
93 * Complete argument, pointing to one of the entries of the
94 * original arguments (`argv`).
99 * Index of this argument amongst all original arguments
102 unsigned int orig_index
;
104 /* Index of this argument amongst other non-option arguments */
105 unsigned int non_opt_index
;
108 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
109 char *argpar_vasprintf(const char * const fmt
, va_list args
)
115 va_copy(args2
, args
);
116 len1
= vsnprintf(NULL
, 0, fmt
, args
);
122 str
= malloc(len1
+ 1);
127 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
128 ARGPAR_ASSERT(len1
== len2
);
136 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
137 char *argpar_asprintf(const char * const fmt
, ...)
143 str
= argpar_vasprintf(fmt
, args
);
148 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
149 bool try_append_string_printf(char ** const str
, const char *fmt
, ...)
151 char *new_str
= NULL
;
152 char *addendum
= NULL
;
163 addendum
= argpar_vasprintf(fmt
, args
);
171 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
187 enum argpar_item_type
argpar_item_type(const struct argpar_item
* const item
)
194 const struct argpar_opt_descr
*argpar_item_opt_descr(
195 const struct argpar_item
* const item
)
198 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
199 return ((const struct argpar_item_opt
*) item
)->descr
;
203 const char *argpar_item_opt_arg(const struct argpar_item
* const item
)
206 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
207 return ((const struct argpar_item_opt
*) item
)->arg
;
211 const char *argpar_item_non_opt_arg(const struct argpar_item
* const item
)
214 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
215 return ((const struct argpar_item_non_opt
*) item
)->arg
;
219 unsigned int argpar_item_non_opt_orig_index(
220 const struct argpar_item
* const item
)
223 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
224 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
228 unsigned int argpar_item_non_opt_non_opt_index(
229 const struct argpar_item
* const item
)
232 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
233 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
237 void argpar_item_destroy(const struct argpar_item
* const item
)
243 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
244 struct argpar_item_opt
* const opt_item
=
245 (struct argpar_item_opt
*) item
;
257 struct argpar_item_opt
*create_opt_item(
258 const struct argpar_opt_descr
* const descr
,
259 const char * const arg
)
261 struct argpar_item_opt
*opt_item
=
262 ARGPAR_ZALLOC(struct argpar_item_opt
);
268 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
269 opt_item
->descr
= descr
;
272 opt_item
->arg
= strdup(arg
);
273 if (!opt_item
->arg
) {
281 argpar_item_destroy(&opt_item
->base
);
289 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
290 const unsigned int orig_index
,
291 const unsigned int non_opt_index
)
293 struct argpar_item_non_opt
* const non_opt_item
=
294 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
300 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
301 non_opt_item
->arg
= arg
;
302 non_opt_item
->orig_index
= orig_index
;
303 non_opt_item
->non_opt_index
= non_opt_index
;
310 const struct argpar_opt_descr
*find_descr(
311 const struct argpar_opt_descr
* const descrs
,
312 const char short_name
, const char * const long_name
)
314 const struct argpar_opt_descr
*descr
;
316 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
317 if (short_name
&& descr
->short_name
&&
318 short_name
== descr
->short_name
) {
322 if (long_name
&& descr
->long_name
&&
323 strcmp(long_name
, descr
->long_name
) == 0) {
329 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
332 enum parse_orig_arg_opt_ret
{
333 PARSE_ORIG_ARG_OPT_RET_OK
,
334 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -1,
335 PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
= -2,
336 PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
= -4,
337 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -5,
341 enum parse_orig_arg_opt_ret
parse_short_opt_group(
342 const char * const short_opt_group
,
343 const char * const next_orig_arg
,
344 const struct argpar_opt_descr
* const descrs
,
345 struct argpar_iter
* const iter
,
346 char ** const error
, struct argpar_item
** const item
)
348 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
349 bool used_next_orig_arg
= false;
350 const char *opt_arg
= NULL
;
351 const struct argpar_opt_descr
*descr
;
352 struct argpar_item_opt
*opt_item
;
354 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
356 if (!iter
->short_opt_group_ch
) {
357 iter
->short_opt_group_ch
= short_opt_group
;
360 /* Find corresponding option descriptor */
361 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
363 try_append_string_printf(error
, "Unknown option `-%c`",
364 *iter
->short_opt_group_ch
);
365 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
369 if (descr
->with_arg
) {
370 if (iter
->short_opt_group_ch
[1]) {
372 opt_arg
= &iter
->short_opt_group_ch
[1];
375 opt_arg
= next_orig_arg
;
376 used_next_orig_arg
= true;
380 * We accept `-o ''` (empty option argument), but not
381 * `-o` alone if an option argument is expected.
383 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
384 strlen(opt_arg
) == 0)) {
385 try_append_string_printf(error
,
386 "Missing required argument for option `-%c`",
387 *iter
->short_opt_group_ch
);
388 used_next_orig_arg
= false;
389 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
394 /* Create and append option argument */
395 opt_item
= create_opt_item(descr
, opt_arg
);
397 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
401 *item
= &opt_item
->base
;
402 iter
->short_opt_group_ch
++;
404 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
405 /* Option has an argument: no more options */
406 iter
->short_opt_group_ch
= NULL
;
408 if (used_next_orig_arg
) {
418 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
425 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
426 const char * const next_orig_arg
,
427 const struct argpar_opt_descr
* const descrs
,
428 struct argpar_iter
* const iter
,
429 char ** const error
, struct argpar_item
** const item
)
431 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
432 const struct argpar_opt_descr
*descr
;
433 struct argpar_item_opt
*opt_item
;
434 bool used_next_orig_arg
= false;
436 /* Option's argument, if any */
437 const char *opt_arg
= NULL
;
439 /* Position of first `=`, if any */
443 const char *long_opt_name
= long_opt_arg
;
445 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
447 /* Find the first `=` in original argument */
448 eq_pos
= strchr(long_opt_arg
, '=');
450 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
452 /* Isolate the option name */
453 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
454 iter
->tmp_buf
.size
*= 2;
455 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
456 char, iter
->tmp_buf
.size
);
457 if (!iter
->tmp_buf
.data
) {
458 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
463 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
464 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
465 long_opt_name
= iter
->tmp_buf
.data
;
468 /* Find corresponding option descriptor */
469 descr
= find_descr(descrs
, '\0', long_opt_name
);
471 try_append_string_printf(error
, "Unknown option `--%s`",
473 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
477 /* Find option's argument if any */
478 if (descr
->with_arg
) {
480 /* `--long-opt=arg` style */
481 opt_arg
= eq_pos
+ 1;
483 /* `--long-opt arg` style */
484 if (!next_orig_arg
) {
485 try_append_string_printf(error
,
486 "Missing required argument for option `--%s`",
488 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
492 opt_arg
= next_orig_arg
;
493 used_next_orig_arg
= true;
497 * Unexpected `--opt=arg` style for a long option which
498 * doesn't accept an argument.
500 try_append_string_printf(error
,
501 "Unexpected argument for option `--%s`", long_opt_name
);
502 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
;
506 /* Create and append option argument */
507 opt_item
= create_opt_item(descr
, opt_arg
);
512 if (used_next_orig_arg
) {
518 *item
= &opt_item
->base
;
522 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
529 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
530 const char * const next_orig_arg
,
531 const struct argpar_opt_descr
* const descrs
,
532 struct argpar_iter
* const iter
, char ** const error
,
533 struct argpar_item
** const item
)
535 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
537 ARGPAR_ASSERT(orig_arg
[0] == '-');
539 if (orig_arg
[1] == '-') {
541 ret
= parse_long_opt(&orig_arg
[2],
542 next_orig_arg
, descrs
, iter
, error
, item
);
545 ret
= parse_short_opt_group(&orig_arg
[1],
546 next_orig_arg
, descrs
, iter
, error
, item
);
553 bool try_prepend_while_parsing_arg_to_error(char ** const error
,
554 const unsigned int i
, const char * const arg
)
564 ARGPAR_ASSERT(*error
);
565 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
581 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
582 const char * const * const argv
,
583 const struct argpar_opt_descr
* const descrs
)
585 struct argpar_iter
*iter
= ARGPAR_ZALLOC(struct argpar_iter
);
593 iter
->descrs
= descrs
;
594 iter
->tmp_buf
.size
= 128;
595 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
596 if (!iter
->tmp_buf
.data
) {
597 argpar_iter_destroy(iter
);
607 void argpar_iter_destroy(struct argpar_iter
* const iter
)
610 free(iter
->tmp_buf
.data
);
616 enum argpar_iter_next_status
argpar_iter_next(
617 struct argpar_iter
* const iter
,
618 const struct argpar_item
** const item
, char ** const error
)
620 enum argpar_iter_next_status status
;
621 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
622 const char *orig_arg
;
623 const char *next_orig_arg
;
625 ARGPAR_ASSERT(iter
->i
<= iter
->argc
);
631 if (iter
->i
== iter
->argc
) {
632 status
= ARGPAR_ITER_NEXT_STATUS_END
;
636 orig_arg
= iter
->argv
[iter
->i
];
638 iter
->i
< (iter
->argc
- 1) ? iter
->argv
[iter
->i
+ 1] : NULL
;
640 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
641 orig_arg
[0] != '-') {
642 /* Non-option argument */
643 const struct argpar_item_non_opt
* const non_opt_item
=
644 create_non_opt_item(orig_arg
, iter
->i
,
645 iter
->non_opt_index
);
648 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
652 iter
->non_opt_index
++;
654 *item
= &non_opt_item
->base
;
655 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
659 /* Option argument */
660 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
661 next_orig_arg
, iter
->descrs
, iter
, error
,
662 (struct argpar_item
**) item
);
663 switch (parse_orig_arg_opt_ret
) {
664 case PARSE_ORIG_ARG_OPT_RET_OK
:
665 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
667 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
668 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
669 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
670 try_prepend_while_parsing_arg_to_error(error
, iter
->i
,
673 switch (parse_orig_arg_opt_ret
) {
674 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
675 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
677 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
678 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
;
680 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
681 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
;
688 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
689 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
700 unsigned int argpar_iter_ingested_orig_args(
701 const struct argpar_iter
* const iter
)