From a93e984c07724c096b4a0af678703d796e26bf0c Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Thu, 20 May 2021 23:12:58 -0400 Subject: [PATCH] cli: sync argpar - adjust to iterator API Sync with commit 1c88181255c1 ("argpar/argpar.h: update API documentation"). The main change in this version is the API that changed from parse-all-at-once (the `argpar_parse` function) to something based on an iterator, where we need to call `argpar_iter_next` to obtain the next item. Update the top-level option parsing. As before, we stop as soon as we encounter an unknown option or a non-option argument. Update all commands to use the new `parse_next_item` function, which factors out a bit of otherwise repetitive code. Another change in this argpar version is that argpar does not provide a format error string anymore. It provides an `argpar_error` object contaning all the raw information needed to create such string. The new `format_arg_error` function formats the errors using the exact same syntax as argpar did, such that no changes in the tests are necessary. Change-Id: Ied9080953a0bb1b81f9880263a3f9dd1724eba07 Signed-off-by: Simon Marchi Reviewed-on: https://review.lttng.org/c/babeltrace/+/5928 Tested-by: jenkins --- src/argpar/argpar.c | 838 +++++++++++++++++------------ src/argpar/argpar.h | 832 ++++++++++++++++++++++------ src/cli/babeltrace2-cfg-cli-args.c | 714 +++++++++++++++--------- 3 files changed, 1625 insertions(+), 759 deletions(-) diff --git a/src/argpar/argpar.c b/src/argpar/argpar.c index cd916d6e..10f6ee8d 100644 --- a/src/argpar/argpar.c +++ b/src/argpar/argpar.c @@ -1,7 +1,8 @@ /* * SPDX-License-Identifier: MIT * - * Copyright 2019 Philippe Proulx + * Copyright (c) 2019-2021 Philippe Proulx + * Copyright (c) 2020-2021 Simon Marchi */ #include @@ -13,199 +14,194 @@ #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_ASSERT(_cond) assert(_cond) - -#ifdef __MINGW_PRINTF_FORMAT -# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT -#else -# define ARGPAR_PRINTF_FORMAT printf -#endif +#define ARGPAR_CALLOC(_type, _nmemb) \ + ((_type *) calloc((_nmemb), sizeof(_type))) -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; +#define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1) - 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; - } +#define ARGPAR_ASSERT(_cond) assert(_cond) - 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; @@ -224,20 +220,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; @@ -252,6 +255,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, @@ -275,106 +398,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++; - /* Go to next short option */ - short_opt_ch++; + if (descr->with_arg || !*iter->short_opt_group_ch) { + /* Option has an argument: no more options */ + iter->short_opt_group_ch = NULL; + + 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; @@ -382,17 +535,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, '='); @@ -400,23 +546,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; } @@ -428,15 +582,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 */ @@ -445,27 +616,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; @@ -474,142 +655,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; - - ARGPAR_ASSERT(error); - ARGPAR_ASSERT(*error); + struct argpar_iter *iter = ARGPAR_ZALLOC(struct argpar_iter); - 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 occurred */ - 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; } diff --git a/src/argpar/argpar.h b/src/argpar/argpar.h index 00334cd6..27503c56 100644 --- a/src/argpar/argpar.h +++ b/src/argpar/argpar.h @@ -1,217 +1,715 @@ /* * SPDX-License-Identifier: MIT * - * Copyright 2019 Philippe Proulx + * Copyright (c) 2019-2021 Philippe Proulx + * Copyright (c) 2020-2021 Simon Marchi */ -#ifndef BABELTRACE_ARGPAR_H -#define BABELTRACE_ARGPAR_H +#ifndef ARGPAR_ARGPAR_H +#define ARGPAR_ARGPAR_H #include -/* Sentinel for an option descriptor array */ -#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } +/*! +@mainpage + +See the \ref api module. + +@addtogroup api argpar API +@{ + +argpar is a library which provides an iterator-based API to parse +command-line arguments. + +The argpar parser supports: + +
    +
  • + Short options without an argument, possibly tied together: + + @code{.unparsed} + -f -auf -n + @endcode + +
  • + Short options with arguments: + + @code{.unparsed} + -b 45 -f/mein/file -xyzhello + @endcode + +
  • + Long options without an argument: + + @code{.unparsed} + --five-guys --burger-king --pizza-hut --subway + @endcode + +
  • + Long options with arguments (two original arguments or a single + one with a = character): + + @code{.unparsed} + --security enable --time=18.56 + @endcode + +
  • + Non-option arguments (anything else, including + - and \--). + + A non-option argument cannot have the form of an option, for example + if you need to pass the exact relative path + \--component. In that case, you would need to pass + ./\--component. There's no generic way to escape + - as of this version. +
