2 * SPDX-License-Identifier: MIT
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
15 #define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
16 #define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
17 #define argpar_zalloc(_type) argpar_calloc(_type, 1)
19 #define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
21 #ifdef __MINGW_PRINTF_FORMAT
22 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
24 # define ARGPAR_PRINTF_FORMAT printf
28 * Structure holding the argpar state between successive argpar_state_parse_next
31 * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`.
35 * Data provided by the user in argpar_state_create, does not change
39 const char * const *argv
;
40 const struct argpar_opt_descr
*descrs
;
43 * Index of the argument to process in the next argpar_state_parse_next
48 /* Counter of non-option arguments. */
52 * Short option state: if set, we are in the middle of a short option
53 * group, so we should resume there at the next argpar_state_parse_next
56 const char *short_opt_ch
;
59 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
60 char *argpar_vasprintf(const char *fmt
, va_list args
)
68 len1
= vsnprintf(NULL
, 0, fmt
, args
);
74 str
= malloc(len1
+ 1);
79 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
81 ARGPAR_ASSERT(len1
== len2
);
89 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
90 char *argpar_asprintf(const char *fmt
, ...)
96 str
= argpar_vasprintf(fmt
, args
);
102 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
103 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
105 char *new_str
= NULL
;
113 addendum
= argpar_vasprintf(fmt
, args
);
121 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
139 void argpar_item_destroy(struct argpar_item
*item
)
145 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
146 struct argpar_item_opt
* const opt_item
= (void *) item
;
148 free((void *) opt_item
->arg
);
158 bool push_item(struct argpar_item_array
* const array
,
159 struct argpar_item
* const item
)
163 ARGPAR_ASSERT(array
);
166 if (array
->n_items
== array
->n_alloc
) {
167 unsigned int new_n_alloc
= array
->n_alloc
* 2;
168 struct argpar_item
**new_items
;
170 new_items
= argpar_realloc(array
->items
,
171 struct argpar_item
*, new_n_alloc
);
177 array
->n_alloc
= new_n_alloc
;
178 array
->items
= new_items
;
181 array
->items
[array
->n_items
] = item
;
191 void destroy_item_array(struct argpar_item_array
* const array
)
196 for (i
= 0; i
< array
->n_items
; i
++) {
197 argpar_item_destroy(array
->items
[i
]);
206 struct argpar_item_array
*new_item_array(void)
208 struct argpar_item_array
*ret
;
209 const int initial_size
= 10;
211 ret
= argpar_zalloc(struct argpar_item_array
);
216 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
221 ret
->n_alloc
= initial_size
;
226 destroy_item_array(ret
);
234 struct argpar_item_opt
*create_opt_item(
235 const struct argpar_opt_descr
* const descr
,
236 const char * const arg
)
238 struct argpar_item_opt
*opt_item
=
239 argpar_zalloc(struct argpar_item_opt
);
245 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
246 opt_item
->descr
= descr
;
249 opt_item
->arg
= strdup(arg
);
250 if (!opt_item
->arg
) {
258 argpar_item_destroy(&opt_item
->base
);
266 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
267 const unsigned int orig_index
,
268 const unsigned int non_opt_index
)
270 struct argpar_item_non_opt
* const non_opt_item
=
271 argpar_zalloc(struct argpar_item_non_opt
);
277 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
278 non_opt_item
->arg
= arg
;
279 non_opt_item
->orig_index
= orig_index
;
280 non_opt_item
->non_opt_index
= non_opt_index
;
287 const struct argpar_opt_descr
*find_descr(
288 const struct argpar_opt_descr
* const descrs
,
289 const char short_name
, const char * const long_name
)
291 const struct argpar_opt_descr
*descr
;
293 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
294 if (short_name
&& descr
->short_name
&&
295 short_name
== descr
->short_name
) {
299 if (long_name
&& descr
->long_name
&&
300 strcmp(long_name
, descr
->long_name
) == 0) {
306 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
309 enum parse_orig_arg_opt_ret
{
310 PARSE_ORIG_ARG_OPT_RET_OK
,
311 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -2,
312 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
316 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
317 const char * const next_orig_arg
,
318 const struct argpar_opt_descr
* const descrs
,
319 struct argpar_state
*state
,
321 struct argpar_item
**item
)
323 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
324 bool used_next_orig_arg
= false;
326 if (strlen(short_opts
) == 0) {
327 argpar_string_append_printf(error
, "Invalid argument");
331 if (!state
->short_opt_ch
) {
332 state
->short_opt_ch
= short_opts
;
335 const char *opt_arg
= NULL
;
336 const struct argpar_opt_descr
*descr
;
337 struct argpar_item_opt
*opt_item
;
339 /* Find corresponding option descriptor */
340 descr
= find_descr(descrs
, *state
->short_opt_ch
, NULL
);
342 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
343 argpar_string_append_printf(error
,
344 "Unknown option `-%c`", *state
->short_opt_ch
);
348 if (descr
->with_arg
) {
349 if (state
->short_opt_ch
[1]) {
351 opt_arg
= &state
->short_opt_ch
[1];
354 opt_arg
= next_orig_arg
;
355 used_next_orig_arg
= true;
359 * We accept `-o ''` (empty option's argument),
360 * but not `-o` alone if an option's argument is
363 if (!opt_arg
|| (state
->short_opt_ch
[1] && strlen(opt_arg
) == 0)) {
364 argpar_string_append_printf(error
,
365 "Missing required argument for option `-%c`",
366 *state
->short_opt_ch
);
367 used_next_orig_arg
= false;
372 /* Create and append option argument */
373 opt_item
= create_opt_item(descr
, opt_arg
);
378 *item
= &opt_item
->base
;
380 state
->short_opt_ch
++;
382 if (descr
->with_arg
|| !*state
->short_opt_ch
) {
383 /* Option has an argument: no more options */
384 state
->short_opt_ch
= NULL
;
386 if (used_next_orig_arg
) {
396 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
397 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
405 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
406 const char * const next_orig_arg
,
407 const struct argpar_opt_descr
* const descrs
,
408 struct argpar_state
*state
,
410 struct argpar_item
**item
)
412 const size_t max_len
= 127;
413 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
414 const struct argpar_opt_descr
*descr
;
415 struct argpar_item_opt
*opt_item
;
416 bool used_next_orig_arg
= false;
418 /* Option's argument, if any */
419 const char *opt_arg
= NULL
;
421 /* Position of first `=`, if any */
424 /* Buffer holding option name when `long_opt_arg` contains `=` */
425 char buf
[max_len
+ 1];
428 const char *long_opt_name
= long_opt_arg
;
430 if (strlen(long_opt_arg
) == 0) {
431 argpar_string_append_printf(error
,
436 /* Find the first `=` in original argument */
437 eq_pos
= strchr(long_opt_arg
, '=');
439 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
441 /* Isolate the option name */
442 if (long_opt_name_size
> max_len
) {
443 argpar_string_append_printf(error
,
444 "Invalid argument `--%s`", long_opt_arg
);
448 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
449 buf
[long_opt_name_size
] = '\0';
453 /* Find corresponding option descriptor */
454 descr
= find_descr(descrs
, '\0', long_opt_name
);
456 argpar_string_append_printf(error
,
457 "Unknown option `--%s`", long_opt_name
);
458 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
462 /* Find option's argument if any */
463 if (descr
->with_arg
) {
465 /* `--long-opt=arg` style */
466 opt_arg
= eq_pos
+ 1;
468 /* `--long-opt arg` style */
469 if (!next_orig_arg
) {
470 argpar_string_append_printf(error
,
471 "Missing required argument for option `--%s`",
476 opt_arg
= next_orig_arg
;
477 used_next_orig_arg
= true;
481 * Unexpected `--opt=arg` style for a long option which
482 * doesn't accept an argument.
484 argpar_string_append_printf(error
,
485 "Unexpected argument for option `--%s`",
490 /* Create and append option argument */
491 opt_item
= create_opt_item(descr
, opt_arg
);
496 if (used_next_orig_arg
) {
502 *item
= &opt_item
->base
;
506 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
507 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
515 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
516 const char * const next_orig_arg
,
517 const struct argpar_opt_descr
* const descrs
,
518 struct argpar_state
*state
,
520 struct argpar_item
**item
)
522 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
524 ARGPAR_ASSERT(orig_arg
[0] == '-');
526 if (orig_arg
[1] == '-') {
528 ret
= parse_long_opt(&orig_arg
[2],
529 next_orig_arg
, descrs
, state
, error
, item
);
532 ret
= parse_short_opts(&orig_arg
[1],
533 next_orig_arg
, descrs
, state
, error
, item
);
540 bool prepend_while_parsing_arg_to_error(char **error
,
541 const unsigned int i
, const char * const arg
)
546 ARGPAR_ASSERT(error
);
547 ARGPAR_ASSERT(*error
);
549 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
565 struct argpar_state
*argpar_state_create(
567 const char * const *argv
,
568 const struct argpar_opt_descr
* const descrs
)
570 struct argpar_state
*state
;
572 state
= argpar_zalloc(struct argpar_state
);
579 state
->descrs
= descrs
;
586 void argpar_state_destroy(struct argpar_state
*state
)
592 enum argpar_state_parse_next_status
argpar_state_parse_next(
593 struct argpar_state
*state
,
594 struct argpar_item
**item
,
597 enum argpar_state_parse_next_status status
;
599 ARGPAR_ASSERT(state
->i
<= state
->argc
);
603 if (state
->i
== state
->argc
) {
604 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_END
;
608 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
609 const char * const orig_arg
= state
->argv
[state
->i
];
610 const char * const next_orig_arg
=
611 state
->i
< (state
->argc
- 1) ? state
->argv
[state
->i
+ 1] : NULL
;
613 if (orig_arg
[0] != '-') {
614 /* Non-option argument */
615 struct argpar_item_non_opt
*non_opt_item
=
616 create_non_opt_item(orig_arg
, state
->i
, state
->non_opt_index
);
619 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
623 state
->non_opt_index
++;
626 *item
= &non_opt_item
->base
;
627 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
631 /* Option argument */
632 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
633 next_orig_arg
, state
->descrs
, state
, error
, item
);
634 switch (parse_orig_arg_opt_ret
) {
635 case PARSE_ORIG_ARG_OPT_RET_OK
:
636 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
638 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
639 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
641 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
642 prepend_while_parsing_arg_to_error(
643 error
, state
->i
, orig_arg
);
644 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
655 int argpar_state_get_ingested_orig_args(struct argpar_state
*state
)
661 struct argpar_parse_ret
argpar_parse(unsigned int argc
,
662 const char * const *argv
,
663 const struct argpar_opt_descr
* const descrs
,
664 bool fail_on_unknown_opt
)
666 struct argpar_parse_ret parse_ret
= { 0 };
667 struct argpar_item
*item
= NULL
;
668 struct argpar_state
*state
= NULL
;
670 parse_ret
.items
= new_item_array();
671 if (!parse_ret
.items
) {
672 parse_ret
.error
= strdup("Failed to create items array.");
673 ARGPAR_ASSERT(parse_ret
.error
);
677 state
= argpar_state_create(argc
, argv
, descrs
);
679 parse_ret
.error
= strdup("Failed to create argpar state.");
680 ARGPAR_ASSERT(parse_ret
.error
);
685 enum argpar_state_parse_next_status status
;
687 status
= argpar_state_parse_next(state
, &item
, &parse_ret
.error
);
688 if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
) {
690 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_END
) {
692 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
693 if (fail_on_unknown_opt
) {
694 parse_ret
.ingested_orig_args
=
695 argpar_state_get_ingested_orig_args(state
);
696 prepend_while_parsing_arg_to_error(
697 &parse_ret
.error
, parse_ret
.ingested_orig_args
,
698 argv
[parse_ret
.ingested_orig_args
]);
699 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
703 free(parse_ret
.error
);
704 parse_ret
.error
= NULL
;
708 ARGPAR_ASSERT(status
== ARGPAR_STATE_PARSE_NEXT_STATUS_OK
);
710 if (!push_item(parse_ret
.items
, item
)) {
716 ARGPAR_ASSERT(!parse_ret
.error
);
717 parse_ret
.ingested_orig_args
=
718 argpar_state_get_ingested_orig_args(state
);
722 ARGPAR_ASSERT(parse_ret
.error
);
724 /* That's how we indicate that an error occurred */
725 destroy_item_array(parse_ret
.items
);
726 parse_ret
.items
= NULL
;
729 argpar_state_destroy(state
);
730 argpar_item_destroy(item
);
735 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
739 destroy_item_array(ret
->items
);