2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 #define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
33 #define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
34 #define argpar_zalloc(_type) argpar_calloc(_type, 1)
36 #define ARGPAR_ASSERT(_cond) assert(_cond)
39 * Structure holding the argpar state between successive argpar_state_parse_next
42 * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`.
46 * Data provided by the user in argpar_state_create, does not change
50 const char * const *argv
;
51 const struct argpar_opt_descr
*descrs
;
54 * Index of the argument to process in the next argpar_state_parse_next
59 /* Counter of non-option arguments. */
63 * Short option state: if set, we are in the middle of a short option
64 * group, so we should resume there at the next argpar_state_parse_next
67 const char *short_opt_ch
;
71 char *argpar_vasprintf(const char *fmt
, va_list args
)
79 len1
= vsnprintf(NULL
, 0, fmt
, args
);
85 str
= malloc(len1
+ 1);
90 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
92 ARGPAR_ASSERT(len1
== len2
);
101 char *argpar_asprintf(const char *fmt
, ...)
107 str
= argpar_vasprintf(fmt
, args
);
114 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
116 char *new_str
= NULL
;
124 addendum
= argpar_vasprintf(fmt
, args
);
132 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
150 void argpar_item_destroy(struct argpar_item
*item
)
156 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
157 struct argpar_item_opt
* const opt_item
= (void *) item
;
159 free((void *) opt_item
->arg
);
169 bool push_item(struct argpar_item_array
* const array
,
170 struct argpar_item
* const item
)
174 ARGPAR_ASSERT(array
);
177 if (array
->n_items
== array
->n_alloc
) {
178 unsigned int new_n_alloc
= array
->n_alloc
* 2;
179 struct argpar_item
**new_items
;
181 new_items
= argpar_realloc(array
->items
,
182 struct argpar_item
*, new_n_alloc
);
188 array
->n_alloc
= new_n_alloc
;
189 array
->items
= new_items
;
192 array
->items
[array
->n_items
] = item
;
202 void destroy_item_array(struct argpar_item_array
* const array
)
207 for (i
= 0; i
< array
->n_items
; i
++) {
208 argpar_item_destroy(array
->items
[i
]);
217 struct argpar_item_array
*new_item_array(void)
219 struct argpar_item_array
*ret
;
220 const int initial_size
= 10;
222 ret
= argpar_zalloc(struct argpar_item_array
);
227 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
232 ret
->n_alloc
= initial_size
;
237 destroy_item_array(ret
);
245 struct argpar_item_opt
*create_opt_item(
246 const struct argpar_opt_descr
* const descr
,
247 const char * const arg
)
249 struct argpar_item_opt
*opt_item
=
250 argpar_zalloc(struct argpar_item_opt
);
256 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
257 opt_item
->descr
= descr
;
260 opt_item
->arg
= strdup(arg
);
261 if (!opt_item
->arg
) {
269 argpar_item_destroy(&opt_item
->base
);
277 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
278 const unsigned int orig_index
,
279 const unsigned int non_opt_index
)
281 struct argpar_item_non_opt
* const non_opt_item
=
282 argpar_zalloc(struct argpar_item_non_opt
);
288 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
289 non_opt_item
->arg
= arg
;
290 non_opt_item
->orig_index
= orig_index
;
291 non_opt_item
->non_opt_index
= non_opt_index
;
298 const struct argpar_opt_descr
*find_descr(
299 const struct argpar_opt_descr
* const descrs
,
300 const char short_name
, const char * const long_name
)
302 const struct argpar_opt_descr
*descr
;
304 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
305 if (short_name
&& descr
->short_name
&&
306 short_name
== descr
->short_name
) {
310 if (long_name
&& descr
->long_name
&&
311 strcmp(long_name
, descr
->long_name
) == 0) {
317 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
320 enum parse_orig_arg_opt_ret
{
321 PARSE_ORIG_ARG_OPT_RET_OK
,
322 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -2,
323 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
327 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
328 const char * const next_orig_arg
,
329 const struct argpar_opt_descr
* const descrs
,
330 struct argpar_state
*state
,
332 struct argpar_item
**item
)
334 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
335 bool used_next_orig_arg
= false;
337 if (strlen(short_opts
) == 0) {
338 argpar_string_append_printf(error
, "Invalid argument");
342 if (!state
->short_opt_ch
) {
343 state
->short_opt_ch
= short_opts
;
346 const char *opt_arg
= NULL
;
347 const struct argpar_opt_descr
*descr
;
348 struct argpar_item_opt
*opt_item
;
350 /* Find corresponding option descriptor */
351 descr
= find_descr(descrs
, *state
->short_opt_ch
, NULL
);
353 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
354 argpar_string_append_printf(error
,
355 "Unknown option `-%c`", *state
->short_opt_ch
);
359 if (descr
->with_arg
) {
360 if (state
->short_opt_ch
[1]) {
362 opt_arg
= &state
->short_opt_ch
[1];
365 opt_arg
= next_orig_arg
;
366 used_next_orig_arg
= true;
370 * We accept `-o ''` (empty option's argument),
371 * but not `-o` alone if an option's argument is
374 if (!opt_arg
|| (state
->short_opt_ch
[1] && strlen(opt_arg
) == 0)) {
375 argpar_string_append_printf(error
,
376 "Missing required argument for option `-%c`",
377 *state
->short_opt_ch
);
378 used_next_orig_arg
= false;
383 /* Create and append option argument */
384 opt_item
= create_opt_item(descr
, opt_arg
);
389 *item
= &opt_item
->base
;
391 state
->short_opt_ch
++;
393 if (descr
->with_arg
|| !*state
->short_opt_ch
) {
394 /* Option has an argument: no more options */
395 state
->short_opt_ch
= NULL
;
397 if (used_next_orig_arg
) {
407 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
408 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
416 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
417 const char * const next_orig_arg
,
418 const struct argpar_opt_descr
* const descrs
,
419 struct argpar_state
*state
,
421 struct argpar_item
**item
)
423 const size_t max_len
= 127;
424 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
425 const struct argpar_opt_descr
*descr
;
426 struct argpar_item_opt
*opt_item
;
427 bool used_next_orig_arg
= false;
429 /* Option's argument, if any */
430 const char *opt_arg
= NULL
;
432 /* Position of first `=`, if any */
435 /* Buffer holding option name when `long_opt_arg` contains `=` */
436 char buf
[max_len
+ 1];
439 const char *long_opt_name
= long_opt_arg
;
441 if (strlen(long_opt_arg
) == 0) {
442 argpar_string_append_printf(error
,
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 if (long_opt_name_size
> max_len
) {
454 argpar_string_append_printf(error
,
455 "Invalid argument `--%s`", long_opt_arg
);
459 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
460 buf
[long_opt_name_size
] = '\0';
464 /* Find corresponding option descriptor */
465 descr
= find_descr(descrs
, '\0', long_opt_name
);
467 argpar_string_append_printf(error
,
468 "Unknown option `--%s`", long_opt_name
);
469 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
473 /* Find option's argument if any */
474 if (descr
->with_arg
) {
476 /* `--long-opt=arg` style */
477 opt_arg
= eq_pos
+ 1;
479 /* `--long-opt arg` style */
480 if (!next_orig_arg
) {
481 argpar_string_append_printf(error
,
482 "Missing required argument for option `--%s`",
487 opt_arg
= next_orig_arg
;
488 used_next_orig_arg
= true;
492 /* Create and append option argument */
493 opt_item
= create_opt_item(descr
, opt_arg
);
498 if (used_next_orig_arg
) {
504 *item
= &opt_item
->base
;
508 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
509 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
517 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
518 const char * const next_orig_arg
,
519 const struct argpar_opt_descr
* const descrs
,
520 struct argpar_state
*state
,
522 struct argpar_item
**item
)
524 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
526 ARGPAR_ASSERT(orig_arg
[0] == '-');
528 if (orig_arg
[1] == '-') {
530 ret
= parse_long_opt(&orig_arg
[2],
531 next_orig_arg
, descrs
, state
, error
, item
);
534 ret
= parse_short_opts(&orig_arg
[1],
535 next_orig_arg
, descrs
, state
, error
, item
);
542 bool prepend_while_parsing_arg_to_error(char **error
,
543 const unsigned int i
, const char * const arg
)
548 ARGPAR_ASSERT(error
);
549 ARGPAR_ASSERT(*error
);
551 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
567 struct argpar_state
*argpar_state_create(
569 const char * const *argv
,
570 const struct argpar_opt_descr
* const descrs
)
572 struct argpar_state
*state
;
574 state
= argpar_zalloc(struct argpar_state
);
581 state
->descrs
= descrs
;
588 void argpar_state_destroy(struct argpar_state
*state
)
594 enum argpar_state_parse_next_status
argpar_state_parse_next(
595 struct argpar_state
*state
,
596 struct argpar_item
**item
,
599 enum argpar_state_parse_next_status status
;
601 ARGPAR_ASSERT(state
->i
<= state
->argc
);
605 if (state
->i
== state
->argc
) {
606 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_END
;
610 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
611 const char * const orig_arg
= state
->argv
[state
->i
];
612 const char * const next_orig_arg
=
613 state
->i
< (state
->argc
- 1) ? state
->argv
[state
->i
+ 1] : NULL
;
615 if (orig_arg
[0] != '-') {
616 /* Non-option argument */
617 struct argpar_item_non_opt
*non_opt_item
=
618 create_non_opt_item(orig_arg
, state
->i
, state
->non_opt_index
);
621 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
625 state
->non_opt_index
++;
628 *item
= &non_opt_item
->base
;
629 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
633 /* Option argument */
634 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
635 next_orig_arg
, state
->descrs
, state
, error
, item
);
636 switch (parse_orig_arg_opt_ret
) {
637 case PARSE_ORIG_ARG_OPT_RET_OK
:
638 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_OK
;
640 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
641 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
;
643 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
644 prepend_while_parsing_arg_to_error(
645 error
, state
->i
, orig_arg
);
646 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
657 int argpar_state_get_ingested_orig_args(struct argpar_state
*state
)
663 struct argpar_parse_ret
argpar_parse(unsigned int argc
,
664 const char * const *argv
,
665 const struct argpar_opt_descr
* const descrs
,
666 bool fail_on_unknown_opt
)
668 struct argpar_parse_ret parse_ret
= { 0 };
669 struct argpar_item
*item
= NULL
;
670 struct argpar_state
*state
= NULL
;
672 parse_ret
.items
= new_item_array();
673 if (!parse_ret
.items
) {
674 parse_ret
.error
= strdup("Failed to create items array.");
675 ARGPAR_ASSERT(parse_ret
.error
);
679 state
= argpar_state_create(argc
, argv
, descrs
);
681 parse_ret
.error
= strdup("Failed to create argpar state.");
682 ARGPAR_ASSERT(parse_ret
.error
);
687 enum argpar_state_parse_next_status status
;
689 status
= argpar_state_parse_next(state
, &item
, &parse_ret
.error
);
690 if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
) {
692 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_END
) {
694 } else if (status
== ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
695 if (fail_on_unknown_opt
) {
696 parse_ret
.ingested_orig_args
=
697 argpar_state_get_ingested_orig_args(state
);
698 prepend_while_parsing_arg_to_error(
699 &parse_ret
.error
, parse_ret
.ingested_orig_args
,
700 argv
[parse_ret
.ingested_orig_args
]);
701 status
= ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR
;
705 free(parse_ret
.error
);
706 parse_ret
.error
= NULL
;
710 ARGPAR_ASSERT(status
== ARGPAR_STATE_PARSE_NEXT_STATUS_OK
);
712 if (!push_item(parse_ret
.items
, item
)) {
718 ARGPAR_ASSERT(!parse_ret
.error
);
719 parse_ret
.ingested_orig_args
=
720 argpar_state_get_ingested_orig_args(state
);
724 ARGPAR_ASSERT(parse_ret
.error
);
726 /* That's how we indicate that an error occured */
727 destroy_item_array(parse_ret
.items
);
728 parse_ret
.items
= NULL
;
731 argpar_state_destroy(state
);
732 argpar_item_destroy(item
);
737 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
741 destroy_item_array(ret
->items
);