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
28 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
29 char *argpar_vasprintf(const char *fmt
, va_list args
)
37 len1
= vsnprintf(NULL
, 0, fmt
, args
);
43 str
= malloc(len1
+ 1);
48 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
50 ARGPAR_ASSERT(len1
== len2
);
58 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
59 char *argpar_asprintf(const char *fmt
, ...)
65 str
= argpar_vasprintf(fmt
, args
);
71 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
72 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
82 addendum
= argpar_vasprintf(fmt
, args
);
90 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
108 void destroy_item(struct argpar_item
* const item
)
114 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
115 struct argpar_item_opt
* const opt_item
= (void *) item
;
117 free((void *) opt_item
->arg
);
127 bool push_item(struct argpar_item_array
* const array
,
128 struct argpar_item
* const item
)
132 ARGPAR_ASSERT(array
);
135 if (array
->n_items
== array
->n_alloc
) {
136 unsigned int new_n_alloc
= array
->n_alloc
* 2;
137 struct argpar_item
**new_items
;
139 new_items
= argpar_realloc(array
->items
,
140 struct argpar_item
*, new_n_alloc
);
146 array
->n_alloc
= new_n_alloc
;
147 array
->items
= new_items
;
150 array
->items
[array
->n_items
] = item
;
160 void destroy_item_array(struct argpar_item_array
* const array
)
165 for (i
= 0; i
< array
->n_items
; i
++) {
166 destroy_item(array
->items
[i
]);
175 struct argpar_item_array
*new_item_array(void)
177 struct argpar_item_array
*ret
;
178 const int initial_size
= 10;
180 ret
= argpar_zalloc(struct argpar_item_array
);
185 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
190 ret
->n_alloc
= initial_size
;
195 destroy_item_array(ret
);
203 struct argpar_item_opt
*create_opt_item(
204 const struct argpar_opt_descr
* const descr
,
205 const char * const arg
)
207 struct argpar_item_opt
*opt_item
=
208 argpar_zalloc(struct argpar_item_opt
);
214 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
215 opt_item
->descr
= descr
;
218 opt_item
->arg
= strdup(arg
);
219 if (!opt_item
->arg
) {
227 destroy_item(&opt_item
->base
);
235 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
236 const unsigned int orig_index
,
237 const unsigned int non_opt_index
)
239 struct argpar_item_non_opt
* const non_opt_item
=
240 argpar_zalloc(struct argpar_item_non_opt
);
246 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
247 non_opt_item
->arg
= arg
;
248 non_opt_item
->orig_index
= orig_index
;
249 non_opt_item
->non_opt_index
= non_opt_index
;
256 const struct argpar_opt_descr
*find_descr(
257 const struct argpar_opt_descr
* const descrs
,
258 const char short_name
, const char * const long_name
)
260 const struct argpar_opt_descr
*descr
;
262 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
263 if (short_name
&& descr
->short_name
&&
264 short_name
== descr
->short_name
) {
268 if (long_name
&& descr
->long_name
&&
269 strcmp(long_name
, descr
->long_name
) == 0) {
275 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
278 enum parse_orig_arg_opt_ret
{
279 PARSE_ORIG_ARG_OPT_RET_OK
,
280 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -2,
281 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
285 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
286 const char * const next_orig_arg
,
287 const struct argpar_opt_descr
* const descrs
,
288 struct argpar_parse_ret
* const parse_ret
,
289 bool * const used_next_orig_arg
)
291 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
292 const char *short_opt_ch
= short_opts
;
294 if (strlen(short_opts
) == 0) {
295 argpar_string_append_printf(&parse_ret
->error
, "Invalid argument");
299 while (*short_opt_ch
) {
300 const char *opt_arg
= NULL
;
301 const struct argpar_opt_descr
*descr
;
302 struct argpar_item_opt
*opt_item
;
304 /* Find corresponding option descriptor */
305 descr
= find_descr(descrs
, *short_opt_ch
, NULL
);
307 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
308 argpar_string_append_printf(&parse_ret
->error
,
309 "Unknown option `-%c`", *short_opt_ch
);
313 if (descr
->with_arg
) {
314 if (short_opt_ch
[1]) {
316 opt_arg
= &short_opt_ch
[1];
319 opt_arg
= next_orig_arg
;
320 *used_next_orig_arg
= true;
324 * We accept `-o ''` (empty option's argument),
325 * but not `-o` alone if an option's argument is
328 if (!opt_arg
|| (short_opt_ch
[1] && strlen(opt_arg
) == 0)) {
329 argpar_string_append_printf(&parse_ret
->error
,
330 "Missing required argument for option `-%c`",
332 *used_next_orig_arg
= false;
337 /* Create and append option argument */
338 opt_item
= create_opt_item(descr
, opt_arg
);
343 if (!push_item(parse_ret
->items
, &opt_item
->base
)) {
347 if (descr
->with_arg
) {
348 /* Option has an argument: no more options */
352 /* Go to next short option */
359 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
360 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
368 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
369 const char * const next_orig_arg
,
370 const struct argpar_opt_descr
* const descrs
,
371 struct argpar_parse_ret
* const parse_ret
,
372 bool * const used_next_orig_arg
)
374 const size_t max_len
= 127;
375 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
376 const struct argpar_opt_descr
*descr
;
377 struct argpar_item_opt
*opt_item
;
379 /* Option's argument, if any */
380 const char *opt_arg
= NULL
;
382 /* Position of first `=`, if any */
385 /* Buffer holding option name when `long_opt_arg` contains `=` */
386 char buf
[max_len
+ 1];
389 const char *long_opt_name
= long_opt_arg
;
391 if (strlen(long_opt_arg
) == 0) {
392 argpar_string_append_printf(&parse_ret
->error
,
397 /* Find the first `=` in original argument */
398 eq_pos
= strchr(long_opt_arg
, '=');
400 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
402 /* Isolate the option name */
403 if (long_opt_name_size
> max_len
) {
404 argpar_string_append_printf(&parse_ret
->error
,
405 "Invalid argument `--%s`", long_opt_arg
);
409 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
410 buf
[long_opt_name_size
] = '\0';
414 /* Find corresponding option descriptor */
415 descr
= find_descr(descrs
, '\0', long_opt_name
);
417 argpar_string_append_printf(&parse_ret
->error
,
418 "Unknown option `--%s`", long_opt_name
);
419 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
423 /* Find option's argument if any */
424 if (descr
->with_arg
) {
426 /* `--long-opt=arg` style */
427 opt_arg
= eq_pos
+ 1;
429 /* `--long-opt arg` style */
430 if (!next_orig_arg
) {
431 argpar_string_append_printf(&parse_ret
->error
,
432 "Missing required argument for option `--%s`",
437 opt_arg
= next_orig_arg
;
438 *used_next_orig_arg
= true;
442 /* Create and append option argument */
443 opt_item
= create_opt_item(descr
, opt_arg
);
448 if (!push_item(parse_ret
->items
, &opt_item
->base
)) {
455 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
456 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
464 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
465 const char * const next_orig_arg
,
466 const struct argpar_opt_descr
* const descrs
,
467 struct argpar_parse_ret
* const parse_ret
,
468 bool * const used_next_orig_arg
)
470 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
472 ARGPAR_ASSERT(orig_arg
[0] == '-');
474 if (orig_arg
[1] == '-') {
476 ret
= parse_long_opt(&orig_arg
[2],
477 next_orig_arg
, descrs
, parse_ret
,
481 ret
= parse_short_opts(&orig_arg
[1],
482 next_orig_arg
, descrs
, parse_ret
,
490 bool prepend_while_parsing_arg_to_error(char **error
,
491 const unsigned int i
, const char * const arg
)
496 ARGPAR_ASSERT(error
);
497 ARGPAR_ASSERT(*error
);
499 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
515 struct argpar_parse_ret
argpar_parse(unsigned int argc
,
516 const char * const *argv
,
517 const struct argpar_opt_descr
* const descrs
,
518 bool fail_on_unknown_opt
)
520 struct argpar_parse_ret parse_ret
= { 0 };
522 unsigned int non_opt_index
= 0;
524 parse_ret
.items
= new_item_array();
525 if (!parse_ret
.items
) {
529 for (i
= 0; i
< argc
; i
++) {
530 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
531 bool used_next_orig_arg
= false;
532 const char * const orig_arg
= argv
[i
];
533 const char * const next_orig_arg
=
534 i
< argc
- 1 ? argv
[i
+ 1] : NULL
;
536 if (orig_arg
[0] != '-') {
537 /* Non-option argument */
538 struct argpar_item_non_opt
*non_opt_item
=
539 create_non_opt_item(orig_arg
, i
, non_opt_index
);
547 if (!push_item(parse_ret
.items
, &non_opt_item
->base
)) {
554 /* Option argument */
555 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
556 next_orig_arg
, descrs
, &parse_ret
, &used_next_orig_arg
);
557 switch (parse_orig_arg_opt_ret
) {
558 case PARSE_ORIG_ARG_OPT_RET_OK
:
560 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
561 ARGPAR_ASSERT(!used_next_orig_arg
);
563 if (fail_on_unknown_opt
) {
564 prepend_while_parsing_arg_to_error(
565 &parse_ret
.error
, i
, orig_arg
);
570 * The current original argument is not
571 * considered ingested because it triggered an
574 parse_ret
.ingested_orig_args
= i
;
575 free(parse_ret
.error
);
576 parse_ret
.error
= NULL
;
578 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
579 prepend_while_parsing_arg_to_error(
580 &parse_ret
.error
, i
, orig_arg
);
586 if (used_next_orig_arg
) {
591 parse_ret
.ingested_orig_args
= argc
;
592 free(parse_ret
.error
);
593 parse_ret
.error
= NULL
;
597 /* That's how we indicate that an error occurred */
598 destroy_item_array(parse_ret
.items
);
599 parse_ret
.items
= NULL
;
606 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
610 destroy_item_array(ret
->items
);