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
;
109 struct argpar_error
{
110 /* Original argument index */
111 unsigned int orig_index
;
113 /* Name of unknown option; owned by this */
114 char *unknown_opt_name
;
116 /* Option descriptor */
117 const struct argpar_opt_descr
*opt_descr
;
119 /* `true` if a short option caused the error */
124 enum argpar_item_type
argpar_item_type(const struct argpar_item
* const item
)
131 const struct argpar_opt_descr
*argpar_item_opt_descr(
132 const struct argpar_item
* const item
)
135 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
136 return ((const struct argpar_item_opt
*) item
)->descr
;
140 const char *argpar_item_opt_arg(const struct argpar_item
* const item
)
143 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_OPT
);
144 return ((const struct argpar_item_opt
*) item
)->arg
;
148 const char *argpar_item_non_opt_arg(const struct argpar_item
* const item
)
151 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
152 return ((const struct argpar_item_non_opt
*) item
)->arg
;
156 unsigned int argpar_item_non_opt_orig_index(
157 const struct argpar_item
* const item
)
160 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
161 return ((const struct argpar_item_non_opt
*) item
)->orig_index
;
165 unsigned int argpar_item_non_opt_non_opt_index(
166 const struct argpar_item
* const item
)
169 ARGPAR_ASSERT(item
->type
== ARGPAR_ITEM_TYPE_NON_OPT
);
170 return ((const struct argpar_item_non_opt
*) item
)->non_opt_index
;
174 void argpar_item_destroy(const struct argpar_item
* const item
)
180 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
181 struct argpar_item_opt
* const opt_item
=
182 (struct argpar_item_opt
*) item
;
194 struct argpar_item_opt
*create_opt_item(
195 const struct argpar_opt_descr
* const descr
,
196 const char * const arg
)
198 struct argpar_item_opt
*opt_item
=
199 ARGPAR_ZALLOC(struct argpar_item_opt
);
205 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
206 opt_item
->descr
= descr
;
209 opt_item
->arg
= strdup(arg
);
210 if (!opt_item
->arg
) {
218 argpar_item_destroy(&opt_item
->base
);
226 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
227 const unsigned int orig_index
,
228 const unsigned int non_opt_index
)
230 struct argpar_item_non_opt
* const non_opt_item
=
231 ARGPAR_ZALLOC(struct argpar_item_non_opt
);
237 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
238 non_opt_item
->arg
= arg
;
239 non_opt_item
->orig_index
= orig_index
;
240 non_opt_item
->non_opt_index
= non_opt_index
;
247 * If `error` is not `NULL`, sets the error `error` to a new parsing
248 * error object, setting its `unknown_opt_name`, `opt_descr`, and
249 * `is_short` members from the parameters.
251 * `unknown_opt_name` is the unknown option name without any `-` or `--`
252 * prefix: `is_short` controls which type of unknown option it is.
254 * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
258 int set_error(struct argpar_error
** const error
,
259 const char * const unknown_opt_name
,
260 const struct argpar_opt_descr
* const opt_descr
,
269 *error
= ARGPAR_ZALLOC(struct argpar_error
);
274 if (unknown_opt_name
) {
275 (*error
)->unknown_opt_name
= ARGPAR_CALLOC(char,
276 strlen(unknown_opt_name
) + 1 + is_short
? 1 : 2);
277 if (!(*error
)->unknown_opt_name
) {
282 strcpy((*error
)->unknown_opt_name
, "-");
284 strcpy((*error
)->unknown_opt_name
, "--");
287 strcat((*error
)->unknown_opt_name
, unknown_opt_name
);
290 (*error
)->opt_descr
= opt_descr
;
291 (*error
)->is_short
= is_short
;
295 argpar_error_destroy(*error
);
303 unsigned int argpar_error_orig_index(const struct argpar_error
* const error
)
305 ARGPAR_ASSERT(error
);
306 return error
->orig_index
;
310 const char *argpar_error_unknown_opt_name(
311 const struct argpar_error
* const error
)
313 ARGPAR_ASSERT(error
);
314 ARGPAR_ASSERT(error
->unknown_opt_name
);
315 return error
->unknown_opt_name
;
319 const struct argpar_opt_descr
*argpar_error_opt_descr(
320 const struct argpar_error
* const error
, bool * const is_short
)
322 ARGPAR_ASSERT(error
);
323 ARGPAR_ASSERT(error
->opt_descr
);
326 *is_short
= error
->is_short
;
329 return error
->opt_descr
;
333 void argpar_error_destroy(const struct argpar_error
* const error
)
336 free(error
->unknown_opt_name
);
337 free((void *) error
);
342 const struct argpar_opt_descr
*find_descr(
343 const struct argpar_opt_descr
* const descrs
,
344 const char short_name
, const char * const long_name
)
346 const struct argpar_opt_descr
*descr
;
348 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
349 if (short_name
&& descr
->short_name
&&
350 short_name
== descr
->short_name
) {
354 if (long_name
&& descr
->long_name
&&
355 strcmp(long_name
, descr
->long_name
) == 0) {
361 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
364 enum parse_orig_arg_opt_ret
{
365 PARSE_ORIG_ARG_OPT_RET_OK
,
366 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -1,
367 PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
= -2,
368 PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
= -4,
369 PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
= -5,
373 enum parse_orig_arg_opt_ret
parse_short_opt_group(
374 const char * const short_opt_group
,
375 const char * const next_orig_arg
,
376 const struct argpar_opt_descr
* const descrs
,
377 struct argpar_iter
* const iter
,
378 struct argpar_error
** const error
,
379 struct argpar_item
** const item
)
381 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
382 bool used_next_orig_arg
= false;
383 const char *opt_arg
= NULL
;
384 const struct argpar_opt_descr
*descr
;
385 struct argpar_item_opt
*opt_item
;
387 ARGPAR_ASSERT(strlen(short_opt_group
) != 0);
389 if (!iter
->short_opt_group_ch
) {
390 iter
->short_opt_group_ch
= short_opt_group
;
393 /* Find corresponding option descriptor */
394 descr
= find_descr(descrs
, *iter
->short_opt_group_ch
, NULL
);
396 const char unknown_opt_name
[] =
397 {*iter
->short_opt_group_ch
, '\0'};
399 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
401 if (set_error(error
, unknown_opt_name
, NULL
, true)) {
402 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
408 if (descr
->with_arg
) {
409 if (iter
->short_opt_group_ch
[1]) {
411 opt_arg
= &iter
->short_opt_group_ch
[1];
414 opt_arg
= next_orig_arg
;
415 used_next_orig_arg
= true;
419 * We accept `-o ''` (empty option argument), but not
420 * `-o` alone if an option argument is expected.
422 if (!opt_arg
|| (iter
->short_opt_group_ch
[1] &&
423 strlen(opt_arg
) == 0)) {
424 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
426 if (set_error(error
, NULL
, descr
, true)) {
427 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
434 /* Create and append option argument */
435 opt_item
= create_opt_item(descr
, opt_arg
);
437 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
441 *item
= &opt_item
->base
;
442 iter
->short_opt_group_ch
++;
444 if (descr
->with_arg
|| !*iter
->short_opt_group_ch
) {
445 /* Option has an argument: no more options */
446 iter
->short_opt_group_ch
= NULL
;
448 if (used_next_orig_arg
) {
458 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
465 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
466 const char * const next_orig_arg
,
467 const struct argpar_opt_descr
* const descrs
,
468 struct argpar_iter
* const iter
,
469 struct argpar_error
** const error
,
470 struct argpar_item
** const item
)
472 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
473 const struct argpar_opt_descr
*descr
;
474 struct argpar_item_opt
*opt_item
;
475 bool used_next_orig_arg
= false;
477 /* Option's argument, if any */
478 const char *opt_arg
= NULL
;
480 /* Position of first `=`, if any */
484 const char *long_opt_name
= long_opt_arg
;
486 ARGPAR_ASSERT(strlen(long_opt_arg
) != 0);
488 /* Find the first `=` in original argument */
489 eq_pos
= strchr(long_opt_arg
, '=');
491 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
493 /* Isolate the option name */
494 while (long_opt_name_size
> iter
->tmp_buf
.size
- 1) {
495 iter
->tmp_buf
.size
*= 2;
496 iter
->tmp_buf
.data
= ARGPAR_REALLOC(iter
->tmp_buf
.data
,
497 char, iter
->tmp_buf
.size
);
498 if (!iter
->tmp_buf
.data
) {
499 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
504 memcpy(iter
->tmp_buf
.data
, long_opt_arg
, long_opt_name_size
);
505 iter
->tmp_buf
.data
[long_opt_name_size
] = '\0';
506 long_opt_name
= iter
->tmp_buf
.data
;
509 /* Find corresponding option descriptor */
510 descr
= find_descr(descrs
, '\0', long_opt_name
);
512 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
514 if (set_error(error
, long_opt_name
, NULL
, false)) {
515 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
521 /* Find option's argument if any */
522 if (descr
->with_arg
) {
524 /* `--long-opt=arg` style */
525 opt_arg
= eq_pos
+ 1;
527 /* `--long-opt arg` style */
528 if (!next_orig_arg
) {
529 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
;
531 if (set_error(error
, NULL
, descr
, false)) {
532 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
538 opt_arg
= next_orig_arg
;
539 used_next_orig_arg
= true;
543 * Unexpected `--opt=arg` style for a long option which
544 * doesn't accept an argument.
546 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
;
548 if (set_error(error
, NULL
, descr
, false)) {
549 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
;
555 /* Create and append option argument */
556 opt_item
= create_opt_item(descr
, opt_arg
);
561 if (used_next_orig_arg
) {
567 *item
= &opt_item
->base
;
571 ARGPAR_ASSERT(ret
!= PARSE_ORIG_ARG_OPT_RET_OK
);
578 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
579 const char * const next_orig_arg
,
580 const struct argpar_opt_descr
* const descrs
,
581 struct argpar_iter
* const iter
,
582 struct argpar_error
** const error
,
583 struct argpar_item
** const item
)
585 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
587 ARGPAR_ASSERT(orig_arg
[0] == '-');
589 if (orig_arg
[1] == '-') {
591 ret
= parse_long_opt(&orig_arg
[2],
592 next_orig_arg
, descrs
, iter
, error
, item
);
595 ret
= parse_short_opt_group(&orig_arg
[1],
596 next_orig_arg
, descrs
, iter
, error
, item
);
603 struct argpar_iter
*argpar_iter_create(const unsigned int argc
,
604 const char * const * const argv
,
605 const struct argpar_opt_descr
* const descrs
)
607 struct argpar_iter
*iter
= ARGPAR_ZALLOC(struct argpar_iter
);
615 iter
->descrs
= descrs
;
616 iter
->tmp_buf
.size
= 128;
617 iter
->tmp_buf
.data
= ARGPAR_CALLOC(char, iter
->tmp_buf
.size
);
618 if (!iter
->tmp_buf
.data
) {
619 argpar_iter_destroy(iter
);
629 void argpar_iter_destroy(struct argpar_iter
* const iter
)
632 free(iter
->tmp_buf
.data
);
638 enum argpar_iter_next_status
argpar_iter_next(
639 struct argpar_iter
* const iter
,
640 const struct argpar_item
** const item
,
641 const struct argpar_error
** const error
)
643 enum argpar_iter_next_status status
;
644 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
645 const char *orig_arg
;
646 const char *next_orig_arg
;
647 struct argpar_error
** const nc_error
= (struct argpar_error
**) error
;
649 ARGPAR_ASSERT(iter
->i
<= iter
->argc
);
655 if (iter
->i
== iter
->argc
) {
656 status
= ARGPAR_ITER_NEXT_STATUS_END
;
660 orig_arg
= iter
->argv
[iter
->i
];
662 iter
->i
< (iter
->argc
- 1) ? iter
->argv
[iter
->i
+ 1] : NULL
;
664 if (strcmp(orig_arg
, "-") == 0 || strcmp(orig_arg
, "--") == 0 ||
665 orig_arg
[0] != '-') {
666 /* Non-option argument */
667 const struct argpar_item_non_opt
* const non_opt_item
=
668 create_non_opt_item(orig_arg
, iter
->i
,
669 iter
->non_opt_index
);
672 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
676 iter
->non_opt_index
++;
678 *item
= &non_opt_item
->base
;
679 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
683 /* Option argument */
684 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
685 next_orig_arg
, iter
->descrs
, iter
, nc_error
,
686 (struct argpar_item
**) item
);
687 switch (parse_orig_arg_opt_ret
) {
688 case PARSE_ORIG_ARG_OPT_RET_OK
:
689 status
= ARGPAR_ITER_NEXT_STATUS_OK
;
691 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
692 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
693 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
695 ARGPAR_ASSERT(*error
);
696 (*nc_error
)->orig_index
= iter
->i
;
699 switch (parse_orig_arg_opt_ret
) {
700 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
701 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
703 case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG
:
704 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
;
706 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG
:
707 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
;
714 case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY
:
715 status
= ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY
;
726 unsigned int argpar_iter_ingested_orig_args(
727 const struct argpar_iter
* const iter
)