X-Git-Url: http://git.efficios.com/?p=argpar.git;a=blobdiff_plain;f=argpar%2Fargpar.c;h=82b561ceaae707002f36754c55e828d35b882206;hp=13f66812894037b669924eb4446fd3f5472e464b;hb=143cec42e14e050571be640cb2dfa5c5e0198d59;hpb=1ae22b5e8335dfe14b805404a16b90817f1a99e1 diff --git a/argpar/argpar.c b/argpar/argpar.c index 13f6681..82b561c 100644 --- a/argpar/argpar.c +++ b/argpar/argpar.c @@ -1,26 +1,10 @@ /* - * 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 #include #include #include @@ -29,199 +13,203 @@ #include "argpar.h" -#define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) -#define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type))) -#define argpar_zalloc(_type) argpar_calloc(_type, 1) +#define ARGPAR_REALLOC(_ptr, _type, _nmemb) \ + ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) + +#define ARGPAR_CALLOC(_type, _nmemb) \ + ((_type *) calloc((_nmemb), sizeof(_type))) -#define ARGPAR_ASSERT(_cond) assert(_cond) +#define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1) -#ifdef __MINGW_PRINTF_FORMAT -# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#ifdef NDEBUG +/* + * Force usage of the assertion condition to prevent unused variable warnings + * when `assert()` are disabled by the `NDEBUG` definition. + */ +# define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0)) #else -# define ARGPAR_PRINTF_FORMAT printf +# include +# define ARGPAR_ASSERT(_cond) assert(_cond) #endif -static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0))) -char *argpar_vasprintf(const char *fmt, va_list args) -{ - int len1, len2; - char *str; - va_list args2; - - va_copy(args2, args); - - len1 = vsnprintf(NULL, 0, fmt, args); - if (len1 < 0) { - str = NULL; - goto end; - } - - str = malloc(len1 + 1); - if (!str) { - goto end; - } - - len2 = vsnprintf(str, len1 + 1, fmt, args2); +/* + * An argpar iterator. + * + * Such a structure contains the state of an iterator between calls to + * argpar_iter_next(). + */ +struct argpar_iter { + /* + * Data provided by the user to argpar_iter_create(); immutable + * afterwards. + */ + struct { + unsigned int argc; + const char * const *argv; + const struct argpar_opt_descr *descrs; + } user; + + /* + * Index of the argument to process in the next + * argpar_iter_next() call. + */ + unsigned int i; - ARGPAR_ASSERT(len1 == len2); + /* Counter of non-option arguments */ + int non_opt_index; + + /* + * Current character within the current short option group: if + * it's not `NULL`, the parser is within a short option group, + * therefore it must resume there in the next argpar_iter_next() + * call. + */ + const char *short_opt_group_ch; + + /* Temporary character buffer which only grows */ + struct { + size_t size; + char *data; + } tmp_buf; +}; -end: - va_end(args2); - return str; -} +/* Base parsing item */ +struct argpar_item { + enum argpar_item_type type; +}; +/* Option parsing item */ +struct argpar_item_opt { + struct argpar_item base; -static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2))) -char *argpar_asprintf(const char *fmt, ...) -{ - va_list args; - char *str; + /* Corresponding descriptor */ + const struct argpar_opt_descr *descr; - va_start(args, fmt); - str = argpar_vasprintf(fmt, args); - va_end(args); + /* Argument, or `NULL` if none; owned by this */ + char *arg; +}; - return str; -} +/* Non-option parsing item */ +struct argpar_item_non_opt { + struct argpar_item base; -static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3))) -bool argpar_string_append_printf(char **str, const char *fmt, ...) -{ - char *new_str = NULL; - char *addendum; - bool success; - va_list args; + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; - ARGPAR_ASSERT(str); + /* + * Index of this argument amongst all original arguments + * (`argv`). + */ + unsigned int orig_index; - va_start(args, fmt); - addendum = argpar_vasprintf(fmt, args); - va_end(args); + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +}; - if (!addendum) { - success = false; - goto end; - } +/* Parsing error */ +struct argpar_error { + /* Error type */ + enum argpar_error_type type; - new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum); - if (!new_str) { - success = false; - goto end; - } + /* Original argument index */ + unsigned int orig_index; - free(*str); - *str = new_str; + /* Name of unknown option; owned by this */ + char *unknown_opt_name; - success = true; + /* Option descriptor */ + const struct argpar_opt_descr *opt_descr; -end: - free(addendum); + /* `true` if a short option caused the error */ + bool is_short; +}; - return success; +ARGPAR_HIDDEN +enum argpar_item_type argpar_item_type(const struct argpar_item * const item) +{ + ARGPAR_ASSERT(item); + return item->type; } -static -void destroy_item(struct argpar_item * const item) +ARGPAR_HIDDEN +const struct argpar_opt_descr *argpar_item_opt_descr( + const struct argpar_item * const item) { - if (!item) { - goto end; - } - - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - struct argpar_item_opt * const opt_item = (void *) item; - - free((void *) opt_item->arg); - } - - free(item); - -end: - return; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const struct argpar_item_opt *) item)->descr; } -static -bool push_item(struct argpar_item_array * const array, - struct argpar_item * const item) +ARGPAR_HIDDEN +const char *argpar_item_opt_arg(const struct argpar_item * const item) { - bool success; - - ARGPAR_ASSERT(array); ARGPAR_ASSERT(item); - - if (array->n_items == array->n_alloc) { - unsigned int new_n_alloc = array->n_alloc * 2; - struct argpar_item **new_items; - - new_items = argpar_realloc(array->items, - struct argpar_item *, new_n_alloc); - if (!new_items) { - success = false; - goto end; - } - - array->n_alloc = new_n_alloc; - array->items = new_items; - } - - array->items[array->n_items] = item; - array->n_items++; - - success = true; - -end: - return success; + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const struct argpar_item_opt *) item)->arg; } -static -void destroy_item_array(struct argpar_item_array * const array) +ARGPAR_HIDDEN +const char *argpar_item_non_opt_arg(const struct argpar_item * const item) { - if (array) { - unsigned int i; - - for (i = 0; i < array->n_items; i++) { - destroy_item(array->items[i]); - } + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->arg; +} - free(array->items); - free(array); - } +ARGPAR_HIDDEN +unsigned int argpar_item_non_opt_orig_index( + const struct argpar_item * const item) +{ + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->orig_index; } -static -struct argpar_item_array *new_item_array(void) +ARGPAR_HIDDEN +unsigned int argpar_item_non_opt_non_opt_index( + const struct argpar_item * const item) { - struct argpar_item_array *ret; - const int initial_size = 10; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->non_opt_index; +} - ret = argpar_zalloc(struct argpar_item_array); - if (!ret) { +ARGPAR_HIDDEN +void argpar_item_destroy(const struct argpar_item * const item) +{ + if (!item) { goto end; } - ret->items = argpar_calloc(struct argpar_item *, initial_size); - if (!ret->items) { - goto error; - } - - ret->n_alloc = initial_size; + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt * const opt_item = + (struct argpar_item_opt *) item; - goto end; + free(opt_item->arg); + } -error: - destroy_item_array(ret); - ret = NULL; + free((void *) item); end: - return ret; + return; } +/* + * Creates and returns an option parsing item for the descriptor `descr` + * and having the argument `arg` (copied; may be `NULL`). + * + * Returns `NULL` on memory error. + */ static struct argpar_item_opt *create_opt_item( const struct argpar_opt_descr * const descr, const char * const arg) { struct argpar_item_opt *opt_item = - argpar_zalloc(struct argpar_item_opt); + ARGPAR_ZALLOC(struct argpar_item_opt); if (!opt_item) { goto end; @@ -240,20 +228,27 @@ struct argpar_item_opt *create_opt_item( goto end; error: - destroy_item(&opt_item->base); + argpar_item_destroy(&opt_item->base); opt_item = NULL; end: return opt_item; } +/* + * Creates and returns a non-option parsing item for the original + * argument `arg` having the original index `orig_index` and the + * non-option index `non_opt_index`. + * + * Returns `NULL` on memory error. + */ static 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 argpar_item_non_opt * const non_opt_item = - argpar_zalloc(struct argpar_item_non_opt); + ARGPAR_ZALLOC(struct argpar_item_non_opt); if (!non_opt_item) { goto end; @@ -268,6 +263,126 @@ end: return non_opt_item; } +/* + * If `error` is not `NULL`, sets the error `error` to a new parsing + * error object, setting its `unknown_opt_name`, `opt_descr`, and + * `is_short` members from the parameters. + * + * `unknown_opt_name` is the unknown option name without any `-` or `--` + * prefix: `is_short` controls which type of unknown option it is. + * + * Returns 0 on success (including if `error` is `NULL`) or -1 on memory + * error. + */ +static +int set_error(struct argpar_error ** const error, + enum argpar_error_type type, + const char * const unknown_opt_name, + const struct argpar_opt_descr * const opt_descr, + const bool is_short) +{ + int ret = 0; + + if (!error) { + goto end; + } + + *error = ARGPAR_ZALLOC(struct argpar_error); + if (!*error) { + goto error; + } + + (*error)->type = type; + + if (unknown_opt_name) { + (*error)->unknown_opt_name = ARGPAR_CALLOC(char, + strlen(unknown_opt_name) + 1 + (is_short ? 1 : 2)); + if (!(*error)->unknown_opt_name) { + goto error; + } + + if (is_short) { + strcpy((*error)->unknown_opt_name, "-"); + } else { + strcpy((*error)->unknown_opt_name, "--"); + } + + strcat((*error)->unknown_opt_name, unknown_opt_name); + } + + (*error)->opt_descr = opt_descr; + (*error)->is_short = is_short; + goto end; + +error: + argpar_error_destroy(*error); + ret = -1; + +end: + return ret; +} + +ARGPAR_HIDDEN +enum argpar_error_type argpar_error_type( + const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + return error->type; +} + +ARGPAR_HIDDEN +unsigned int argpar_error_orig_index(const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + return error->orig_index; +} + +ARGPAR_HIDDEN +const char *argpar_error_unknown_opt_name( + const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_UNKNOWN_OPT); + ARGPAR_ASSERT(error->unknown_opt_name); + return error->unknown_opt_name; +} + +ARGPAR_HIDDEN +const struct argpar_opt_descr *argpar_error_opt_descr( + const struct argpar_error * const error, bool * const is_short) +{ + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_MISSING_OPT_ARG || + error->type == ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG); + ARGPAR_ASSERT(error->opt_descr); + + if (is_short) { + *is_short = error->is_short; + } + + return error->opt_descr; +} + +ARGPAR_HIDDEN +void argpar_error_destroy(const struct argpar_error * const error) +{ + if (error) { + free(error->unknown_opt_name); + free((void *) error); + } +} + +/* + * Finds and returns the _first_ descriptor having the short option name + * `short_name` or the long option name `long_name` within the option + * descriptors `descrs`. + * + * `short_name` may be `'\0'` to not consider it. + * + * `long_name` may be `NULL` to not consider it. + * + * Returns `NULL` if no descriptor is found. + */ static const struct argpar_opt_descr *find_descr( const struct argpar_opt_descr * const descrs, @@ -291,106 +406,136 @@ end: return !descr->short_name && !descr->long_name ? NULL : descr; } +/* Return type of parse_short_opt_group() and parse_long_opt() */ enum parse_orig_arg_opt_ret { PARSE_ORIG_ARG_OPT_RET_OK, - PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2, PARSE_ORIG_ARG_OPT_RET_ERROR = -1, + PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY = -2, }; +/* + * Parses the short option group argument `short_opt_group`, starting + * where needed depending on the state of `iter`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ static -enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, +enum parse_orig_arg_opt_ret parse_short_opt_group( + const char * const short_opt_group, const char * const next_orig_arg, const struct argpar_opt_descr * const descrs, - struct argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + struct argpar_iter * const iter, + struct argpar_error ** 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"); - goto error; + ARGPAR_ASSERT(strlen(short_opt_group) != 0); + + if (!iter->short_opt_group_ch) { + iter->short_opt_group_ch = short_opt_group; } - while (*short_opt_ch) { - const char *opt_arg = NULL; - const struct argpar_opt_descr *descr; - struct 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; - } + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *iter->short_opt_group_ch, NULL); + if (!descr) { + const char unknown_opt_name[] = + {*iter->short_opt_group_ch, '\0'}; - 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; - } + ret = PARSE_ORIG_ARG_OPT_RET_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 (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + unknown_opt_name, NULL, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; } - /* Create and append option argument */ - opt_item = create_opt_item(descr, opt_arg); - if (!opt_item) { - goto error; + goto error; + } + + if (descr->with_arg) { + if (iter->short_opt_group_ch[1]) { + /* `-oarg` form */ + opt_arg = &iter->short_opt_group_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + used_next_orig_arg = true; } - if (!push_item(parse_ret->items, &opt_item->base)) { + /* + * We accept `-o ''` (empty option argument), but not + * `-o` alone if an option argument is expected. + */ + if (!opt_arg || (iter->short_opt_group_ch[1] && + strlen(opt_arg) == 0)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + NULL, descr, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } + } - if (descr->with_arg) { - /* Option has an argument: no more options */ - break; - } + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + goto error; + } + + *item = &opt_item->base; + iter->short_opt_group_ch++; + + if (descr->with_arg || !*iter->short_opt_group_ch) { + /* Option has an argument: no more options */ + iter->short_opt_group_ch = NULL; - /* Go to next short option */ - short_opt_ch++; + if (used_next_orig_arg) { + iter->i += 2; + } else { + iter->i++; + } } goto end; error: - if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - } + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: return ret; } +/* + * Parses the long option argument `long_opt_arg`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ static enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const char * const next_orig_arg, const struct argpar_opt_descr * const descrs, - struct argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + struct argpar_iter * const iter, + struct argpar_error ** 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 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,17 +543,10 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Position of first `=`, if any */ const char *eq_pos; - /* Buffer holding option name when `long_opt_arg` contains `=` */ - char buf[max_len + 1]; - /* Option name */ const char *long_opt_name = long_opt_arg; - if (strlen(long_opt_arg) == 0) { - argpar_string_append_printf(&parse_ret->error, - "Invalid argument"); - goto error; - } + ARGPAR_ASSERT(strlen(long_opt_arg) != 0); /* Find the first `=` in original argument */ eq_pos = strchr(long_opt_arg, '='); @@ -416,23 +554,31 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const size_t long_opt_name_size = eq_pos - long_opt_arg; /* Isolate the option name */ - if (long_opt_name_size > max_len) { - argpar_string_append_printf(&parse_ret->error, - "Invalid argument `--%s`", long_opt_arg); - goto error; + while (long_opt_name_size > iter->tmp_buf.size - 1) { + iter->tmp_buf.size *= 2; + iter->tmp_buf.data = ARGPAR_REALLOC(iter->tmp_buf.data, + char, iter->tmp_buf.size); + if (!iter->tmp_buf.data) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + goto error; + } } - memcpy(buf, long_opt_arg, long_opt_name_size); - buf[long_opt_name_size] = '\0'; - long_opt_name = buf; + memcpy(iter->tmp_buf.data, long_opt_arg, long_opt_name_size); + iter->tmp_buf.data[long_opt_name_size] = '\0'; + long_opt_name = iter->tmp_buf.data; } /* Find corresponding option descriptor */ descr = find_descr(descrs, '\0', long_opt_name); if (!descr) { - argpar_string_append_printf(&parse_ret->error, - "Unknown option `--%s`", long_opt_name); - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + long_opt_name, NULL, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } @@ -444,15 +590,32 @@ 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, - "Missing required argument for option `--%s`", - long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + 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. + */ + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, + NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; } /* Create and append option argument */ @@ -461,27 +624,37 @@ 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: - if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - } + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: return ret; } +/* + * Parses the original argument `orig_arg`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ static enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, const char * const next_orig_arg, const struct argpar_opt_descr * const descrs, - struct argpar_parse_ret * const parse_ret, - bool * const used_next_orig_arg) + struct argpar_iter * const iter, + struct argpar_error ** const error, + struct argpar_item ** const item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; @@ -490,142 +663,127 @@ 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); + ret = parse_short_opt_group(&orig_arg[1], + next_orig_arg, descrs, iter, error, item); } return ret; } -static -bool prepend_while_parsing_arg_to_error(char **error, - const unsigned int i, const char * const arg) +ARGPAR_HIDDEN +struct argpar_iter *argpar_iter_create(const unsigned int argc, + const char * const * const argv, + const struct argpar_opt_descr * const descrs) { - char *new_error; - bool success; + struct argpar_iter *iter = ARGPAR_ZALLOC(struct argpar_iter); - ARGPAR_ASSERT(error); - ARGPAR_ASSERT(*error); - - new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s", - i + 1, arg, *error); - if (!new_error) { - success = false; + if (!iter) { goto end; } - free(*error); - *error = new_error; - success = true; + iter->user.argc = argc; + iter->user.argv = argv; + iter->user.descrs = descrs; + iter->tmp_buf.size = 128; + iter->tmp_buf.data = ARGPAR_CALLOC(char, iter->tmp_buf.size); + if (!iter->tmp_buf.data) { + argpar_iter_destroy(iter); + iter = NULL; + goto end; + } end: - return success; + return iter; } ARGPAR_HIDDEN -struct argpar_parse_ret argpar_parse(unsigned int argc, - const char * const *argv, - const struct argpar_opt_descr * const descrs, - bool fail_on_unknown_opt) +void argpar_iter_destroy(struct argpar_iter * const iter) { - struct argpar_parse_ret parse_ret = { 0 }; - unsigned int i; - unsigned int non_opt_index = 0; - - parse_ret.items = new_item_array(); - if (!parse_ret.items) { - goto error; + if (iter) { + free(iter->tmp_buf.data); + free(iter); } +} - 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; - - if (orig_arg[0] != '-') { - /* Non-option argument */ - struct argpar_item_non_opt *non_opt_item = - create_non_opt_item(orig_arg, i, non_opt_index); +ARGPAR_HIDDEN +enum argpar_iter_next_status argpar_iter_next( + struct argpar_iter * const iter, + const struct argpar_item ** const item, + const struct argpar_error ** const error) +{ + enum argpar_iter_next_status status; + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + const char *orig_arg; + const char *next_orig_arg; + struct argpar_error ** const nc_error = (struct argpar_error **) error; - if (!non_opt_item) { - goto error; - } + ARGPAR_ASSERT(iter->i <= iter->user.argc); - non_opt_index++; + if (error) { + *nc_error = NULL; + } - if (!push_item(parse_ret.items, &non_opt_item->base)) { - goto error; - } + if (iter->i == iter->user.argc) { + status = ARGPAR_ITER_NEXT_STATUS_END; + goto end; + } - continue; - } + orig_arg = iter->user.argv[iter->i]; + next_orig_arg = + iter->i < (iter->user.argc - 1) ? + iter->user.argv[iter->i + 1] : NULL; - /* 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); - - if (fail_on_unknown_opt) { - prepend_while_parsing_arg_to_error( - &parse_ret.error, i, orig_arg); - goto error; - } + if (strcmp(orig_arg, "-") == 0 || strcmp(orig_arg, "--") == 0 || + orig_arg[0] != '-') { + /* Non-option argument */ + const struct argpar_item_non_opt * const non_opt_item = + create_non_opt_item(orig_arg, iter->i, + iter->non_opt_index); - /* - * 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; + if (!non_opt_item) { + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; 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(); } - if (used_next_orig_arg) { - i++; - } + iter->non_opt_index++; + iter->i++; + *item = &non_opt_item->base; + status = ARGPAR_ITER_NEXT_STATUS_OK; + goto end; } - parse_ret.ingested_orig_args = argc; - free(parse_ret.error); - parse_ret.error = NULL; - goto end; - -error: - /* That's how we indicate that an error occured */ - destroy_item_array(parse_ret.items); - parse_ret.items = NULL; + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, + next_orig_arg, iter->user.descrs, iter, nc_error, + (struct argpar_item **) item); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + status = ARGPAR_ITER_NEXT_STATUS_OK; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + if (error) { + ARGPAR_ASSERT(*error); + (*nc_error)->orig_index = iter->i; + } + status = ARGPAR_ITER_NEXT_STATUS_ERROR; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY: + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; + break; + default: + abort(); + } end: - return parse_ret; + return status; } ARGPAR_HIDDEN -void argpar_parse_ret_fini(struct argpar_parse_ret *ret) +unsigned int argpar_iter_ingested_orig_args( + const struct argpar_iter * const iter) { - ARGPAR_ASSERT(ret); - - destroy_item_array(ret->items); - ret->items = NULL; - - free(ret->error); - ret->error = NULL; + return iter->i; }