X-Git-Url: http://git.efficios.com/?p=argpar.git;a=blobdiff_plain;f=argpar%2Fargpar.c;h=9e3ab7625c6f5d6f51e05d98b7d872ddcbea9da8;hp=6a9e256e02854f32e9df08574f6f0600e8132e0e;hb=fc07e5264804963a3de6d1b929a9eb4fd37e49d4;hpb=7ac57709d4964e2c59c9b0592632c374056caa00 diff --git a/argpar/argpar.c b/argpar/argpar.c index 6a9e256..9e3ab76 100644 --- a/argpar/argpar.c +++ b/argpar/argpar.c @@ -1,23 +1,8 @@ /* - * Copyright 2019 Philippe Proulx + * SPDX-License-Identifier: MIT * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Copyright (c) 2019-2021 Philippe Proulx + * Copyright (c) 2020-2021 Simon Marchi */ #include @@ -35,7 +20,46 @@ #define ARGPAR_ASSERT(_cond) assert(_cond) -static +#ifdef __MINGW_PRINTF_FORMAT +# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#else +# define ARGPAR_PRINTF_FORMAT printf +#endif + +/* + * An argpar iterator. + * + * Such a structure contains the state of an iterator between + * calls to argpar_iter_parse_next(). + */ +struct argpar_iter { + /* + * Data provided by the user to argpar_iter_create(); immutable + * afterwards. + */ + unsigned int argc; + const char * const *argv; + const struct argpar_opt_descr *descrs; + + /* + * Index of the argument to process in the next + * argpar_iter_parse_next() call. + */ + unsigned int i; + + /* Counter of non-option arguments */ + int non_opt_index; + + /* + * Current character of the current short option group: if it's + * not `NULL`, the parser is in within a short option group, + * therefore it must resume there in the next + * argpar_iter_parse_next() call. + */ + const char *short_opt_ch; +}; + +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0))) char *argpar_vasprintf(const char *fmt, va_list args) { int len1, len2; @@ -60,16 +84,17 @@ char *argpar_vasprintf(const char *fmt, va_list args) ARGPAR_ASSERT(len1 == len2); end: + va_end(args2); return str; } -static +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2))) char *argpar_asprintf(const char *fmt, ...) { va_list args; char *str; - + va_start(args, fmt); str = argpar_vasprintf(fmt, args); va_end(args); @@ -77,7 +102,7 @@ char *argpar_asprintf(const char *fmt, ...) return str; } -static +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3))) bool argpar_string_append_printf(char **str, const char *fmt, ...) { char *new_str = NULL; @@ -101,7 +126,7 @@ bool argpar_string_append_printf(char **str, const char *fmt, ...) success = false; goto end; } - + free(*str); *str = new_str; @@ -113,28 +138,29 @@ end: return success; } -static -void destroy_item(struct bt_argpar_item * const item) +ARGPAR_HIDDEN +void argpar_item_destroy(const struct argpar_item * const item) { if (!item) { goto end; } - if (item->type == BT_ARGPAR_ITEM_TYPE_OPT) { - struct bt_argpar_item_opt * const opt_item = (void *) item; + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt * const opt_item = + (struct argpar_item_opt *) item; free((void *) opt_item->arg); } - free(item); + free((void *) item); end: return; } static -bool push_item(struct bt_argpar_item_array * const array, - struct bt_argpar_item * const item) +bool push_item(struct argpar_item_array * const array, + struct argpar_item * const item) { bool success; @@ -143,10 +169,10 @@ bool push_item(struct bt_argpar_item_array * const array, if (array->n_items == array->n_alloc) { unsigned int new_n_alloc = array->n_alloc * 2; - struct bt_argpar_item **new_items; + struct argpar_item **new_items; - new_items = argpar_realloc(array->items, - struct bt_argpar_item *, new_n_alloc); + new_items = argpar_realloc(array->items, + struct argpar_item *, new_n_alloc); if (!new_items) { success = false; goto end; @@ -166,13 +192,13 @@ end: } static -void destroy_item_array(struct bt_argpar_item_array * const array) +void destroy_item_array(struct argpar_item_array * const array) { if (array) { unsigned int i; for (i = 0; i < array->n_items; i++) { - destroy_item(array->items[i]); + argpar_item_destroy(array->items[i]); } free(array->items); @@ -181,17 +207,17 @@ void destroy_item_array(struct bt_argpar_item_array * const array) } static -struct bt_argpar_item_array *new_item_array(void) +struct argpar_item_array *new_item_array(void) { - struct bt_argpar_item_array *ret; + struct argpar_item_array *ret; const int initial_size = 10; - ret = argpar_zalloc(struct bt_argpar_item_array); + ret = argpar_zalloc(struct argpar_item_array); if (!ret) { goto end; } - ret->items = argpar_calloc(struct bt_argpar_item *, initial_size); + ret->items = argpar_calloc(struct argpar_item *, initial_size); if (!ret->items) { goto error; } @@ -209,18 +235,18 @@ end: } static -struct bt_argpar_item_opt *create_opt_item( - const struct bt_argpar_opt_descr * const descr, +struct argpar_item_opt *create_opt_item( + const struct argpar_opt_descr * const descr, const char * const arg) { - struct bt_argpar_item_opt *opt_item = - argpar_zalloc(struct bt_argpar_item_opt); + struct argpar_item_opt *opt_item = + argpar_zalloc(struct argpar_item_opt); if (!opt_item) { goto end; } - opt_item->base.type = BT_ARGPAR_ITEM_TYPE_OPT; + opt_item->base.type = ARGPAR_ITEM_TYPE_OPT; opt_item->descr = descr; if (arg) { @@ -233,7 +259,7 @@ struct bt_argpar_item_opt *create_opt_item( goto end; error: - destroy_item(&opt_item->base); + argpar_item_destroy(&opt_item->base); opt_item = NULL; end: @@ -241,18 +267,18 @@ end: } static -struct bt_argpar_item_non_opt *create_non_opt_item(const char * const arg, +struct argpar_item_non_opt *create_non_opt_item(const char * const arg, const unsigned int orig_index, const unsigned int non_opt_index) { - struct bt_argpar_item_non_opt * const non_opt_item = - argpar_zalloc(struct bt_argpar_item_non_opt); + struct argpar_item_non_opt * const non_opt_item = + argpar_zalloc(struct argpar_item_non_opt); if (!non_opt_item) { goto end; } - non_opt_item->base.type = BT_ARGPAR_ITEM_TYPE_NON_OPT; + non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT; non_opt_item->arg = arg; non_opt_item->orig_index = orig_index; non_opt_item->non_opt_index = non_opt_index; @@ -262,11 +288,11 @@ end: } static -const struct bt_argpar_opt_descr *find_descr( - const struct bt_argpar_opt_descr * const descrs, +const struct argpar_opt_descr *find_descr( + const struct argpar_opt_descr * const descrs, const char short_name, const char * const long_name) { - const struct bt_argpar_opt_descr *descr; + const struct argpar_opt_descr *descr; for (descr = descrs; descr->short_name || descr->long_name; descr++) { if (short_name && descr->short_name && @@ -293,73 +319,75 @@ enum parse_orig_arg_opt_ret { static enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, const char * const next_orig_arg, - const struct bt_argpar_opt_descr * const descrs, - struct bt_argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + const struct argpar_opt_descr * const descrs, + struct argpar_iter * const iter, + char ** const error, struct argpar_item ** const item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - const char *short_opt_ch = short_opts; + bool used_next_orig_arg = false; + const char *opt_arg = NULL; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; if (strlen(short_opts) == 0) { - argpar_string_append_printf(&parse_ret->error, "Invalid argument"); + argpar_string_append_printf(error, "Invalid argument"); goto error; } - while (*short_opt_ch) { - const char *opt_arg = NULL; - const struct bt_argpar_opt_descr *descr; - struct bt_argpar_item_opt *opt_item; - - /* Find corresponding option descriptor */ - descr = find_descr(descrs, *short_opt_ch, NULL); - if (!descr) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; - argpar_string_append_printf(&parse_ret->error, - "Unknown option `-%c`", *short_opt_ch); - goto error; - } + if (!iter->short_opt_ch) { + iter->short_opt_ch = short_opts; + } - if (descr->with_arg) { - if (short_opt_ch[1]) { - /* `-oarg` form */ - opt_arg = &short_opt_ch[1]; - } else { - /* `-o arg` form */ - opt_arg = next_orig_arg; - *used_next_orig_arg = true; - } + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *iter->short_opt_ch, NULL); + if (!descr) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + argpar_string_append_printf(error, + "Unknown option `-%c`", *iter->short_opt_ch); + goto error; + } - /* - * We accept `-o ''` (empty option's argument), - * but not `-o` alone if an option's argument is - * expected. - */ - if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) { - argpar_string_append_printf(&parse_ret->error, - "Missing required argument for option `-%c`", - *short_opt_ch); - *used_next_orig_arg = false; - goto error; - } + if (descr->with_arg) { + if (iter->short_opt_ch[1]) { + /* `-oarg` form */ + opt_arg = &iter->short_opt_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + used_next_orig_arg = true; } - /* Create and append option argument */ - opt_item = create_opt_item(descr, opt_arg); - if (!opt_item) { + /* + * We accept `-o ''` (empty option argument), but not + * `-o` alone if an option argument is expected. + */ + if (!opt_arg || (iter->short_opt_ch[1] && strlen(opt_arg) == 0)) { + argpar_string_append_printf(error, + "Missing required argument for option `-%c`", + *iter->short_opt_ch); + used_next_orig_arg = false; goto error; } + } - if (!push_item(parse_ret->items, &opt_item->base)) { - goto error; - } + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } - if (descr->with_arg) { - /* Option has an argument: no more options */ - break; - } + *item = &opt_item->base; + iter->short_opt_ch++; + + if (descr->with_arg || !*iter->short_opt_ch) { + /* Option has an argument: no more options */ + iter->short_opt_ch = NULL; - /* Go to next short option */ - short_opt_ch++; + if (used_next_orig_arg) { + iter->i += 2; + } else { + iter->i++; + } } goto end; @@ -376,14 +404,15 @@ end: static enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const char * const next_orig_arg, - const struct bt_argpar_opt_descr * const descrs, - struct bt_argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + const struct argpar_opt_descr * const descrs, + struct argpar_iter * const iter, + char ** const error, struct argpar_item ** const item) { const size_t max_len = 127; enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - const struct bt_argpar_opt_descr *descr; - struct bt_argpar_item_opt *opt_item; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; + bool used_next_orig_arg = false; /* Option's argument, if any */ const char *opt_arg = NULL; @@ -398,8 +427,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const char *long_opt_name = long_opt_arg; if (strlen(long_opt_arg) == 0) { - argpar_string_append_printf(&parse_ret->error, - "Invalid argument"); + argpar_string_append_printf(error, "Invalid argument"); goto error; } @@ -410,7 +438,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Isolate the option name */ if (long_opt_name_size > max_len) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Invalid argument `--%s`", long_opt_arg); goto error; } @@ -423,7 +451,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Find corresponding option descriptor */ descr = find_descr(descrs, '\0', long_opt_name); if (!descr) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Unknown option `--%s`", long_opt_name); ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; goto error; @@ -437,15 +465,23 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, } else { /* `--long-opt arg` style */ if (!next_orig_arg) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Missing required argument for option `--%s`", long_opt_name); goto error; } opt_arg = next_orig_arg; - *used_next_orig_arg = true; + used_next_orig_arg = true; } + } else if (eq_pos) { + /* + * Unexpected `--opt=arg` style for a long option which + * doesn't accept an argument. + */ + argpar_string_append_printf(error, + "Unexpected argument for option `--%s`", long_opt_name); + goto error; } /* Create and append option argument */ @@ -454,10 +490,13 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, goto error; } - if (!push_item(parse_ret->items, &opt_item->base)) { - goto error; + if (used_next_orig_arg) { + iter->i += 2; + } else { + iter->i++; } + *item = &opt_item->base; goto end; error: @@ -472,9 +511,10 @@ end: static enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, const char * const next_orig_arg, - const struct bt_argpar_opt_descr * const descrs, - struct bt_argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + const struct argpar_opt_descr * const descrs, + struct argpar_iter * const iter, + char ** const error, + struct argpar_item ** const item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; @@ -483,13 +523,11 @@ enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, if (orig_arg[1] == '-') { /* Long option */ ret = parse_long_opt(&orig_arg[2], - next_orig_arg, descrs, parse_ret, - used_next_orig_arg); + next_orig_arg, descrs, iter, error, item); } else { /* Short option */ ret = parse_short_opts(&orig_arg[1], - next_orig_arg, descrs, parse_ret, - used_next_orig_arg); + next_orig_arg, descrs, iter, error, item); } return ret; @@ -521,98 +559,175 @@ end: } ARGPAR_HIDDEN -struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, - const char * const *argv, - const struct bt_argpar_opt_descr * const descrs, - bool fail_on_unknown_opt) +struct argpar_iter *argpar_iter_create(const unsigned int argc, + const char * const * const argv, + const struct argpar_opt_descr * const descrs) { - struct bt_argpar_parse_ret parse_ret = { 0 }; - unsigned int i; - unsigned int non_opt_index = 0; + struct argpar_iter * const iter = argpar_zalloc(struct argpar_iter); - parse_ret.items = new_item_array(); - if (!parse_ret.items) { - goto error; + if (!iter) { + goto end; } - for (i = 0; i < argc; i++) { - enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; - bool used_next_orig_arg = false; - const char * const orig_arg = argv[i]; - const char * const next_orig_arg = - i < argc - 1 ? argv[i + 1] : NULL; + iter->argc = argc; + iter->argv = argv; + iter->descrs = descrs; - if (orig_arg[0] != '-') { - /* Non-option argument */ - struct bt_argpar_item_non_opt *non_opt_item = - create_non_opt_item(orig_arg, i, non_opt_index); +end: + return iter; +} - if (!non_opt_item) { - goto error; - } +ARGPAR_HIDDEN +void argpar_iter_destroy(struct argpar_iter * const iter) +{ + free(iter); +} - non_opt_index++; +ARGPAR_HIDDEN +enum argpar_iter_parse_next_status argpar_iter_parse_next( + struct argpar_iter * const iter, + const struct argpar_item ** const item, + char ** const error) +{ + enum argpar_iter_parse_next_status status; + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + const char *orig_arg; + const char *next_orig_arg; - if (!push_item(parse_ret.items, &non_opt_item->base)) { - goto error; - } + ARGPAR_ASSERT(iter->i <= iter->argc); + *error = NULL; - continue; + if (iter->i == iter->argc) { + status = ARGPAR_ITER_PARSE_NEXT_STATUS_END; + goto end; + } + + orig_arg = iter->argv[iter->i]; + next_orig_arg = + iter->i < (iter->argc - 1) ? iter->argv[iter->i + 1] : NULL; + + if (orig_arg[0] != '-') { + /* Non-option argument */ + struct argpar_item_non_opt * const non_opt_item = + create_non_opt_item(orig_arg, iter->i, + iter->non_opt_index); + + if (!non_opt_item) { + status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR; + goto end; } - /* Option argument */ - parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, - next_orig_arg, descrs, &parse_ret, &used_next_orig_arg); - switch (parse_orig_arg_opt_ret) { - case PARSE_ORIG_ARG_OPT_RET_OK: - break; - case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: - ARGPAR_ASSERT(!used_next_orig_arg); + iter->non_opt_index++; + iter->i++; + *item = &non_opt_item->base; + status = ARGPAR_ITER_PARSE_NEXT_STATUS_OK; + goto end; + } + + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, + next_orig_arg, iter->descrs, iter, error, + (struct argpar_item **) item); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + status = ARGPAR_ITER_PARSE_NEXT_STATUS_OK; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: + prepend_while_parsing_arg_to_error(error, iter->i, orig_arg); + status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + prepend_while_parsing_arg_to_error(error, iter->i, orig_arg); + status = ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR; + break; + default: + abort(); + } + +end: + return status; +} +ARGPAR_HIDDEN +unsigned int argpar_iter_get_ingested_orig_args( + const struct argpar_iter * const iter) +{ + return iter->i; +} + +ARGPAR_HIDDEN +struct argpar_parse_ret argpar_parse(const unsigned int argc, + const char * const * const argv, + const struct argpar_opt_descr * const descrs, + const bool fail_on_unknown_opt) +{ + struct argpar_parse_ret parse_ret = { 0 }; + const struct argpar_item *item = NULL; + struct argpar_iter *iter = NULL; + + parse_ret.items = new_item_array(); + if (!parse_ret.items) { + parse_ret.error = strdup("Failed to create items array."); + ARGPAR_ASSERT(parse_ret.error); + goto error; + } + + iter = argpar_iter_create(argc, argv, descrs); + if (!iter) { + parse_ret.error = strdup("Failed to create argpar iter."); + ARGPAR_ASSERT(parse_ret.error); + goto error; + } + + while (true) { + enum argpar_iter_parse_next_status status; + + status = argpar_iter_parse_next(iter, &item, &parse_ret.error); + if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR) { + goto error; + } else if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_END) { + break; + } else if (status == ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { if (fail_on_unknown_opt) { - prepend_while_parsing_arg_to_error( - &parse_ret.error, i, orig_arg); + parse_ret.ingested_orig_args = + argpar_iter_get_ingested_orig_args(iter); goto error; } - /* - * The current original argument is not - * considered ingested because it triggered an - * unknown option. - */ - parse_ret.ingested_orig_args = i; free(parse_ret.error); parse_ret.error = NULL; - goto end; - case PARSE_ORIG_ARG_OPT_RET_ERROR: - prepend_while_parsing_arg_to_error( - &parse_ret.error, i, orig_arg); - goto error; - default: - abort(); + break; } - if (used_next_orig_arg) { - i++; + ARGPAR_ASSERT(status == ARGPAR_ITER_PARSE_NEXT_STATUS_OK); + + if (!push_item(parse_ret.items, (void *) item)) { + goto error; } + + item = NULL; } - parse_ret.ingested_orig_args = argc; - free(parse_ret.error); - parse_ret.error = NULL; + ARGPAR_ASSERT(!parse_ret.error); + parse_ret.ingested_orig_args = + argpar_iter_get_ingested_orig_args(iter); goto end; error: - /* That's how we indicate that an error occured */ + ARGPAR_ASSERT(parse_ret.error); + + /* That's how we indicate that an error occurred */ destroy_item_array(parse_ret.items); parse_ret.items = NULL; end: + argpar_iter_destroy(iter); + argpar_item_destroy(item); return parse_ret; } ARGPAR_HIDDEN -void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret) +void argpar_parse_ret_fini(struct argpar_parse_ret *ret) { ARGPAR_ASSERT(ret);