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)
38 #ifdef __MINGW_PRINTF_FORMAT
39 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
41 # define ARGPAR_PRINTF_FORMAT printf
44 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 0)))
45 char *argpar_vasprintf(const char *fmt
, va_list args
)
53 len1
= vsnprintf(NULL
, 0, fmt
, args
);
59 str
= malloc(len1
+ 1);
64 len2
= vsnprintf(str
, len1
+ 1, fmt
, args2
);
66 ARGPAR_ASSERT(len1
== len2
);
74 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 1, 2)))
75 char *argpar_asprintf(const char *fmt
, ...)
81 str
= argpar_vasprintf(fmt
, args
);
87 static __attribute__((format(ARGPAR_PRINTF_FORMAT
, 2, 3)))
88 bool argpar_string_append_printf(char **str
, const char *fmt
, ...)
98 addendum
= argpar_vasprintf(fmt
, args
);
106 new_str
= argpar_asprintf("%s%s", *str
? *str
: "", addendum
);
124 void destroy_item(struct argpar_item
* const item
)
130 if (item
->type
== ARGPAR_ITEM_TYPE_OPT
) {
131 struct argpar_item_opt
* const opt_item
= (void *) item
;
133 free((void *) opt_item
->arg
);
143 bool push_item(struct argpar_item_array
* const array
,
144 struct argpar_item
* const item
)
148 ARGPAR_ASSERT(array
);
151 if (array
->n_items
== array
->n_alloc
) {
152 unsigned int new_n_alloc
= array
->n_alloc
* 2;
153 struct argpar_item
**new_items
;
155 new_items
= argpar_realloc(array
->items
,
156 struct argpar_item
*, new_n_alloc
);
162 array
->n_alloc
= new_n_alloc
;
163 array
->items
= new_items
;
166 array
->items
[array
->n_items
] = item
;
176 void destroy_item_array(struct argpar_item_array
* const array
)
181 for (i
= 0; i
< array
->n_items
; i
++) {
182 destroy_item(array
->items
[i
]);
191 struct argpar_item_array
*new_item_array(void)
193 struct argpar_item_array
*ret
;
194 const int initial_size
= 10;
196 ret
= argpar_zalloc(struct argpar_item_array
);
201 ret
->items
= argpar_calloc(struct argpar_item
*, initial_size
);
206 ret
->n_alloc
= initial_size
;
211 destroy_item_array(ret
);
219 struct argpar_item_opt
*create_opt_item(
220 const struct argpar_opt_descr
* const descr
,
221 const char * const arg
)
223 struct argpar_item_opt
*opt_item
=
224 argpar_zalloc(struct argpar_item_opt
);
230 opt_item
->base
.type
= ARGPAR_ITEM_TYPE_OPT
;
231 opt_item
->descr
= descr
;
234 opt_item
->arg
= strdup(arg
);
235 if (!opt_item
->arg
) {
243 destroy_item(&opt_item
->base
);
251 struct argpar_item_non_opt
*create_non_opt_item(const char * const arg
,
252 const unsigned int orig_index
,
253 const unsigned int non_opt_index
)
255 struct argpar_item_non_opt
* const non_opt_item
=
256 argpar_zalloc(struct argpar_item_non_opt
);
262 non_opt_item
->base
.type
= ARGPAR_ITEM_TYPE_NON_OPT
;
263 non_opt_item
->arg
= arg
;
264 non_opt_item
->orig_index
= orig_index
;
265 non_opt_item
->non_opt_index
= non_opt_index
;
272 const struct argpar_opt_descr
*find_descr(
273 const struct argpar_opt_descr
* const descrs
,
274 const char short_name
, const char * const long_name
)
276 const struct argpar_opt_descr
*descr
;
278 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
279 if (short_name
&& descr
->short_name
&&
280 short_name
== descr
->short_name
) {
284 if (long_name
&& descr
->long_name
&&
285 strcmp(long_name
, descr
->long_name
) == 0) {
291 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
294 enum parse_orig_arg_opt_ret
{
295 PARSE_ORIG_ARG_OPT_RET_OK
,
296 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
= -2,
297 PARSE_ORIG_ARG_OPT_RET_ERROR
= -1,
301 enum parse_orig_arg_opt_ret
parse_short_opts(const char * const short_opts
,
302 const char * const next_orig_arg
,
303 const struct argpar_opt_descr
* const descrs
,
304 struct argpar_parse_ret
* const parse_ret
,
305 bool * const used_next_orig_arg
)
307 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
308 const char *short_opt_ch
= short_opts
;
310 if (strlen(short_opts
) == 0) {
311 argpar_string_append_printf(&parse_ret
->error
, "Invalid argument");
315 while (*short_opt_ch
) {
316 const char *opt_arg
= NULL
;
317 const struct argpar_opt_descr
*descr
;
318 struct argpar_item_opt
*opt_item
;
320 /* Find corresponding option descriptor */
321 descr
= find_descr(descrs
, *short_opt_ch
, NULL
);
323 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
324 argpar_string_append_printf(&parse_ret
->error
,
325 "Unknown option `-%c`", *short_opt_ch
);
329 if (descr
->with_arg
) {
330 if (short_opt_ch
[1]) {
332 opt_arg
= &short_opt_ch
[1];
335 opt_arg
= next_orig_arg
;
336 *used_next_orig_arg
= true;
340 * We accept `-o ''` (empty option's argument),
341 * but not `-o` alone if an option's argument is
344 if (!opt_arg
|| (short_opt_ch
[1] && strlen(opt_arg
) == 0)) {
345 argpar_string_append_printf(&parse_ret
->error
,
346 "Missing required argument for option `-%c`",
348 *used_next_orig_arg
= false;
353 /* Create and append option argument */
354 opt_item
= create_opt_item(descr
, opt_arg
);
359 if (!push_item(parse_ret
->items
, &opt_item
->base
)) {
363 if (descr
->with_arg
) {
364 /* Option has an argument: no more options */
368 /* Go to next short option */
375 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
376 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
384 enum parse_orig_arg_opt_ret
parse_long_opt(const char * const long_opt_arg
,
385 const char * const next_orig_arg
,
386 const struct argpar_opt_descr
* const descrs
,
387 struct argpar_parse_ret
* const parse_ret
,
388 bool * const used_next_orig_arg
)
390 const size_t max_len
= 127;
391 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
392 const struct argpar_opt_descr
*descr
;
393 struct argpar_item_opt
*opt_item
;
395 /* Option's argument, if any */
396 const char *opt_arg
= NULL
;
398 /* Position of first `=`, if any */
401 /* Buffer holding option name when `long_opt_arg` contains `=` */
402 char buf
[max_len
+ 1];
405 const char *long_opt_name
= long_opt_arg
;
407 if (strlen(long_opt_arg
) == 0) {
408 argpar_string_append_printf(&parse_ret
->error
,
413 /* Find the first `=` in original argument */
414 eq_pos
= strchr(long_opt_arg
, '=');
416 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
418 /* Isolate the option name */
419 if (long_opt_name_size
> max_len
) {
420 argpar_string_append_printf(&parse_ret
->error
,
421 "Invalid argument `--%s`", long_opt_arg
);
425 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
426 buf
[long_opt_name_size
] = '\0';
430 /* Find corresponding option descriptor */
431 descr
= find_descr(descrs
, '\0', long_opt_name
);
433 argpar_string_append_printf(&parse_ret
->error
,
434 "Unknown option `--%s`", long_opt_name
);
435 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
;
439 /* Find option's argument if any */
440 if (descr
->with_arg
) {
442 /* `--long-opt=arg` style */
443 opt_arg
= eq_pos
+ 1;
445 /* `--long-opt arg` style */
446 if (!next_orig_arg
) {
447 argpar_string_append_printf(&parse_ret
->error
,
448 "Missing required argument for option `--%s`",
453 opt_arg
= next_orig_arg
;
454 *used_next_orig_arg
= true;
458 /* Create and append option argument */
459 opt_item
= create_opt_item(descr
, opt_arg
);
464 if (!push_item(parse_ret
->items
, &opt_item
->base
)) {
471 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
472 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
480 enum parse_orig_arg_opt_ret
parse_orig_arg_opt(const char * const orig_arg
,
481 const char * const next_orig_arg
,
482 const struct argpar_opt_descr
* const descrs
,
483 struct argpar_parse_ret
* const parse_ret
,
484 bool * const used_next_orig_arg
)
486 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
488 ARGPAR_ASSERT(orig_arg
[0] == '-');
490 if (orig_arg
[1] == '-') {
492 ret
= parse_long_opt(&orig_arg
[2],
493 next_orig_arg
, descrs
, parse_ret
,
497 ret
= parse_short_opts(&orig_arg
[1],
498 next_orig_arg
, descrs
, parse_ret
,
506 bool prepend_while_parsing_arg_to_error(char **error
,
507 const unsigned int i
, const char * const arg
)
512 ARGPAR_ASSERT(error
);
513 ARGPAR_ASSERT(*error
);
515 new_error
= argpar_asprintf("While parsing argument #%u (`%s`): %s",
531 struct argpar_parse_ret
argpar_parse(unsigned int argc
,
532 const char * const *argv
,
533 const struct argpar_opt_descr
* const descrs
,
534 bool fail_on_unknown_opt
)
536 struct argpar_parse_ret parse_ret
= { 0 };
538 unsigned int non_opt_index
= 0;
540 parse_ret
.items
= new_item_array();
541 if (!parse_ret
.items
) {
545 for (i
= 0; i
< argc
; i
++) {
546 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret
;
547 bool used_next_orig_arg
= false;
548 const char * const orig_arg
= argv
[i
];
549 const char * const next_orig_arg
=
550 i
< argc
- 1 ? argv
[i
+ 1] : NULL
;
552 if (orig_arg
[0] != '-') {
553 /* Non-option argument */
554 struct argpar_item_non_opt
*non_opt_item
=
555 create_non_opt_item(orig_arg
, i
, non_opt_index
);
563 if (!push_item(parse_ret
.items
, &non_opt_item
->base
)) {
570 /* Option argument */
571 parse_orig_arg_opt_ret
= parse_orig_arg_opt(orig_arg
,
572 next_orig_arg
, descrs
, &parse_ret
, &used_next_orig_arg
);
573 switch (parse_orig_arg_opt_ret
) {
574 case PARSE_ORIG_ARG_OPT_RET_OK
:
576 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
577 ARGPAR_ASSERT(!used_next_orig_arg
);
579 if (fail_on_unknown_opt
) {
580 prepend_while_parsing_arg_to_error(
581 &parse_ret
.error
, i
, orig_arg
);
586 * The current original argument is not
587 * considered ingested because it triggered an
590 parse_ret
.ingested_orig_args
= i
;
591 free(parse_ret
.error
);
592 parse_ret
.error
= NULL
;
594 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
595 prepend_while_parsing_arg_to_error(
596 &parse_ret
.error
, i
, orig_arg
);
602 if (used_next_orig_arg
) {
607 parse_ret
.ingested_orig_args
= argc
;
608 free(parse_ret
.error
);
609 parse_ret
.error
= NULL
;
613 /* That's how we indicate that an error occurred */
614 destroy_item_array(parse_ret
.items
);
615 parse_ret
.items
= NULL
;
622 void argpar_parse_ret_fini(struct argpar_parse_ret
*ret
)
626 destroy_item_array(ret
->items
);