cli: sync argpar - adjust to iterator API
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 21 May 2021 03:12:58 +0000 (23:12 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Fri, 27 Aug 2021 01:26:11 +0000 (21:26 -0400)
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 <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/5928
Tested-by: jenkins <jenkins@lttng.org>
src/argpar/argpar.c
src/argpar/argpar.h
src/cli/babeltrace2-cfg-cli-args.c

index cd916d6ef1d6fa56539c25a36b612fa365a80e55..10f6ee8d42e93a780a65ad4c5cf66347d667c42d 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * SPDX-License-Identifier: MIT
  *
- * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
+ * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
  */
 
 #include <assert.h>
 
 #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;
 }
index 00334cd6fb9d6f0c14a7fc08f5a21d114e778e65..27503c56fd56944207b4cf60869196bf1ae51c64 100644 (file)
 /*
  * SPDX-License-Identifier: MIT
  *
- * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
+ * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
  */
 
-#ifndef BABELTRACE_ARGPAR_H
-#define BABELTRACE_ARGPAR_H
+#ifndef ARGPAR_ARGPAR_H
+#define ARGPAR_ARGPAR_H
 
 #include <stdbool.h>
 
-/* 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:
+
+<ul>
+  <li>
+    Short options without an argument, possibly tied together:
+
+    @code{.unparsed}
+    -f -auf -n
+    @endcode
+
+  <li>
+    Short options with arguments:
+
+    @code{.unparsed}
+    -b 45 -f/mein/file -xyzhello
+    @endcode
+
+  <li>
+    Long options without an argument:
+
+    @code{.unparsed}
+    --five-guys --burger-king --pizza-hut --subway
+    @endcode
+
+  <li>
+    Long options with arguments (two original arguments or a single
+    one with a <code>=</code> character):
+
+    @code{.unparsed}
+    --security enable --time=18.56
+    @endcode
+
+  <li>
+    Non-option arguments (anything else, including
+    <code>-</code> and <code>\--</code>).
+
+    A non-option argument cannot have the form of an option, for example
+    if you need to pass the exact relative path
+    <code>\--component</code>. In that case, you would need to pass
+    <code>./\--component</code>. There's no generic way to escape
+    <code>-</code> as of this version.
+</ul>
+
+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 (<code>\--hello</code>).
+-# Option item (<code>\--count</code> with argument <code>23</code>).
+-# Non-option item (<code>/path/to/file</code>).
+-# Option item (<code>-a</code>).
+-# Option item (<code>-b</code>).
+-# Option item (<code>\--type</code> with argument <code>file</code>).
+-# Non-option item (<code>\--</code>).
+-# Non-option item (<code>magie</code>).
+*/
 
 /*
- * 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
+ * <https://gcc.gnu.org/wiki/Visibility>.
  */
 #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&nbsp;2 while the original
+argument index of \c mix is&nbsp;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&nbsp;0 while the original
+argument index of \c mix is&nbsp;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
+    (<code>const struct argpar_item *</code> 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 <code>-</code> or <code>\--</code>
+prefix.
+
+With the long option with argument form, for example
+<code>\--mireille=deyglun</code>, this function only returns the name
+part (<code>\--mireille</code> 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 <code>'\0'</code>
        const char short_name;
 
-       /* Long option name (without `--`), or `NULL` */
+       /// Long option name (without the <code>\--</code> 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 <code>(argc - 1)</code> as \p argc
+and <code>\&argv[1]</code> as \p argv from what <code>main()</code>
+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 */
index 37b29ec0ab197b44f0de24dcbdd90899bacded0b..86bf918e7a1e5218020c1cef158720f651327fea 100644 (file)
@@ -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;
 }
This page took 0.05814 seconds and 4 git commands to generate.