+ +Create a parsing iterator with argpar_iter_create(), then repeatedly +call argpar_iter_next() to access the parsing results (items), until one +of: + +- There are no more arguments. + +- The argument parser encounters an error (for example, an unknown + option). + +- You need to stop. + +argpar_iter_create() accepts duplicate option descriptors in +\p descrs (argpar_iter_next() produces one item for each +instance). + +A parsing item (the result of argpar_iter_next()) has the type +#argpar_item. + +Get the type (option or non-option) of an item with +\link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. +Each item type has its set of dedicated functions +(\c argpar_item_opt_ and \c argpar_item_non_opt_ prefixes). + +argpar_iter_next() produces the items in the same order that it parses +original arguments, including non-option arguments. This means, for +example, that for: + +@code{.unparsed} +--hello --count=23 /path/to/file -ab --type file -- magie +@endcode + +argpar_iter_next() produces the following items, in this order: + +-# Option item (\--hello). +-# Option item (\--count with argument 23). +-# Non-option item (/path/to/file). +-# Option item (-a). +-# Option item (-b). +-# Option item (\--type with argument file). +-# Non-option item (\--). +-# Non-option item (magie). +*/ /* - * ARGPAR_HIDDEN: if argpar is used in some shared library, we don't want them - * to be exported by that library, so mark them as "hidden". + * If argpar is used in some shared library, we don't want said library + * to export its symbols, so mark them as "hidden". * - * On Windows, symbols are local unless explicitly exported, - * see https://gcc.gnu.org/wiki/Visibility + * On Windows, symbols are local unless explicitly exported; see + * . */ #if defined(_WIN32) || defined(__CYGWIN__) -#define ARGPAR_HIDDEN +# define ARGPAR_HIDDEN #else -#define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +# define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) #endif -/* Option descriptor */ +struct argpar_opt_descr; + +/*! +@name Item API +@{ +*/ + +/*! +@brief + Type of a parsing item, as returned by + \link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. +*/ +enum argpar_item_type { + /// Option + ARGPAR_ITEM_TYPE_OPT, + + /// Non-option + ARGPAR_ITEM_TYPE_NON_OPT, +}; + +/*! +@struct argpar_item + +@brief + Opaque parsing item type + +argpar_iter_next() sets a pointer to such a type. +*/ +struct argpar_item; + +/*! +@brief + Returns the type of the parsing item \p item. + +@param[in] item + Parsing item of which to get the type. + +@returns + Type of \p item. + +@pre + \p item is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +enum argpar_item_type argpar_item_type(const struct argpar_item *item); + +/*! +@brief + Returns the option descriptor of the option parsing item \p item. + +@param[in] item + Option parsing item of which to get the option descriptor. + +@returns + Option descriptor of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const struct argpar_opt_descr *argpar_item_opt_descr( + const struct argpar_item *item); + +/*! +@brief + Returns the argument of the option parsing item \p item, or + \c NULL if none. + +@param[in] item + Option parsing item of which to get the argument. + +@returns + Argument of \p item, or \c NULL if none. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_item_opt_arg(const struct argpar_item *item); + +/*! +@brief + Returns the complete original argument, pointing to one of the + entries of the original arguments (in \p argv, as passed to + argpar_iter_create()), of the non-option parsing item \p item. + +@param[in] item + Non-option parsing item of which to get the complete original + argument. + +@returns + Complete original argument of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_item_non_opt_arg(const struct argpar_item *item); + +/*! +@brief + Returns the index, within \em all the original arguments (in + \p argv, as passed to argpar_iter_create()), of the non-option + parsing item \p item. + +For example, with the following command line (all options have no +argument): + +@code{.unparsed} +-f -m meow --jus mix --kilo +@endcode + +The original argument index of \c meow is 2 while the original +argument index of \c mix is 4. + +@param[in] item + Non-option parsing item of which to get the original argument index. + +@returns + Original argument index of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. + +@sa + argpar_item_non_opt_non_opt_index() -- Returns the non-option index + of a non-option parsing item. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_item_non_opt_orig_index(const struct argpar_item *item); + +/*! +@brief + Returns the index, within the parsed non-option parsing items, of + the non-option parsing item \p item. + +For example, with the following command line (all options have no +argument): + +@code{.unparsed} +-f -m meow --jus mix --kilo +@endcode + +The non-option index of \c meow is 0 while the original +argument index of \c mix is 1. + +@param[in] item + Non-option parsing item of which to get the non-option index. + +@returns + Non-option index of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. + +@sa + argpar_item_non_opt_orig_index() -- Returns the original argument + index of a non-option parsing item. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_item_non_opt_non_opt_index(const struct argpar_item *item); + +/*! +@brief + Destroys the parsing item \p item. + +@param[in] item + Parsing item to destroy (may be \c NULL). +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +void argpar_item_destroy(const struct argpar_item *item); + +/*! +@def ARGPAR_ITEM_DESTROY_AND_RESET(_item) + +@brief + Calls argpar_item_destroy() with \p _item, and then sets \p _item + to \c NULL. + +@param[in] _item + Item to destroy and variable to reset + (const struct argpar_item * type). +*/ +#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ + { \ + argpar_item_destroy(_item); \ + _item = NULL; \ + } + +/// @} + +/*! +@name Error API +@{ +*/ + +/*! +@brief + Parsing error type, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink. +*/ +enum argpar_error_type { + /// Unknown option error + ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + + /// Missing option argument error + ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + + /// Unexpected option argument error + ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, +}; + +/*! +@struct argpar_error + +@brief + Opaque parsing error type +*/ +struct argpar_error; + +/*! +@brief + Returns the type of the parsing error object \p error. + +@param[in] error + Parsing error of which to get the type. + +@returns + Type of \p error. + +@pre + \p error is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +enum argpar_error_type argpar_error_type(const struct argpar_error *error); + +/*! +@brief + Returns the index of the original argument (in \p argv, as passed to + argpar_iter_create()) for which the parsing error described by + \p error occurred. + +@param[in] error + Parsing error of which to get the original argument index. + +@returns + Original argument index of \p error. + +@pre + \p error is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_error_orig_index(const struct argpar_error *error); + +/*! +@brief + Returns the name of the unknown option for which the parsing error + described by \p error occurred. + +The returned name includes any - or \-- +prefix. + +With the long option with argument form, for example +\--mireille=deyglun, this function only returns the name +part (\--mireille in the last example). + +@param[in] error + Parsing error of which to get the name of the unknown option. + +@returns + Name of the unknown option of \p error. + +@pre + \p error is not \c NULL. +@pre + The type of \p error, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + is #ARGPAR_ERROR_TYPE_UNKNOWN_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_error_unknown_opt_name(const struct argpar_error *error); + +/*! +@brief + Returns the descriptor of the option for which the parsing error + described by \p error occurred. + +@param[in] error + Parsing error of which to get the option descriptor. +@param[out] is_short + @parblock + If not \c NULL, this function sets \p *is_short to: + + - \c true if the option for which \p error occurred is a short + option. + + - \c false if the option for which \p error occurred is a long + option. + @endparblock + +@returns + Descriptor of the option of \p error. + +@pre + \p error is not \c NULL. +@pre + The type of \p error, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + is #ARGPAR_ERROR_TYPE_MISSING_OPT_ARG or + #ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const struct argpar_opt_descr *argpar_error_opt_descr( + const struct argpar_error *error, bool *is_short); + +/*! +@brief + Destroys the parsing error \p error. + +@param[in] error + Parsing error to destroy (may be \c NULL). +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +void argpar_error_destroy(const struct argpar_error *error); + +/// @} + +/*! +@name Iterator API +@{ +*/ + +/*! +@brief + Option descriptor + +argpar_iter_create() accepts an array of instances of such a type, +terminated with #ARGPAR_OPT_DESCR_SENTINEL, as its \p descrs parameter. + +The typical usage is, for example: + +@code +const struct argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 1, '\0', "squeeze", true }, + { 2, 'm', "meow", true }, + ARGPAR_OPT_DESCR_SENTINEL, +}; +@endcode +*/ struct argpar_opt_descr { - /* Numeric ID for this option */ + /// Numeric ID, to uniquely identify this descriptor const int id; - /* Short option character, or `\0` */ + /// Short option character, or '\0' const char short_name; - /* Long option name (without `--`), or `NULL` */ + /// Long option name (without the \-- prefix), or \c NULL const char * const long_name; - /* True if this option has an argument */ + /// \c true if this option has an argument const bool with_arg; }; -/* Item type */ -enum argpar_item_type { - /* Option */ - ARGPAR_ITEM_TYPE_OPT, +/*! +@brief + Sentinel for an option descriptor array - /* Non-option */ - ARGPAR_ITEM_TYPE_NON_OPT, -}; +The typical usage is, for example: -/* Base item */ -struct argpar_item { - enum argpar_item_type type; +@code +const struct argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 1, '\0', "squeeze", true }, + { 2, 'm', "meow", true }, + ARGPAR_OPT_DESCR_SENTINEL, }; +@endcode +*/ +#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } -/* Option item */ -struct argpar_item_opt { - struct argpar_item base; +/*! +@struct argpar_iter - /* Corresponding descriptor */ - const struct argpar_opt_descr *descr; +@brief + Opaque argpar iterator type - /* Argument, or `NULL` if none */ - const char *arg; -}; +argpar_iter_create() returns a pointer to such a type. +*/ +struct argpar_iter; -/* Non-option item */ -struct argpar_item_non_opt { - struct argpar_item base; +/*! +@brief + Creates and returns an argument parsing iterator to parse the + original arguments \p argv of which the count is \p argc using the + option descriptors \p descrs. - /* - * Complete argument, pointing to one of the entries of the - * original arguments (`argv`). - */ - const char *arg; +This function initializes the returned structure, but doesn't actually +start parsing the arguments. - /* Index of this argument amongst all original arguments (`argv`) */ - unsigned int orig_index; +argpar considers \em all the elements of \p argv, including the first +one, so that you would typically pass (argc - 1) as \p argc +and \&argv[1] as \p argv from what main() +receives, or ignore the parsing item of the first call to +argpar_iter_next(). - /* Index of this argument amongst other non-option arguments */ - unsigned int non_opt_index; -}; +\p *argv and \p *descrs must \em not change for all of: -struct argpar_item_array { - /* Array of `struct argpar_item *`, or `NULL` on error */ - struct argpar_item **items; +- The lifetime of the returned iterator (until you call + argpar_iter_destroy()). - /* Number of used slots in `items`. */ - unsigned int n_items; +- The lifetime of any parsing item (until you call + argpar_item_destroy()) which argpar_iter_next() creates from the + returned iterator. - /* Number of allocated slots in `items`. */ - unsigned int n_alloc; -}; +- The lifetime of any parsing error (until you call + argpar_error_destroy()) which argpar_iter_next() creates from the + returned iterator. -/* What is returned by argpar_parse() */ -struct argpar_parse_ret { - /* Array of `struct argpar_item *`, or `NULL` on error */ - struct argpar_item_array *items; +@param[in] argc + Number of original arguments to parse in \p argv. +@param[in] argv + Original arguments to parse, of which the count is \p argc. +@param[in] descrs + @parblock + Option descriptor array, terminated with #ARGPAR_OPT_DESCR_SENTINEL. - /* Error string, or `NULL` if none */ - char *error; + May contain duplicate entries. + @endparblock - /* Number of original arguments (`argv`) ingested */ - unsigned int ingested_orig_args; -}; +@returns + New argument parsing iterator, or \c NULL on memory error. -/* - * Parses the arguments `argv` of which the count is `argc` using the - * sentinel-terminated (use `ARGPAR_OPT_DESCR_SENTINEL`) option - * descriptor array `descrs`. - * - * This function considers ALL the elements of `argv`, including the - * first one, so that you would typically pass `argc - 1` and - * `&argv[1]` from what main() receives. - * - * This argument parser supports: - * - * * Short options without an argument, possibly tied together: - * - * -f -auf -n - * - * * Short options with argument: - * - * -b 45 -f/mein/file -xyzhello - * - * * Long options without an argument: - * - * --five-guys --burger-king --pizza-hut --subway - * - * * Long options with arguments: - * - * --security enable --time=18.56 - * - * * Non-option arguments (anything else). - * - * This function does not accept `-` or `--` as arguments. The latter - * means "end of options" for many command-line tools, but this function - * is all about keeping the order of the arguments, so it does not mean - * much to put them at the end. This has the side effect that a - * non-option argument cannot have the form of an option, for example if - * you need to pass the exact relative path `--component`. In that case, - * you would need to pass `./--component`. There's no generic way to - * escape `-` for the moment. - * - * This function accepts duplicate options (the resulting array of items - * contains one entry for each instance). - * - * On success, this function returns an array of items - * (`struct argpar_item *`). Each item is to be casted to the - * appropriate type (`struct argpar_item_opt *` or - * `struct argpar_item_non_opt *`) depending on its type. - * - * The returned array contains the items in the same order that the - * arguments were parsed, including non-option arguments. This means, - * for example, that for - * - * --hello --meow=23 /path/to/file -b - * - * the function returns an array of four items: two options, one - * non-option, and one option. - * - * In the returned structure, `ingested_orig_args` is the number of - * ingested arguments within `argv` to produce the resulting array of - * items. If `fail_on_unknown_opt` is true, then on success - * `ingested_orig_args` is equal to `argc`. Otherwise, - * `ingested_orig_args` contains the number of original arguments until - * an unknown _option_ occurs. For example, with - * - * --great --white contact nuance --shark nuclear - * - * if `--shark` is not described within `descrs` and - * `fail_on_unknown_opt` is false, then `ingested_orig_args` is 4 (two - * options, two non-options), whereas `argc` is 6. - * - * This makes it possible to know where a command name is, for example. - * With those arguments: - * - * --verbose --stuff=23 do-something --specific-opt -f -b - * - * and the descriptors for `--verbose` and `--stuff` only, the function - * returns the `--verbose` and `--stuff` option items, the - * `do-something` non-option item, and that three original arguments - * were ingested. This means you can start the next argument parsing - * stage, with option descriptors depending on the command name, at - * `&argv[3]`. - * - * Note that `ingested_orig_args` is not always equal to the number of - * returned items, as - * - * --hello -fdw - * - * for example contains two ingested original arguments, but four - * resulting items. - * - * On failure, the returned structure's `items` member is `NULL`, and - * the `error` string member contains details about the error. - * - * You can finalize the returned structure with - * argpar_parse_ret_fini(). - */ +@pre + \p argc is greater than 0. +@pre + \p argv is not \c NULL. +@pre + The first \p argc elements of \p argv are not \c NULL. +@pre + \p descrs is not \c NULL. + +@sa + argpar_iter_destroy() -- Destroys an argument parsing iterator. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -struct argpar_parse_ret argpar_parse(unsigned int argc, +/// @endcond +struct argpar_iter *argpar_iter_create(unsigned int argc, const char * const *argv, - const struct argpar_opt_descr *descrs, - bool fail_on_unknown_opt); + const struct argpar_opt_descr *descrs); + +/*! +@brief + Destroys the argument parsing iterator \p iter. + +@param[in] iter + Argument parsing iterator to destroy (may be \c NULL). + +@sa + argpar_iter_create() -- Creates an argument parsing iterator. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +void argpar_iter_destroy(struct argpar_iter *iter); + +/*! +@brief + Return type of argpar_iter_next(). + +Error status enumerators have a negative value. +*/ +enum argpar_iter_next_status { + /// Success + ARGPAR_ITER_NEXT_STATUS_OK, + + /// End of iteration (no more original arguments to parse) + ARGPAR_ITER_NEXT_STATUS_END, + + /// Parsing error + ARGPAR_ITER_NEXT_STATUS_ERROR = -1, + + /// Memory error + ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY = -12, +}; + +/*! +@brief + Sets \p *item to the next item of the argument parsing iterator + \p iter and advances \p iter. + +If there are no more original arguments to parse, this function returns +#ARGPAR_ITER_NEXT_STATUS_END. + +@param[in] iter + Argument parsing iterator from which to get the next parsing item. +@param[out] item + @parblock + On success, \p *item is the next parsing item of \p iter. + + Destroy \p *item with argpar_item_destroy(). + @endparblock +@param[out] error + @parblock + When this function returns #ARGPAR_ITER_NEXT_STATUS_ERROR, + if this parameter is not \c NULL, \p *error contains details about + the error. + + Destroy \p *error with argpar_error_destroy(). + @endparblock + +@returns + Status code. + +@pre + \p iter is not \c NULL. +@pre + \p item is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +enum argpar_iter_next_status argpar_iter_next( + struct argpar_iter *iter, const struct argpar_item **item, + const struct argpar_error **error); /* - * Finalizes what is returned by argpar_parse(). - * - * It is safe to call argpar_parse() multiple times with the same - * structure. + * Returns the number of ingested elements from `argv`, as passed to + * argpar_iter_create() to create `*iter`, that were required to produce + * the previously returned items. */ + +/*! +@brief + Returns the number of ingested original arguments (in + \p argv, as passed to argpar_iter_create() to create \p iter) that + the parser ingested to produce the \em previous parsing items. + +@param[in] iter + Argument parsing iterator of which to get the number of ingested + original arguments. + +@returns + Number of original arguments which \p iter ingested. + +@pre + \p iter is not \c NULL. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -void argpar_parse_ret_fini(struct argpar_parse_ret *ret); +/// @endcond +unsigned int argpar_iter_ingested_orig_args(const struct argpar_iter *iter); + +/// @} + +/// @} -#endif /* BABELTRACE_ARGPAR_H */ +#endif /* ARGPAR_ARGPAR_H */ diff --git a/src/cli/babeltrace2-cfg-cli-args.c b/src/cli/babeltrace2-cfg-cli-args.c index 37b29ec0..86bf918e 100644 --- a/src/cli/babeltrace2-cfg-cli-args.c +++ b/src/cli/babeltrace2-cfg-cli-args.c @@ -32,6 +32,7 @@ #include "common/version.h" #define BT_CLI_LOGE_APPEND_CAUSE_OOM() BT_CLI_LOGE_APPEND_CAUSE("Out of memory.") +#define WHILE_PARSING_ARG_N_FMT "While parsing argument #%d (`%s`): " /* * Returns the plugin name, component class name, component class type, @@ -1289,32 +1290,153 @@ void print_expected_params_format(FILE *fp) fprintf(fp, "babeltrace2 from a shell.\n"); } +/* + * Given argpar error status `status` and error `error`, return a formatted + * error message describing the error. + * + * `argv` is the argument vector that was being parsed. + * + * `prefix_fmt` (formatted using the following arguments) is prepended to + * the error message. + * + * The returned string must be freed by the caller. + */ static -bool help_option_is_specified( - const struct argpar_parse_ret *argpar_parse_ret) +GString *__BT_ATTR_FORMAT_PRINTF(3, 4) format_arg_error( + const struct argpar_error *error, + const char **argv, const char *prefix_fmt, ...) + { - int i; - bool specified = false; + GString *str = g_string_new(NULL); + va_list args; - for (i = 0; i < argpar_parse_ret->items->n_items; i++) { - struct argpar_item *argpar_item = - argpar_parse_ret->items->items[i]; - struct argpar_item_opt *argpar_item_opt; + va_start(args, prefix_fmt); + g_string_append_vprintf(str, prefix_fmt, args); + va_end(args); - if (argpar_item->type != ARGPAR_ITEM_TYPE_OPT) { - continue; + g_string_append(str, ": "); + + switch (argpar_error_type(error)) + { + case ARGPAR_ERROR_TYPE_MISSING_OPT_ARG: + { + bool is_short; + const struct argpar_opt_descr *descr = + argpar_error_opt_descr(error, &is_short); + int orig_index = argpar_error_orig_index(error); + const char *arg = argv[orig_index]; + + if (is_short) { + g_string_append_printf( + str, + WHILE_PARSING_ARG_N_FMT "Missing required argument for option `-%c`", + orig_index + 1, arg, descr->short_name); + } else { + g_string_append_printf( + str, + WHILE_PARSING_ARG_N_FMT "Missing required argument for option `--%s`", + orig_index + 1, arg, descr->long_name); } - argpar_item_opt = (struct argpar_item_opt *) argpar_item; - if (argpar_item_opt->descr->id == OPT_HELP) { - specified = true; - break; + break; + } + case ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG: + { + bool is_short; + const struct argpar_opt_descr *descr = + argpar_error_opt_descr(error, &is_short); + int orig_index = argpar_error_orig_index(error); + const char *arg = argv[orig_index]; + + if (is_short) { + g_string_append_printf( + str, + WHILE_PARSING_ARG_N_FMT "Unexpected argument for option `-%c`", + orig_index + 1, arg, descr->short_name); + } else { + g_string_append_printf( + str, + WHILE_PARSING_ARG_N_FMT "Unexpected argument for option `--%s`", + orig_index + 1, arg, descr->long_name); } + + break; + } + case ARGPAR_ERROR_TYPE_UNKNOWN_OPT: + { + int orig_index = argpar_error_orig_index(error); + const char *unknown_opt = argpar_error_unknown_opt_name(error); + const char *arg = argv[orig_index]; + + g_string_append_printf( + str, + WHILE_PARSING_ARG_N_FMT "Unknown option `%s`", + orig_index + 1, arg, unknown_opt); + + break; + } + + default: + BT_ASSERT(0); + } + + return str; +} + +enum parse_next_item_status +{ + PARSE_NEXT_ITEM_STATUS_OK = 0, + PARSE_NEXT_ITEM_STATUS_END = 1, + PARSE_NEXT_ITEM_STATUS_ERROR = -1, +}; + +/* + * Parse the next item using `iter`. Log and append an error if necessary. + * + * The item in `*item` on entry is freed, and the new item is also + * returned in `*item`. + */ +static +enum parse_next_item_status parse_next_item(struct argpar_iter *iter, + const struct argpar_item **item, const char **argv, + const char *command) +{ + enum argpar_iter_next_status status; + const struct argpar_error *error = NULL; + enum parse_next_item_status ret; + + ARGPAR_ITEM_DESTROY_AND_RESET(*item); + status = argpar_iter_next(iter, item, &error); + + switch (status) { + case ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY: + BT_CLI_LOGE_APPEND_CAUSE_OOM(); + ret = PARSE_NEXT_ITEM_STATUS_ERROR; + break; + case ARGPAR_ITER_NEXT_STATUS_ERROR: + { + GString *err_str = format_arg_error(error, argv, + "While parsing `%s` command's command-line arguments", command); + BT_CLI_LOGE_APPEND_CAUSE("%s", err_str->str); + g_string_free(err_str, TRUE); + ret = PARSE_NEXT_ITEM_STATUS_ERROR; + break; + } + case ARGPAR_ITER_NEXT_STATUS_END: + ret = PARSE_NEXT_ITEM_STATUS_END; + break; + case ARGPAR_ITER_NEXT_STATUS_OK: + ret = PARSE_NEXT_ITEM_STATUS_OK; + break; + default: + bt_common_abort(); } - return specified; + argpar_error_destroy(error); + return ret; } + /* * Prints the help command usage. */ @@ -1352,11 +1474,12 @@ struct bt_config *bt_config_help_from_args(int argc, const char *argv[], int default_log_level) { struct bt_config *cfg = NULL; + const char *plugin_comp_cls_arg = NULL; char *plugin_name = NULL, *comp_cls_name = NULL; - struct argpar_parse_ret argpar_parse_ret = { 0 }; - struct argpar_item_non_opt *non_opt; GString *substring = NULL; size_t end_pos; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; *retcode = 0; cfg = bt_config_help_create(plugin_paths, default_log_level); @@ -1364,63 +1487,77 @@ struct bt_config *bt_config_help_from_args(int argc, const char *argv[], goto error; } - /* Parse options */ - argpar_parse_ret = argpar_parse(argc, argv, help_options, true); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing `help` command's command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(argc, argv, help_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - if (help_option_is_specified(&argpar_parse_ret)) { - print_help_usage(stdout); - *retcode = -1; - BT_OBJECT_PUT_REF_AND_RESET(cfg); - goto end; + while (true) { + enum parse_next_item_status status = + parse_next_item(argpar_iter, &argpar_item, argv, "help"); + + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *opt_descr = + argpar_item_opt_descr(argpar_item); + + switch (opt_descr->id) { + case OPT_HELP: + print_help_usage(stdout); + *retcode = -1; + BT_OBJECT_PUT_REF_AND_RESET(cfg); + goto end; + default: + bt_common_abort(); + } + } else { + const char *arg = argpar_item_non_opt_arg(argpar_item); + + if (plugin_comp_cls_arg) { + BT_CLI_LOGE_APPEND_CAUSE( + "Extraneous command-line argument specified to `help` command: `%s`.", + arg); + goto error; + } + + plugin_comp_cls_arg = arg; + } } - if (argpar_parse_ret.items->n_items == 0) { + if (!plugin_comp_cls_arg) { BT_CLI_LOGE_APPEND_CAUSE( "Missing plugin name or component class descriptor."); goto error; - } else if (argpar_parse_ret.items->n_items > 1) { - /* - * At this point we know there are least two non-option - * arguments because we don't reach here with `--help`, - * the only option. - */ - non_opt = (struct argpar_item_non_opt *) argpar_parse_ret.items->items[1]; - BT_CLI_LOGE_APPEND_CAUSE( - "Extraneous command-line argument specified to `help` command: `%s`.", - non_opt->arg); - goto error; } - non_opt = (struct argpar_item_non_opt *) argpar_parse_ret.items->items[0]; - /* Look for unescaped dots in the argument. */ - substring = bt_common_string_until(non_opt->arg, ".\\", ".", &end_pos); + substring = bt_common_string_until(plugin_comp_cls_arg, ".\\", ".", &end_pos); if (!substring) { BT_CLI_LOGE_APPEND_CAUSE("Could not consume argument: arg=%s", - non_opt->arg); + plugin_comp_cls_arg); goto error; } - if (end_pos == strlen(non_opt->arg)) { + if (end_pos == strlen(plugin_comp_cls_arg)) { /* Didn't find an unescaped dot, treat it as a plugin name. */ g_string_assign(cfg->cmd_data.help.cfg_component->plugin_name, - non_opt->arg); + plugin_comp_cls_arg); } else { /* * Found an unescaped dot, treat it as a component class name. */ - plugin_comp_cls_names(non_opt->arg, NULL, &plugin_name, &comp_cls_name, + plugin_comp_cls_names(plugin_comp_cls_arg, NULL, &plugin_name, &comp_cls_name, &cfg->cmd_data.help.cfg_component->type); if (!plugin_name || !comp_cls_name) { BT_CLI_LOGE_APPEND_CAUSE( "Could not parse argument as a component class name: arg=%s", - non_opt->arg); + plugin_comp_cls_arg); goto error; } @@ -1444,7 +1581,8 @@ end: g_string_free(substring, TRUE); } - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_iter_destroy(argpar_iter); + argpar_item_destroy(argpar_item); return cfg; } @@ -1485,12 +1623,12 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[], int *retcode, const bt_value *plugin_paths, int default_log_level) { - int i; struct bt_config *cfg = NULL; const char *component_class_spec = NULL; const char *query_object = NULL; GString *error_str = NULL; - struct argpar_parse_ret argpar_parse_ret = { 0 }; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; bt_value *params = bt_value_map_create(); if (!params) { @@ -1510,32 +1648,33 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[], goto error; } - /* Parse options */ - argpar_parse_ret = argpar_parse(argc, argv, query_options, true); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing `query` command's command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(argc, argv, query_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - if (help_option_is_specified(&argpar_parse_ret)) { - print_query_usage(stdout); - *retcode = -1; - BT_OBJECT_PUT_REF_AND_RESET(cfg); - goto end; - } + while (true) { + enum parse_next_item_status status = + parse_next_item(argpar_iter, &argpar_item, argv, "query"); - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - struct argpar_item *argpar_item = - argpar_parse_ret.items->items[i]; + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } - if (argpar_item->type == ARGPAR_ITEM_TYPE_OPT) { - struct argpar_item_opt *argpar_item_opt = - (struct argpar_item_opt *) argpar_item; - const char *arg = argpar_item_opt->arg; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *opt_descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (argpar_item_opt->descr->id) { + switch (opt_descr->id) { + case OPT_HELP: + print_query_usage(stdout); + *retcode = -1; + BT_OBJECT_PUT_REF_AND_RESET(cfg); + goto end; case OPT_PARAMS: { bt_value *parsed_params = bt_param_parse(arg, error_str); @@ -1556,13 +1695,10 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[], break; } default: - BT_CLI_LOGE_APPEND_CAUSE("Unknown command-line option specified (option code %d).", - argpar_item_opt->descr->id); - goto error; + bt_common_abort(); } } else { - struct argpar_item_non_opt *argpar_item_non_opt - = (struct argpar_item_non_opt *) argpar_item; + const char *arg = argpar_item_non_opt_arg(argpar_item); /* * We need exactly two non-option arguments @@ -1570,12 +1706,12 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[], * specification and query object. */ if (!component_class_spec) { - component_class_spec = argpar_item_non_opt->arg; + component_class_spec = arg; } else if (!query_object) { - query_object = argpar_item_non_opt->arg; + query_object = arg; } else { BT_CLI_LOGE_APPEND_CAUSE("Extraneous command-line argument specified to `query` command: `%s`.", - argpar_item_non_opt->arg); + arg); goto error; } } @@ -1613,7 +1749,8 @@ error: BT_OBJECT_PUT_REF_AND_RESET(cfg); end: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_iter_destroy(argpar_iter); + argpar_item_destroy(argpar_item); if (error_str) { g_string_free(error_str, TRUE); @@ -1658,7 +1795,8 @@ struct bt_config *bt_config_list_plugins_from_args(int argc, const char *argv[], int *retcode, const bt_value *plugin_paths) { struct bt_config *cfg = NULL; - struct argpar_parse_ret argpar_parse_ret = { 0 }; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; *retcode = 0; cfg = bt_config_list_plugins_create(plugin_paths); @@ -1666,36 +1804,42 @@ struct bt_config *bt_config_list_plugins_from_args(int argc, const char *argv[], goto error; } - /* Parse options */ - argpar_parse_ret = argpar_parse(argc, argv, list_plugins_options, true); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing `list-plugins` command's command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(argc, argv, list_plugins_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - if (help_option_is_specified(&argpar_parse_ret)) { - print_list_plugins_usage(stdout); - *retcode = -1; - BT_OBJECT_PUT_REF_AND_RESET(cfg); - goto end; - } + while (true) { + enum parse_next_item_status status = + parse_next_item(argpar_iter, &argpar_item, argv, "list-plugins"); - if (argpar_parse_ret.items->n_items > 0) { - /* - * At this point we know there's at least one non-option - * argument because we don't reach here with `--help`, - * the only option. - */ - struct argpar_item_non_opt *non_opt = - (struct argpar_item_non_opt *) argpar_parse_ret.items->items[0]; + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } - BT_CLI_LOGE_APPEND_CAUSE( - "Extraneous command-line argument specified to `list-plugins` command: `%s`.", - non_opt->arg); - goto error; - } + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *opt_descr = + argpar_item_opt_descr(argpar_item); + + switch (opt_descr->id) { + case OPT_HELP: + print_list_plugins_usage(stdout); + *retcode = -1; + BT_OBJECT_PUT_REF_AND_RESET(cfg); + goto end; + default: + bt_common_abort(); + } + } else { + BT_CLI_LOGE_APPEND_CAUSE( + "Extraneous command-line argument specified to `list-plugins` command: `%s`.", + argpar_item_non_opt_arg(argpar_item)); + goto error; + } + } goto end; @@ -1704,7 +1848,8 @@ error: BT_OBJECT_PUT_REF_AND_RESET(cfg); end: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_iter_destroy(argpar_iter); + argpar_item_destroy(argpar_item); return cfg; } @@ -1802,8 +1947,8 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[], long retry_duration = -1; bt_value_map_extend_status extend_status; GString *error_str = NULL; - struct argpar_parse_ret argpar_parse_ret = { 0 }; - int i; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; static const struct argpar_opt_descr run_options[] = { { OPT_BASE_PARAMS, 'b', "base-params", true }, @@ -1855,42 +2000,40 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[], goto error; } - /* Parse options */ - argpar_parse_ret = argpar_parse(argc, argv, run_options, true); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing `run` command's command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(argc, argv, run_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - if (help_option_is_specified(&argpar_parse_ret)) { - print_run_usage(stdout); - *retcode = -1; - BT_OBJECT_PUT_REF_AND_RESET(cfg); - goto end; - } - - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - struct argpar_item *argpar_item = - argpar_parse_ret.items->items[i]; - struct argpar_item_opt *argpar_item_opt; + while (true) { + enum parse_next_item_status status; + const struct argpar_opt_descr *opt_descr; const char *arg; - /* This command does not accept non-option arguments.*/ - if (argpar_item->type == ARGPAR_ITEM_TYPE_NON_OPT) { - struct argpar_item_non_opt *argpar_nonopt_item = - (struct argpar_item_non_opt *) argpar_item; + status = parse_next_item(argpar_iter, &argpar_item, argv, "run"); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + /* This command does not accept non-option arguments.*/ + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_NON_OPT) { BT_CLI_LOGE_APPEND_CAUSE("Unexpected argument: `%s`", - argpar_nonopt_item->arg); + argpar_item_non_opt_arg(argpar_item)); goto error; } - argpar_item_opt = (struct argpar_item_opt *) argpar_item; - arg = argpar_item_opt->arg; + opt_descr = argpar_item_opt_descr(argpar_item); + arg = argpar_item_opt_arg(argpar_item); - switch (argpar_item_opt->descr->id) { + switch (opt_descr->id) { + case OPT_HELP: + print_run_usage(stdout); + *retcode = -1; + BT_OBJECT_PUT_REF_AND_RESET(cfg); + goto end; case OPT_COMPONENT: { enum bt_config_component_dest dest; @@ -2008,14 +2151,14 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[], break; case OPT_RETRY_DURATION: { gchar *end; - size_t arg_len = strlen(argpar_item_opt->arg); + size_t arg_len = strlen(arg); - retry_duration = g_ascii_strtoll(argpar_item_opt->arg, &end, 10); + retry_duration = g_ascii_strtoll(arg, &end, 10); - if (arg_len == 0 || end != (argpar_item_opt->arg + arg_len)) { + if (arg_len == 0 || end != (arg + arg_len)) { BT_CLI_LOGE_APPEND_CAUSE( "Could not parse --retry-duration option's argument as an unsigned integer: `%s`", - argpar_item_opt->arg); + arg); goto error; } @@ -2030,9 +2173,7 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[], break; } default: - BT_CLI_LOGE_APPEND_CAUSE("Unknown command-line option specified (option code %d).", - argpar_item_opt->descr->id); - goto error; + bt_common_abort(); } } @@ -2067,7 +2208,9 @@ end: g_string_free(error_str, TRUE); } - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_iter_destroy(argpar_iter); + argpar_item_destroy(argpar_item); + BT_OBJECT_PUT_REF_AND_RESET(cur_cfg_comp); BT_VALUE_PUT_REF_AND_RESET(cur_base_params); BT_VALUE_PUT_REF_AND_RESET(instance_names); @@ -3202,7 +3345,8 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], char *output = NULL; struct auto_source_discovery auto_disc = { NULL }; GString *auto_disc_comp_name = NULL; - struct argpar_parse_ret argpar_parse_ret = { 0 }; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; GString *name_gstr = NULL; GString *component_arg_for_run = NULL; bt_value *live_inputs_array_val = NULL; @@ -3325,35 +3469,36 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], * arguments if needed to automatically name unnamed component * instances. */ - argpar_parse_ret = argpar_parse(argc, argv, convert_options, true); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing `convert` command's command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(argc, argv, convert_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - if (help_option_is_specified(&argpar_parse_ret)) { - print_convert_usage(stdout); - *retcode = -1; - BT_OBJECT_PUT_REF_AND_RESET(cfg); - goto end; - } - - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - struct argpar_item *argpar_item = - argpar_parse_ret.items->items[i]; - struct argpar_item_opt *argpar_item_opt; + while (true) { + enum parse_next_item_status status; char *name = NULL; char *plugin_name = NULL; char *comp_cls_name = NULL; - const char *arg; - if (argpar_item->type == ARGPAR_ITEM_TYPE_OPT) { - argpar_item_opt = (struct argpar_item_opt *) argpar_item; - arg = argpar_item_opt->arg; + status = parse_next_item(argpar_iter, &argpar_item, argv, "convert"); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *opt_descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (argpar_item_opt->descr->id) { + switch (opt_descr->id) { + case OPT_HELP: + print_convert_usage(stdout); + *retcode = -1; + BT_OBJECT_PUT_REF_AND_RESET(cfg); + goto end; case OPT_COMPONENT: { bt_component_class_type type; @@ -3570,20 +3715,15 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], /* Ignore in this pass */ break; default: - BT_CLI_LOGE_APPEND_CAUSE("Unknown command-line option specified (option code %d).", - argpar_item_opt->descr->id); - goto error; + bt_common_abort(); } - } else if (argpar_item->type == ARGPAR_ITEM_TYPE_NON_OPT) { - struct argpar_item_non_opt *argpar_item_non_opt; + } else { + const char *arg = argpar_item_non_opt_arg(argpar_item); bt_value_array_append_element_status append_status; current_item_type = CONVERT_CURRENT_ITEM_TYPE_NON_OPT; - argpar_item_non_opt = (struct argpar_item_non_opt *) argpar_item; - - append_status = bt_value_array_append_string_element(non_opts, - argpar_item_non_opt->arg); + append_status = bt_value_array_append_string_element(non_opts, arg); if (append_status != BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK) { BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; @@ -3601,8 +3741,6 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - } else { - bt_common_abort(); } } @@ -3611,20 +3749,33 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], * arguments into implicit component instances for the run * command. */ - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - struct argpar_item *argpar_item = - argpar_parse_ret.items->items[i]; - struct argpar_item_opt *argpar_item_opt; + argpar_iter_destroy(argpar_iter); + argpar_iter = argpar_iter_create(argc, argv, convert_options); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); + goto error; + } + + while (true) { + enum parse_next_item_status status; + const struct argpar_opt_descr *opt_descr; const char *arg; - if (argpar_item->type != ARGPAR_ITEM_TYPE_OPT) { + status = parse_next_item(argpar_iter, &argpar_item, argv, "convert"); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + + if (argpar_item_type(argpar_item) != ARGPAR_ITEM_TYPE_OPT) { continue; } - argpar_item_opt = (struct argpar_item_opt *) argpar_item; - arg = argpar_item_opt->arg; + opt_descr = argpar_item_opt_descr(argpar_item); + arg = argpar_item_opt_arg(argpar_item); - switch (argpar_item_opt->descr->id) { + switch (opt_descr->id) { case OPT_BEGIN: if (trimmer_has_begin) { BT_CLI_LOGE_APPEND_CAUSE("At --begin option: --begin or --timerange option already specified\n %s\n", @@ -3894,8 +4045,18 @@ struct bt_config *bt_config_convert_from_args(int argc, const char *argv[], *default_log_level = logging_level_min(*default_log_level, BT_LOG_TRACE); break; - default: + case OPT_COMPONENT: + case OPT_HELP: + case OPT_LOG_LEVEL: + case OPT_OMIT_HOME_PLUGIN_PATH: + case OPT_OMIT_SYSTEM_PLUGIN_PATH: + case OPT_PARAMS: + case OPT_PLUGIN_PATH: + case OPT_RETRY_DURATION: + /* Ignore in this pass */ break; + default: + bt_common_abort(); } } @@ -4402,7 +4563,8 @@ error: BT_OBJECT_PUT_REF_AND_RESET(cfg); end: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_iter_destroy(argpar_iter); + argpar_item_destroy(argpar_item); free(output); @@ -4487,14 +4649,15 @@ struct bt_config *bt_config_cli_args_create(int argc, const char *argv[], const bt_interrupter *interrupter) { struct bt_config *config = NULL; - int i; int top_level_argc; const char **top_level_argv; int command_argc = -1; const char **command_argv = NULL; const char *command_name = NULL; int default_log_level = -1; - struct argpar_parse_ret argpar_parse_ret = { 0 }; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; + const struct argpar_error *argpar_error = NULL; bt_value *plugin_paths = NULL; /* Top-level option descriptions. */ @@ -4555,93 +4718,128 @@ struct bt_config *bt_config_cli_args_create(int argc, const char *argv[], /* Skip first argument, the name of the program. */ top_level_argc = argc - 1; top_level_argv = argv + 1; - argpar_parse_ret = argpar_parse(top_level_argc, top_level_argv, - descrs, false); - if (argpar_parse_ret.error) { - BT_CLI_LOGE_APPEND_CAUSE( - "While parsing command-line arguments: %s", - argpar_parse_ret.error); + argpar_iter = argpar_iter_create(top_level_argc, top_level_argv, descrs); + if (!argpar_iter) { + BT_CLI_LOGE_APPEND_CAUSE_OOM(); goto error; } - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - struct argpar_item *item; + while (true) { + enum argpar_iter_next_status status; - item = argpar_parse_ret.items->items[i]; + ARGPAR_ITEM_DESTROY_AND_RESET(argpar_item); + status = argpar_iter_next(argpar_iter, &argpar_item, &argpar_error); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - struct argpar_item_opt *item_opt = - (struct argpar_item_opt *) item; + switch (status) { + case ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY: + BT_CLI_LOGE_APPEND_CAUSE_OOM(); + goto error; + case ARGPAR_ITER_NEXT_STATUS_ERROR: + { + if (argpar_error_type(argpar_error) + != ARGPAR_ERROR_TYPE_UNKNOWN_OPT) { + GString *err_str = format_arg_error(argpar_error, top_level_argv, + "While parsing command-line arguments"); + BT_CLI_LOGE_APPEND_CAUSE("%s", err_str->str); + g_string_free(err_str, TRUE); + goto error; + } - switch (item_opt->descr->id) { - case OPT_DEBUG: - default_log_level = - logging_level_min(default_log_level, BT_LOG_TRACE); - break; - case OPT_VERBOSE: - default_log_level = - logging_level_min(default_log_level, BT_LOG_INFO); - break; - case OPT_LOG_LEVEL: - { - int level = bt_log_get_level_from_string(item_opt->arg); + break; + } + default: + break; + } - if (level < 0) { - BT_CLI_LOGE_APPEND_CAUSE( - "Invalid argument for --log-level option:\n %s", - item_opt->arg); - goto error; - } + if (status == ARGPAR_ITER_NEXT_STATUS_END) { + break; + } - default_log_level = - logging_level_min(default_log_level, level); - break; + if (status == ARGPAR_ITER_NEXT_STATUS_ERROR) { + BT_ASSERT(argpar_error_type(argpar_error) == + ARGPAR_ERROR_TYPE_UNKNOWN_OPT); + /* + * Unknown option, assume this is implicitly the + * convert command, stop processing arguments. + */ + break; + } + + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *opt_descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); + + switch (opt_descr->id) { + case OPT_DEBUG: + default_log_level = + logging_level_min(default_log_level, BT_LOG_TRACE); + break; + case OPT_VERBOSE: + default_log_level = + logging_level_min(default_log_level, BT_LOG_INFO); + break; + case OPT_LOG_LEVEL: + { + int level = bt_log_get_level_from_string(arg); + + if (level < 0) { + BT_CLI_LOGE_APPEND_CAUSE( + "Invalid argument for --log-level option:\n %s", + arg); + goto error; } - case OPT_PLUGIN_PATH: - if (bt_config_append_plugin_paths_check_setuid_setgid( - plugin_paths, item_opt->arg)) { - goto error; - } - break; - case OPT_OMIT_SYSTEM_PLUGIN_PATH: - omit_system_plugin_path = true; - break; - case OPT_OMIT_HOME_PLUGIN_PATH: - omit_home_plugin_path = true; - break; - case OPT_VERSION: - print_version(); - goto end; - case OPT_HELP: - print_gen_usage(stdout); - goto end; + + default_log_level = + logging_level_min(default_log_level, level); + break; } - } else if (item->type == ARGPAR_ITEM_TYPE_NON_OPT) { - struct argpar_item_non_opt *item_non_opt = - (struct argpar_item_non_opt *) item; + case OPT_PLUGIN_PATH: + if (bt_config_append_plugin_paths_check_setuid_setgid( + plugin_paths, arg)) { + goto error; + } + break; + case OPT_OMIT_SYSTEM_PLUGIN_PATH: + omit_system_plugin_path = true; + break; + case OPT_OMIT_HOME_PLUGIN_PATH: + omit_home_plugin_path = true; + break; + case OPT_VERSION: + print_version(); + goto end; + case OPT_HELP: + print_gen_usage(stdout); + goto end; + default: + bt_common_abort(); + } + } else { + const char *arg = argpar_item_non_opt_arg(argpar_item); + unsigned int orig_index = argpar_item_non_opt_orig_index(argpar_item); + /* * First unknown argument: is it a known command * name? */ - command_argc = - top_level_argc - item_non_opt->orig_index - 1; - command_argv = - &top_level_argv[item_non_opt->orig_index + 1]; + command_argc = top_level_argc - orig_index - 1; + command_argv = &top_level_argv[orig_index + 1]; - if (strcmp(item_non_opt->arg, "convert") == 0) { + if (strcmp(arg, "convert") == 0) { command_type = COMMAND_TYPE_CONVERT; command_name = "convert"; - } else if (strcmp(item_non_opt->arg, "list-plugins") == 0) { + } else if (strcmp(arg, "list-plugins") == 0) { command_type = COMMAND_TYPE_LIST_PLUGINS; command_name = "list-plugins"; - } else if (strcmp(item_non_opt->arg, "help") == 0) { + } else if (strcmp(arg, "help") == 0) { command_type = COMMAND_TYPE_HELP; command_name = "help"; - } else if (strcmp(item_non_opt->arg, "query") == 0) { + } else if (strcmp(arg, "query") == 0) { command_type = COMMAND_TYPE_QUERY; command_name = "query"; - } else if (strcmp(item_non_opt->arg, "run") == 0) { + } else if (strcmp(arg, "run") == 0) { command_type = COMMAND_TYPE_RUN; command_name = "run"; } else { @@ -4655,12 +4853,16 @@ struct bt_config *bt_config_cli_args_create(int argc, const char *argv[], command_argc++; command_argv--; } + + /* Stop processing arguments. */ break; } } if (command_type == COMMAND_TYPE_NONE) { - if (argpar_parse_ret.ingested_orig_args == top_level_argc) { + unsigned int ingested_orig_args = argpar_iter_ingested_orig_args(argpar_iter); + + if (ingested_orig_args == top_level_argc) { /* * We only got non-help, non-version general options * like --verbose and --debug, without any other @@ -4677,10 +4879,8 @@ struct bt_config *bt_config_cli_args_create(int argc, const char *argv[], */ command_type = COMMAND_TYPE_CONVERT; command_name = "convert"; - command_argc = - top_level_argc - argpar_parse_ret.ingested_orig_args; - command_argv = - &top_level_argv[argpar_parse_ret.ingested_orig_args]; + command_argc = top_level_argc - ingested_orig_args; + command_argv = &top_level_argv[ingested_orig_args]; } BT_ASSERT(command_argv); @@ -4751,7 +4951,9 @@ error: *retcode = 1; end: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_error_destroy(argpar_error); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); bt_value_put_ref(plugin_paths); return config; } -- 2.34.1