2 * SPDX-License-Identifier: MIT
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
16 #define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
17 #define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
18 #define argpar_zalloc(_type) argpar_calloc(_type, 1)
20 #define ARGPAR_ASSERT(_cond) assert(_cond)
22 #ifdef __MINGW_PRINTF_FORMAT
23 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
25 # define ARGPAR_PRINTF_FORMAT printf
29 * Structure holding the argpar state between successive argpar_state_parse_next
32 * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`.
36 * Data provided by the user in argpar_state_create, does not change
40 const char * const *argv
;
41 const struct argpar_opt_descr
*descrs
;
44 * Index of the argument to process in the next argpar_state_parse_next
49 /* Counter of non-option arguments. */
53 * Short option state: if set, we are in the middle of a short option
54 * group, so we should resume there at the next argpar_state_parse_next
57 const char *short_opt_ch
;
60 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
61 char *argpar_vasprintf(const char *fmt
, va_list args
)
69 len1
= vsnprintf(NULL
, 0, fmt
, args
);
75 str
= malloc(len1
+ 1);
80 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
82 ARGPAR_ASSERT(len1
== len2
);
90 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
91 char *argpar_asprintf(const char *fmt
, ...)
97 str
= argpar_vasprintf(fmt
, args
);
103 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
104 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
106 char *new_str
= NULL
;
114 addendum
= argpar_vasprintf(fmt
, args
);
122 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
140 void argpar_item_destroy(struct argpar_item
*item
)
146 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
147 struct argpar_item_opt
* const opt_item
= (void *) item
;
149 free((void *) opt_item
->arg
);
159 bool push_item(struct argpar_item_array
* const array
,
160 struct argpar_item
* const item
)
164 ARGPAR_ASSERT(array
);
167 if (array
->n_items
== array
->n_alloc
) {
168 unsigned int new_n_alloc
= array
->n_alloc
* 2;
169 struct argpar_item
**new_items
;
171 new_items
= argpar_realloc(array
->items
,
172 struct argpar_item
*, new_n_alloc
);
178 array
->n_alloc
= new_n_alloc
;
179 array
->items
= new_items
;
182 array
->items
[array
->n_items
] = item
;
192 void destroy_item_array(struct argpar_item_array
* const array
)
197 for (i
= 0; i
< array
->n_items
; i
++) {
198 argpar_item_destroy(array
->items
[i
]);
207 struct argpar_item_array
*new_item_array(void)
209 struct argpar_item_array
*ret
;
210 const int initial_size
= 10;
212 ret
= argpar_zalloc(struct argpar_item_array
);
217 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
222 ret
->n_alloc
= initial_size
;
227 destroy_item_array(ret
);
235 struct argpar_item_opt
*create_opt_item(
236 const struct argpar_opt_descr
* const descr
,
237 const char * const arg
)
239 struct argpar_item_opt
*opt_item
=
240 argpar_zalloc(struct argpar_item_opt
);
246 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
247 opt_item
->descr
= descr
;
250 opt_item
->arg
= strdup(arg
);
251 if (!opt_item
->arg
) {
259 argpar_item_destroy(&opt_item
->base
);
267 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
268 const unsigned int orig_index
,
269 const unsigned int non_opt_index
)
271 struct argpar_item_non_opt
* const non_opt_item
=
272 argpar_zalloc(struct argpar_item_non_opt
);
278 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
279 non_opt_item
->arg
= arg
;
280 non_opt_item
->orig_index
= orig_index
;
281 non_opt_item
->non_opt_index
= non_opt_index
;
288 const struct argpar_opt_descr
*find_descr(
289 const struct argpar_opt_descr
* const descrs
,
290 const char short_name
, const char * const long_name
)
292 const struct argpar_opt_descr
*descr
;
294 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
295 if (short_name
&& descr
->short_name
&&
296 short_name
== descr
->short_name
) {
300 if (long_name
&& descr
->long_name
&&
301 strcmp(long_name
, descr
->long_name
) == 0) {
307 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
310 enum parse_orig_arg_opt_ret
{
311 PARSE_ORIG_ARG_OPT_RET_OK
,
312 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -2,
313 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
317 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
318 const char * const next_orig_arg
,
319 const struct argpar_opt_descr
* const descrs
,
320 struct argpar_state
*state
,
322 struct argpar_item
**item
)
324 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
325 bool used_next_orig_arg
= false;
327 if (strlen(short_opts
) == 0) {
328 argpar_string_append_printf(error
, "Invalid argument");
332 if (!state
->short_opt_ch
) {
333 state
->short_opt_ch
= short_opts
;
336 const char *opt_arg
= NULL
;
337 const struct argpar_opt_descr
*descr
;
338 struct argpar_item_opt
*opt_item
;
340 /* Find corresponding option descriptor */
341 descr
= find_descr(descrs
, *state
->short_opt_ch
, NULL
);
343 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
344 argpar_string_append_printf(error
,
345 "Unknown option `-%c`", *state
->short_opt_ch
);
349 if (descr
->with_arg
) {
350 if (state
->short_opt_ch
[1]) {
352 opt_arg
= &state
->short_opt_ch
[1];
355 opt_arg
= next_orig_arg
;
356 used_next_orig_arg
= true;
360 * We accept `-o ''` (empty option's argument),
361 * but not `-o` alone if an option's argument is
364 if (!opt_arg
|| (state
->short_opt_ch
[1] && strlen(opt_arg
) == 0)) {
365 argpar_string_append_printf(error
,
366 "Missing required argument for option `-%c`",
367 *state
->short_opt_ch
);
368 used_next_orig_arg
= false;
373 /* Create and append option argument */
374 opt_item
= create_opt_item(descr
, opt_arg
);
379 *item
= &opt_item
->base
;
381 state
->short_opt_ch
++;
383 if (descr
->with_arg
|| !*state
->short_opt_ch
) {
384 /* Option has an argument: no more options */
385 state
->short_opt_ch
= NULL
;
387 if (used_next_orig_arg
) {
397 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
398 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
406 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
407 const char * const next_orig_arg
,
408 const struct argpar_opt_descr
* const descrs
,
409 struct argpar_state
*state
,
411 struct argpar_item
**item
)
413 const size_t max_len
= 127;
414 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
415 const struct argpar_opt_descr
*descr
;
416 struct argpar_item_opt
*opt_item
;
417 bool used_next_orig_arg
= false;
419 /* Option's argument, if any */
420 const char *opt_arg
= NULL
;
422 /* Position of first `=`, if any */
425 /* Buffer holding option name when `long_opt_arg` contains `=` */
426 char buf
[max_len
+ 1];
429 const char *long_opt_name
= long_opt_arg
;
431 if (strlen(long_opt_arg
) == 0) {
432 argpar_string_append_printf(error
,
437 /* Find the first `=` in original argument */
438 eq_pos
= strchr(long_opt_arg
, '=');
440 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
442 /* Isolate the option name */
443 if (long_opt_name_size
> max_len
) {
444 argpar_string_append_printf(error
,
445 "Invalid argument `--%s`", long_opt_arg
);
449 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
450 buf
[long_opt_name_size
] = '\0';
454 /* Find corresponding option descriptor */
455 descr
= find_descr(descrs
, '\0', long_opt_name
);
457 argpar_string_append_printf(error
,
458 "Unknown option `--%s`", long_opt_name
);
459 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
463 /* Find option's argument if any */
464 if (descr
->with_arg
) {
466 /* `--long-opt=arg` style */
467 opt_arg
= eq_pos
+ 1;
469 /* `--long-opt arg` style */
470 if (!next_orig_arg
) {
471 argpar_string_append_printf(error
,
472 "Missing required argument for option `--%s`",
477 opt_arg
= next_orig_arg
;
478 used_next_orig_arg
= true;
482 /* Create and append option argument */
483 opt_item
= create_opt_item(descr
, opt_arg
);
488 if (used_next_orig_arg
) {
494 *item
= &opt_item
->base
;
498 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
499 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
507 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
508 const char * const next_orig_arg
,
509 const struct argpar_opt_descr
* const descrs
,
510 struct argpar_state
*state
,
512 struct argpar_item
**item
)
514 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
516 ARGPAR_ASSERT(orig_arg
[0] == '-');
518 if (orig_arg
[1] == '-') {
520 ret
= parse_long_opt(&orig_arg
[2],
521 next_orig_arg
, descrs
, state
, error
, item
);
524 ret
= parse_short_opts(&orig_arg
[1],
525 next_orig_arg
, descrs
, state
, error
, item
);
532 bool prepend_while_parsing_arg_to_error(char **error
,
533 const unsigned int i
, const char * const arg
)
538 ARGPAR_ASSERT(error
);
539 ARGPAR_ASSERT(*error
);
541 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
557 struct argpar_state
*argpar_state_create(
559 const char * const *argv
,
560 const struct argpar_opt_descr
* const descrs
)
562 struct argpar_state
*state
;
564 state
= argpar_zalloc(struct argpar_state
);
571 state
->descrs
= descrs
;
578 void argpar_state_destroy(struct argpar_state
*state
)
584 enum argpar_state_parse_next_status
argpar_state_parse_next(
585 struct argpar_state
*state
,
586 struct argpar_item
**item
,
589 enum argpar_state_parse_next_status status
;
591 ARGPAR_ASSERT(state
->i
<= state
->argc
);
595 if (state
->i
== state
->argc
) {
596 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_END
;
600 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
601 const char * const orig_arg
= state
->argv
[state
->i
];
602 const char * const next_orig_arg
=
603 state
->i
< (state
->argc
- 1) ? state
->argv
[state
->i
+ 1] : NULL
;
605 if (orig_arg
[0] != '-') {
606 /* Non-option argument */
607 struct argpar_item_non_opt
*non_opt_item
=
608 create_non_opt_item(orig_arg
, state
->i
, state
->non_opt_index
);
611 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
615 state
->non_opt_index
++;
618 *item
= &non_opt_item
->base
;
619 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
623 /* Option argument */
624 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
625 next_orig_arg
, state
->descrs
, state
, error
, item
);
626 switch (parse_orig_arg_opt_ret
) {
627 case PARSE_ORIG_ARG_OPT_RET_OK
:
628 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
630 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
631 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
633 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
634 prepend_while_parsing_arg_to_error(
635 error
, state
->i
, orig_arg
);
636 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
647 int argpar_state_get_ingested_orig_args(struct argpar_state
*state
)
653 struct argpar_parse_ret
argpar_parse(unsigned int argc
,
654 const char * const *argv
,
655 const struct argpar_opt_descr
* const descrs
,
656 bool fail_on_unknown_opt
)
658 struct argpar_parse_ret parse_ret
= { 0 };
659 struct argpar_item
*item
= NULL
;
660 struct argpar_state
*state
= NULL
;
662 parse_ret
.items
= new_item_array();
663 if (!parse_ret
.items
) {
664 parse_ret
.error
= strdup("Failed to create items array.");
665 ARGPAR_ASSERT(parse_ret
.error
);
669 state
= argpar_state_create(argc
, argv
, descrs
);
671 parse_ret
.error
= strdup("Failed to create argpar state.");
672 ARGPAR_ASSERT(parse_ret
.error
);
677 enum argpar_state_parse_next_status status
;
679 status
= argpar_state_parse_next(state
, &item
, &parse_ret
.error
);
680 if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
) {
682 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_END
) {
684 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
685 if (fail_on_unknown_opt
) {
686 parse_ret
.ingested_orig_args
=
687 argpar_state_get_ingested_orig_args(state
);
688 prepend_while_parsing_arg_to_error(
689 &parse_ret
.error
, parse_ret
.ingested_orig_args
,
690 argv
[parse_ret
.ingested_orig_args
]);
691 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
695 free(parse_ret
.error
);
696 parse_ret
.error
= NULL
;
700 ARGPAR_ASSERT(status
== ARGPAR_STATE_PARSE_NEXT_STATUS_OK
);
702 if (!push_item(parse_ret
.items
, item
)) {
708 ARGPAR_ASSERT(!parse_ret
.error
);
709 parse_ret
.ingested_orig_args
=
710 argpar_state_get_ingested_orig_args(state
);
714 ARGPAR_ASSERT(parse_ret
.error
);
716 /* That's how we indicate that an error occurred */
717 destroy_item_array(parse_ret
.items
);
718 parse_ret
.items
= NULL
;
721 argpar_state_destroy(state
);
722 argpar_item_destroy(item
);
727 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
731 destroy_item_array(ret
->items
);