argpar: sync with upstream - adjust to iterator API
authorSimon Marchi <simon.marchi@efficios.com>
Fri, 20 Aug 2021 18:39:20 +0000 (14:39 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 16 Dec 2021 00:10:16 +0000 (19:10 -0500)
Sync with commit 143cec42e14e ("Force usage of ARGPAR_ASSERT() condition
when NDEBUG is defined").

The main change in this sync 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.  This was prototyped here (in lttng-tools), so this patch converts
the code to the API that was actually implemented in upstream argpar.

A difference between what we had and the current argpar API is that
argpar does not provide a formatted error string anymore.  It provides
an `argpar_error` object contaning all the raw information needed to
create such string.  The new `format_arg_error_v` function formats the
errors using the exact same syntax as argpar did, such that no changes
in the tests are necessary.

The new `parse_next_item` function factors out the code around calling
argpar_iter_next that would otherwise be duplicated at a few places.

These two new functions are placed into a new `argpar-utils` convenience
library.  I originally put them in the `libcommon.la` convenience
library, but that caused some parts of the code that don't do any
argument parsing (e.g. liblttng-ctl) to have to be linked against
argpar.  As a separate library, we can limit that to just the `lttng`
binary.

Change-Id: I94aa90ffcd93f52b6073c4cd7caca78cfd0f2e05
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
15 files changed:
configure.ac
src/bin/lttng/Makefile.am
src/bin/lttng/commands/add_trigger.cpp
src/bin/lttng/commands/list_triggers.cpp
src/bin/lttng/commands/remove_trigger.cpp
src/common/Makefile.am
src/common/argpar-utils/Makefile.am [new file with mode: 0644]
src/common/argpar-utils/argpar-utils.c [new file with mode: 0644]
src/common/argpar-utils/argpar-utils.h [new file with mode: 0644]
src/common/argpar/argpar.c
src/common/argpar/argpar.h
src/common/error.h
src/common/macros.h
src/common/string-utils/string-utils.cpp
src/common/string-utils/string-utils.h

index ae4374dca6188789550573a29978989e905b53d7..6166b25a0bc60a805ed60fa24d966ccb69f86d5c 100644 (file)
@@ -1143,6 +1143,7 @@ AC_CONFIG_FILES([
        src/Makefile
        src/common/Makefile
        src/common/argpar/Makefile
+       src/common/argpar-utils/Makefile
        src/common/bytecode/Makefile
        src/common/kernel-ctl/Makefile
        src/common/kernel-consumer/Makefile
index 5d01c45a8c14341e548260c2ad2ddc0b0aed2896..f1c04a3e94aef453f048dd21e53d9865a7f8f7f0 100644 (file)
@@ -43,5 +43,6 @@ lttng_LDADD = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \
                        $(top_builddir)/src/common/config/libconfig.la \
                        $(top_builddir)/src/common/string-utils/libstring-utils.la \
                        $(top_builddir)/src/common/filter/libfilter.la \
+                       $(top_builddir)/src/common/argpar-utils/libargpar-utils.la \
                        $(top_builddir)/src/common/argpar/libargpar.la \
                        $(POPT_LIBS)
index 939d510802eddf18420cb80e7d530e94a68cdcee..79303f6944087188f0935ae09119514ddf22953c 100644 (file)
@@ -8,12 +8,14 @@
 #include <ctype.h>
 #include <stdio.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "../command.h"
 #include "../loglevel.h"
 #include "../uprobe.h"
 
 #include "common/argpar/argpar.h"
+#include "common/argpar-utils/argpar-utils.h"
 #include "common/dynamic-array.h"
 #include "common/mi-lttng.h"
 #include "common/string-utils/string-utils.h"
@@ -651,9 +653,8 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
 {
        enum lttng_event_rule_type event_rule_type =
                        LTTNG_EVENT_RULE_TYPE_UNKNOWN;
-       struct argpar_state *state;
-       struct argpar_item *item = NULL;
-       char *error = NULL;
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        int consumed_args = -1;
        struct lttng_kernel_probe_location *kernel_probe_location = NULL;
        struct lttng_userspace_probe_location *userspace_probe_location = NULL;
@@ -685,74 +686,67 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
 
        lttng_dynamic_pointer_array_init(&exclude_names, free);
 
-       state = argpar_state_create(*argc, *argv, event_rule_opt_descrs);
-       if (!state) {
-               ERR("Failed to allocate an argpar state.");
+       argpar_iter = argpar_iter_create(*argc, *argv, event_rule_opt_descrs);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
        while (true) {
-               enum argpar_state_parse_next_status status;
+               enum parse_next_item_status status;
 
-               ARGPAR_ITEM_DESTROY_AND_RESET(item);
-               status = argpar_state_parse_next(state, &item, &error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       ERR("%s", error);
+               status = parse_next_item(argpar_iter, &argpar_item, *argv,
+                       false, NULL);
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
                        goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       /* Just stop parsing here. */
-                       break;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
                        break;
                }
 
-               LTTNG_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
+               LTTNG_ASSERT(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
+                       const char *arg = argpar_item_opt_arg(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_TYPE:
-                               if (!assign_event_rule_type(&event_rule_type,
-                                               item_opt->arg)) {
+                               if (!assign_event_rule_type(&event_rule_type, arg)) {
                                        goto error;
                                }
 
                                /* Save the string for later use. */
-                               if (!assign_string(&event_rule_type_str,
-                                                   item_opt->arg,
-                                                   "--type/-t")) {
+                               if (!assign_string(&event_rule_type_str, arg,
+                                               "--type/-t")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_LOCATION:
-                               if (!assign_string(&location,
-                                               item_opt->arg,
+                               if (!assign_string(&location, arg,
                                                "--location/-L")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_EVENT_NAME:
-                               if (!assign_string(&event_name,
-                                                   item_opt->arg,
-                                                   "--event-name/-E")) {
+                               if (!assign_string(&event_name, arg,
+                                               "--event-name/-E")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_FILTER:
-                               if (!assign_string(&filter, item_opt->arg,
-                                                   "--filter/-f")) {
+                               if (!assign_string(&filter, arg,
+                                               "--filter/-f")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_NAME:
-                               if (!assign_string(&name, item_opt->arg,
-                                                   "--name/-n")) {
+                               if (!assign_string(&name, arg,
+                                               "--name/-n")) {
                                        goto error;
                                }
 
@@ -763,7 +757,7 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
 
                                ret = lttng_dynamic_pointer_array_add_pointer(
                                                &exclude_names,
-                                               strdup(item_opt->arg));
+                                               strdup(arg));
                                if (ret != 0) {
                                        ERR("Failed to add pointer to dynamic pointer array.");
                                        goto error;
@@ -772,8 +766,8 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
                                break;
                        }
                        case OPT_LOG_LEVEL:
-                               if (!assign_string(&log_level_str,
-                                                   item_opt->arg, "--log-level/-l")) {
+                               if (!assign_string(&log_level_str, arg,
+                                               "--log-level/-l")) {
                                        goto error;
                                }
 
@@ -781,19 +775,16 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
                        case OPT_CAPTURE:
                        {
                                int ret;
-                               const char *capture_str = item_opt->arg;
 
                                ret = filter_parser_ctx_create_from_filter_expression(
-                                               capture_str, &parser_ctx);
+                                               arg, &parser_ctx);
                                if (ret) {
-                                       ERR("Failed to parse capture expression `%s`.",
-                                                       capture_str);
+                                       ERR("Failed to parse capture expression `%s`.", arg);
                                        goto error;
                                }
 
                                event_expr = ir_op_root_to_event_expr(
-                                               parser_ctx->ir_root,
-                                               capture_str);
+                                               parser_ctx->ir_root, arg);
                                if (!event_expr) {
                                        /*
                                         * ir_op_root_to_event_expr has printed
@@ -821,12 +812,10 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
                                abort();
                        }
                } else {
-                       const struct argpar_item_non_opt *item_non_opt =
-                                       (const struct argpar_item_non_opt *)
-                                                       item;
+                       const char *arg = argpar_item_non_opt_arg(argpar_item);
 
                        /* Don't accept non-option arguments. */
-                       ERR("Unexpected argument '%s'", item_non_opt->arg);
+                       ERR("Unexpected argument '%s'", arg);
                        goto error;
                }
        }
@@ -909,7 +898,7 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv)
        /*
         * Update *argc and *argv so our caller can keep parsing what follows.
         */
-       consumed_args = argpar_state_get_ingested_orig_args(state);
+       consumed_args = argpar_iter_ingested_orig_args(argpar_iter);
        LTTNG_ASSERT(consumed_args >= 0);
        *argc -= consumed_args;
        *argv += consumed_args;
@@ -1341,9 +1330,8 @@ end:
        }
 
        lttng_event_expr_destroy(event_expr);
-       argpar_item_destroy(item);
-       free(error);
-       argpar_state_destroy(state);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        free(filter);
        free(name);
        lttng_dynamic_pointer_array_reset(&exclude_names);
@@ -1539,42 +1527,38 @@ static
 struct lttng_action *handle_action_notify(int *argc, const char ***argv)
 {
        struct lttng_action *action = NULL;
-       struct argpar_state *state = NULL;
-       struct argpar_item *item = NULL;
-       char *error = NULL;
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        struct lttng_rate_policy *policy = NULL;
 
-       state = argpar_state_create(*argc, *argv, notify_action_opt_descrs);
-       if (!state) {
-               ERR("Failed to allocate an argpar state.");
+       argpar_iter = argpar_iter_create(*argc, *argv, notify_action_opt_descrs);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
        while (true) {
-               enum argpar_state_parse_next_status status;
+               enum parse_next_item_status status;
 
-               ARGPAR_ITEM_DESTROY_AND_RESET(item);
-               status = argpar_state_parse_next(state, &item, &error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       ERR("%s", error);
+               status = parse_next_item(argpar_iter, &argpar_item, *argv,
+                       false, "While parsing `notify` action:");
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
                        goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       /* Just stop parsing here. */
-                       break;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
                        break;
                }
 
-               LTTNG_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
+               LTTNG_ASSERT(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
+                       const char *arg = argpar_item_opt_arg(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_RATE_POLICY:
                        {
-                               policy = parse_rate_policy(item_opt->arg);
+                               policy = parse_rate_policy(arg);
                                if (!policy) {
                                        goto error;
                                }
@@ -1584,23 +1568,15 @@ struct lttng_action *handle_action_notify(int *argc, const char ***argv)
                                abort();
                        }
                } else {
-                       const struct argpar_item_non_opt *item_non_opt;
-
-                       LTTNG_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT);
-
-                       item_non_opt = (const struct argpar_item_non_opt *) item;
+                       const char *arg = argpar_item_non_opt_arg(argpar_item);
 
-                       switch (item_non_opt->non_opt_index) {
-                       default:
-                               ERR("Unexpected argument `%s`.",
-                                               item_non_opt->arg);
-                               goto error;
-                       }
+                       ERR("Unexpected argument `%s`.", arg);
+                       goto error;
                }
        }
 
-       *argc -= argpar_state_get_ingested_orig_args(state);
-       *argv += argpar_state_get_ingested_orig_args(state);
+       *argc -= argpar_iter_ingested_orig_args(argpar_iter);
+       *argv += argpar_iter_ingested_orig_args(argpar_iter);
 
        action = lttng_action_notify_create();
        if (!action) {
@@ -1623,10 +1599,9 @@ error:
        lttng_action_destroy(action);
        action = NULL;
 end:
-       free(error);
        lttng_rate_policy_destroy(policy);
-       argpar_state_destroy(state);
-       argpar_item_destroy(item);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        return action;
 }
 
@@ -1646,10 +1621,9 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc,
                const char *action_name)
 {
        struct lttng_action *action = NULL;
-       struct argpar_state *state = NULL;
-       struct argpar_item *item = NULL;
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        const char *session_name_arg = NULL;
-       char *error = NULL;
        enum lttng_action_status action_status;
        struct lttng_rate_policy *policy = NULL;
 
@@ -1661,37 +1635,34 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc,
                ARGPAR_OPT_DESCR_SENTINEL
        };
 
-       state = argpar_state_create(*argc, *argv, rate_policy_opt_descrs);
-       if (!state) {
-               ERR("Failed to allocate an argpar state.");
+       argpar_iter = argpar_iter_create(*argc, *argv, rate_policy_opt_descrs);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
        while (true) {
-               enum argpar_state_parse_next_status status;
+               enum parse_next_item_status status;
 
-               ARGPAR_ITEM_DESTROY_AND_RESET(item);
-               status = argpar_state_parse_next(state, &item, &error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       ERR("%s", error);
+               status = parse_next_item(argpar_iter, &argpar_item, *argv,
+                       false, "While parsing `%s` action:", action_name);
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
                        goto error;
-               } else if (status ==
-                               ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       /* Just stop parsing here. */
-                       break;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
                        break;
                }
 
-               LTTNG_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               LTTNG_ASSERT(status == PARSE_NEXT_ITEM_STATUS_OK);
+
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
+                       const char *arg = argpar_item_opt_arg(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_RATE_POLICY:
                        {
-                               policy = parse_rate_policy(item_opt->arg);
+                               policy = parse_rate_policy(arg);
                                if (!policy) {
                                        goto error;
                                }
@@ -1701,23 +1672,22 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc,
                                abort();
                        }
                } else {
-                       const struct argpar_item_non_opt *item_non_opt;
-                       item_non_opt = (const struct argpar_item_non_opt *) item;
+                       const char *arg = argpar_item_non_opt_arg(argpar_item);
+                       unsigned int idx = argpar_item_non_opt_non_opt_index(argpar_item);
 
-                       switch (item_non_opt->non_opt_index) {
+                       switch (idx) {
                        case 0:
-                               session_name_arg = item_non_opt->arg;
+                               session_name_arg = arg;
                                break;
                        default:
-                               ERR("Unexpected argument `%s`.",
-                                               item_non_opt->arg);
+                               ERR("Unexpected argument `%s`.", arg);
                                goto error;
                        }
                }
        }
 
-       *argc -= argpar_state_get_ingested_orig_args(state);
-       *argv += argpar_state_get_ingested_orig_args(state);
+       *argc -= argpar_iter_ingested_orig_args(argpar_iter);
+       *argv += argpar_iter_ingested_orig_args(argpar_iter);
 
        if (!session_name_arg) {
                ERR("Missing session name.");
@@ -1750,11 +1720,11 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc,
 error:
        lttng_action_destroy(action);
        action = NULL;
-       argpar_item_destroy(item);
+
 end:
        lttng_rate_policy_destroy(policy);
-       free(error);
-       argpar_state_destroy(state);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        return action;
 }
 
@@ -1805,8 +1775,8 @@ struct lttng_action *handle_action_snapshot_session(int *argc,
                const char ***argv)
 {
        struct lttng_action *action = NULL;
-       struct argpar_state *state = NULL;
-       struct argpar_item *item = NULL;
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        const char *session_name_arg = NULL;
        char *snapshot_name_arg = NULL;
        char *ctrl_url_arg = NULL;
@@ -1821,73 +1791,70 @@ struct lttng_action *handle_action_snapshot_session(int *argc,
        int ret;
        unsigned int locations_specified = 0;
 
-       state = argpar_state_create(*argc, *argv, snapshot_action_opt_descrs);
-       if (!state) {
-               ERR("Failed to allocate an argpar state.");
+       argpar_iter = argpar_iter_create(*argc, *argv, snapshot_action_opt_descrs);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
        while (true) {
-               enum argpar_state_parse_next_status status;
+               enum parse_next_item_status status;
 
-               ARGPAR_ITEM_DESTROY_AND_RESET(item);
-               status = argpar_state_parse_next(state, &item, &error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       ERR("%s", error);
+               status = parse_next_item(argpar_iter, &argpar_item, *argv,
+                       false, "While parsing `snapshot` action:");
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
                        goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       /* Just stop parsing here. */
-                       break;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
                        break;
                }
 
-               LTTNG_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
+               LTTNG_ASSERT(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
+                       const char *arg = argpar_item_opt_arg(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_NAME:
-                               if (!assign_string(&snapshot_name_arg, item_opt->arg, "--name/-n")) {
+                               if (!assign_string(&snapshot_name_arg, arg, "--name/-n")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_MAX_SIZE:
-                               if (!assign_string(&max_size_arg, item_opt->arg, "--max-size/-m")) {
+                               if (!assign_string(&max_size_arg, arg, "--max-size/-m")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_CTRL_URL:
-                               if (!assign_string(&ctrl_url_arg, item_opt->arg, "--ctrl-url")) {
+                               if (!assign_string(&ctrl_url_arg, arg, "--ctrl-url")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_DATA_URL:
-                               if (!assign_string(&data_url_arg, item_opt->arg, "--data-url")) {
+                               if (!assign_string(&data_url_arg, arg, "--data-url")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_URL:
-                               if (!assign_string(&url_arg, item_opt->arg, "--url")) {
+                               if (!assign_string(&url_arg, arg, "--url")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_PATH:
-                               if (!assign_string(&path_arg, item_opt->arg, "--path")) {
+                               if (!assign_string(&path_arg, arg, "--path")) {
                                        goto error;
                                }
 
                                break;
                        case OPT_RATE_POLICY:
                        {
-                               policy = parse_rate_policy(item_opt->arg);
+                               policy = parse_rate_policy(arg);
                                if (!policy) {
                                        goto error;
                                }
@@ -1897,26 +1864,22 @@ struct lttng_action *handle_action_snapshot_session(int *argc,
                                abort();
                        }
                } else {
-                       const struct argpar_item_non_opt *item_non_opt;
-
-                       LTTNG_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT);
-
-                       item_non_opt = (const struct argpar_item_non_opt *) item;
+                       const char *arg = argpar_item_non_opt_arg(argpar_item);
+                       const unsigned int idx = argpar_item_non_opt_non_opt_index(argpar_item);
 
-                       switch (item_non_opt->non_opt_index) {
+                       switch (idx) {
                        case 0:
-                               session_name_arg = item_non_opt->arg;
+                               session_name_arg = arg;
                                break;
                        default:
-                               ERR("Unexpected argument `%s`.",
-                                               item_non_opt->arg);
+                               ERR("Unexpected argument `%s`.", arg);
                                goto error;
                        }
                }
        }
 
-       *argc -= argpar_state_get_ingested_orig_args(state);
-       *argv += argpar_state_get_ingested_orig_args(state);
+       *argc -= argpar_iter_ingested_orig_args(argpar_iter);
+       *argv += argpar_iter_ingested_orig_args(argpar_iter);
 
        if (!session_name_arg) {
                ERR("Missing session name.");
@@ -2102,8 +2065,8 @@ end:
        free(snapshot_output);
        free(max_size_arg);
        lttng_rate_policy_destroy(policy);
-       argpar_state_destroy(state);
-       argpar_item_destroy(item);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        return action;
 }
 
@@ -2179,12 +2142,11 @@ int cmd_add_trigger(int argc, const char **argv)
        const char **my_argv = argv + 1;
        struct lttng_condition *condition = NULL;
        struct lttng_dynamic_pointer_array actions;
-       struct argpar_state *argpar_state = NULL;
-       struct argpar_item *argpar_item = NULL;
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        struct lttng_action *action_list = NULL;
        struct lttng_action *action = NULL;
        struct lttng_trigger *trigger = NULL;
-       char *error = NULL;
        char *name = NULL;
        int i;
        char *owner_uid = NULL;
@@ -2219,50 +2181,44 @@ int cmd_add_trigger(int argc, const char **argv)
        }
 
        while (true) {
-               enum argpar_state_parse_next_status status;
-               const struct argpar_item_opt *item_opt;
+               enum parse_next_item_status status;
                int ingested_args;
+               const struct argpar_opt_descr *descr;
+               const char *arg;
 
-               argpar_state_destroy(argpar_state);
-               argpar_state = argpar_state_create(my_argc, my_argv,
+               argpar_iter_destroy(argpar_iter);
+               argpar_iter = argpar_iter_create(my_argc, my_argv,
                        add_trigger_options);
-               if (!argpar_state) {
-                       ERR("Failed to create argpar state.");
+               if (!argpar_iter) {
+                       ERR("Failed to create argpar iter.");
                        goto error;
                }
 
-               ARGPAR_ITEM_DESTROY_AND_RESET(argpar_item);
-               status = argpar_state_parse_next(argpar_state, &argpar_item, &error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       ERR("%s", error);
+               status = parse_next_item(argpar_iter, &argpar_item, my_argv,
+                       true, NULL);
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
                        goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       ERR("%s", error);
-                       goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
                        break;
                }
 
-               LTTNG_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
-
-               if (argpar_item->type == ARGPAR_ITEM_TYPE_NON_OPT) {
-                       const struct argpar_item_non_opt *item_non_opt =
-                                       (const struct argpar_item_non_opt *)
-                                                       argpar_item;
+               LTTNG_ASSERT(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-                       ERR("Unexpected argument `%s`.", item_non_opt->arg);
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_NON_OPT) {
+                       ERR("Unexpected argument `%s`.",
+                               argpar_item_non_opt_arg(argpar_item));
                        goto error;
                }
 
-               item_opt = (const struct argpar_item_opt *) argpar_item;
-
-               ingested_args = argpar_state_get_ingested_orig_args(
-                               argpar_state);
+               ingested_args = argpar_iter_ingested_orig_args(argpar_iter);
 
                my_argc -= ingested_args;
                my_argv += ingested_args;
 
-               switch (item_opt->descr->id) {
+               descr = argpar_item_opt_descr(argpar_item);
+               arg = argpar_item_opt_arg(argpar_item);
+
+               switch (descr->id) {
                case OPT_HELP:
                        SHOW_HELP();
                        ret = 0;
@@ -2278,7 +2234,7 @@ int cmd_add_trigger(int argc, const char **argv)
                                goto error;
                        }
 
-                       condition = parse_condition(item_opt->arg, &my_argc, &my_argv);
+                       condition = parse_condition(arg, &my_argc, &my_argv);
                        if (!condition) {
                                /*
                                 * An error message was already printed by
@@ -2291,7 +2247,7 @@ int cmd_add_trigger(int argc, const char **argv)
                }
                case OPT_ACTION:
                {
-                       action = parse_action(item_opt->arg, &my_argc, &my_argv);
+                       action = parse_action(arg, &my_argc, &my_argv);
                        if (!action) {
                                /*
                                 * An error message was already printed by
@@ -2314,7 +2270,7 @@ int cmd_add_trigger(int argc, const char **argv)
                }
                case OPT_NAME:
                {
-                       if (!assign_string(&name, item_opt->arg, "--name")) {
+                       if (!assign_string(&name, arg, "--name")) {
                                goto error;
                        }
 
@@ -2322,7 +2278,7 @@ int cmd_add_trigger(int argc, const char **argv)
                }
                case OPT_OWNER_UID:
                {
-                       if (!assign_string(&owner_uid, item_opt->arg,
+                       if (!assign_string(&owner_uid, arg,
                                        "--owner-uid")) {
                                goto error;
                        }
@@ -2456,14 +2412,13 @@ end:
        }
 
 cleanup:
-       argpar_state_destroy(argpar_state);
+       argpar_iter_destroy(argpar_iter);
        argpar_item_destroy(argpar_item);
        lttng_dynamic_pointer_array_reset(&actions);
        lttng_condition_destroy(condition);
        lttng_action_destroy(action_list);
        lttng_action_destroy(action);
        lttng_trigger_destroy(trigger);
-       free(error);
        free(name);
        free(owner_uid);
        if (mi_writer && mi_lttng_writer_destroy(mi_writer)) {
index 9a57714b6f9cfa1ba9a5a073da37732278e82489..f7798b15033f6285ddda88ea06fca61fd9be188e 100644 (file)
@@ -10,6 +10,7 @@
 #include "../command.h"
 
 #include "common/argpar/argpar.h"
+#include "common/argpar-utils/argpar-utils.h"
 #include "common/dynamic-array.h"
 #include "common/mi-lttng.h"
 /* For lttng_condition_type_str(). */
@@ -1317,27 +1318,38 @@ static enum lttng_error_code mi_error_query_condition_callback(
 int cmd_list_triggers(int argc, const char **argv)
 {
        int ret;
-       struct argpar_parse_ret argpar_parse_ret = {};
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        struct lttng_triggers *triggers = NULL;
-       int i;
        struct mi_writer *mi_writer = NULL;
 
-       argpar_parse_ret = argpar_parse(
-                       argc - 1, argv + 1, list_trigger_options, true);
-       if (!argpar_parse_ret.items) {
-               ERR("%s", argpar_parse_ret.error);
+       argc--;
+       argv++;
+
+       argpar_iter = argpar_iter_create(argc, argv, list_trigger_options);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
-       for (i = 0; i < argpar_parse_ret.items->n_items; i++) {
-               const struct argpar_item *item =
-                               argpar_parse_ret.items->items[i];
+       while (true) {
+               enum parse_next_item_status status;
+
+               status = parse_next_item(argpar_iter, &argpar_item, argv,
+                       true, NULL);
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
+                       goto error;
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
+                       break;
+               }
+
+               assert(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_HELP:
                                SHOW_HELP();
                                ret = 0;
@@ -1354,10 +1366,8 @@ int cmd_list_triggers(int argc, const char **argv)
                        }
 
                } else {
-                       const struct argpar_item_non_opt *item_non_opt =
-                               (const struct argpar_item_non_opt *) item;
-
-                       ERR("Unexpected argument: %s", item_non_opt->arg);
+                       ERR("Unexpected argument: %s",
+                               argpar_item_non_opt_arg(argpar_item));
                }
        }
 
@@ -1438,7 +1448,8 @@ error:
        ret = 1;
 
 end:
-       argpar_parse_ret_fini(&argpar_parse_ret);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        lttng_triggers_destroy(triggers);
        /* Mi clean-up. */
        if (mi_writer && mi_lttng_writer_destroy(mi_writer)) {
index 2ac237c3a872ee7449a065d403f601458f615794..0c817d188bbfba83fcb9d0cd9506fbb2d4503d1e 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "../command.h"
 #include "common/argpar/argpar.h"
+#include "common/argpar-utils/argpar-utils.h"
 #include "common/mi-lttng.h"
 #include <lttng/lttng.h>
 #include <stdio.h>
@@ -61,7 +62,8 @@ int cmd_remove_trigger(int argc, const char **argv)
 {
        enum lttng_error_code ret_code;
        int ret;
-       struct argpar_parse_ret argpar_parse_ret = {};
+       struct argpar_iter *argpar_iter = NULL;
+       const struct argpar_item *argpar_item = NULL;
        const char *name = NULL;
        int i;
        struct lttng_triggers *triggers = NULL;
@@ -97,22 +99,34 @@ int cmd_remove_trigger(int argc, const char **argv)
                }
        }
 
-       argpar_parse_ret = argpar_parse(argc - 1, argv + 1,
-               remove_trigger_options, true);
-       if (!argpar_parse_ret.items) {
-               ERR("%s", argpar_parse_ret.error);
+       argc--;
+       argv++;
+
+       argpar_iter = argpar_iter_create(argc, argv, remove_trigger_options);
+       if (!argpar_iter) {
+               ERR("Failed to allocate an argpar iter.");
                goto error;
        }
 
-       for (i = 0; i < argpar_parse_ret.items->n_items; i++) {
-               const struct argpar_item *item =
-                               argpar_parse_ret.items->items[i];
+       while (true) {
+               enum parse_next_item_status status;
+
+               status = parse_next_item(argpar_iter, &argpar_item, argv,
+                       true, NULL);
+               if (status == PARSE_NEXT_ITEM_STATUS_ERROR) {
+                       goto error;
+               } else if (status == PARSE_NEXT_ITEM_STATUS_END) {
+                       break;
+               }
+
+               assert(status == PARSE_NEXT_ITEM_STATUS_OK);
 
-               if (item->type == ARGPAR_ITEM_TYPE_OPT) {
-                       const struct argpar_item_opt *item_opt =
-                                       (const struct argpar_item_opt *) item;
+               if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) {
+                       const struct argpar_opt_descr *descr =
+                               argpar_item_opt_descr(argpar_item);
+                       const char *arg = argpar_item_opt_arg(argpar_item);
 
-                       switch (item_opt->descr->id) {
+                       switch (descr->id) {
                        case OPT_HELP:
                                SHOW_HELP();
                                ret = 0;
@@ -124,7 +138,7 @@ int cmd_remove_trigger(int argc, const char **argv)
                                goto end;
                        case OPT_OWNER_UID:
                        {
-                               if (!assign_string(&owner_uid, item_opt->arg,
+                               if (!assign_string(&owner_uid, arg,
                                                "--owner-uid")) {
                                        goto error;
                                }
@@ -134,15 +148,14 @@ int cmd_remove_trigger(int argc, const char **argv)
                                abort();
                        }
                } else {
-                       const struct argpar_item_non_opt *item_non_opt =
-                                       (const struct argpar_item_non_opt *) item;
+                       const char *arg = argpar_item_non_opt_arg(argpar_item);
 
                        if (name) {
-                               ERR("Unexpected argument '%s'", item_non_opt->arg);
+                               ERR("Unexpected argument '%s'", arg);
                                goto error;
                        }
 
-                       name = item_non_opt->arg;
+                       name = arg;
                }
        }
 
@@ -251,7 +264,8 @@ end:
        }
 
 cleanup:
-       argpar_parse_ret_fini(&argpar_parse_ret);
+       argpar_item_destroy(argpar_item);
+       argpar_iter_destroy(argpar_iter);
        lttng_triggers_destroy(triggers);
        free(owner_uid);
 
index a12edcb58681ca2769ab9053ea870e79e8ed8397..7ac2b09ccc9eaca9c35c28eb8621b384405d68a0 100644 (file)
@@ -11,7 +11,8 @@ SUBDIRS = \
        fd-tracker \
        string-utils \
        filter \
-       hashtable
+       hashtable \
+       argpar-utils
 
 # Make sure to always distribute all folders
 # since SUBDIRS is decided at configure time.
@@ -33,7 +34,8 @@ DIST_SUBDIRS = \
        fd-tracker \
        bytecode \
        filter \
-       argpar
+       argpar \
+       argpar-utils
 
 # Common library
 noinst_LTLIBRARIES = libcommon.la
diff --git a/src/common/argpar-utils/Makefile.am b/src/common/argpar-utils/Makefile.am
new file mode 100644 (file)
index 0000000..00d31c9
--- /dev/null
@@ -0,0 +1,5 @@
+noinst_LTLIBRARIES = libargpar-utils.la
+
+libargpar_utils_la_SOURCES = \
+       argpar-utils.c \
+       argpar-utils.h
diff --git a/src/common/argpar-utils/argpar-utils.c b/src/common/argpar-utils/argpar-utils.c
new file mode 100644 (file)
index 0000000..1f1f38b
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 Simon Marchi <simon.marchi@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include "argpar-utils.h"
+
+#include <stdio.h>
+
+#include <common/error.h>
+#include <common/string-utils/string-utils.h>
+
+#define WHILE_PARSING_ARG_N_ARG_FMT "While parsing argument #%d (`%s`): "
+
+/*
+ * 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.
+ *
+ * `context_fmt`, if non-NULL, is formatted using `args` and prepended to the
+ * error message.
+ *
+ * The returned string must be freed by the caller.
+ */
+static ATTR_FORMAT_PRINTF(3, 0)
+char *format_arg_error_v(const struct argpar_error *error,
+               const char **argv, const char *context_fmt, va_list args)
+{
+       char *str = NULL;
+       char *str_ret = NULL;
+       int ret;
+
+       if (context_fmt) {
+               ret = vasprintf(&str, context_fmt, args);
+               if (ret == -1) {
+                       /*
+                        * If vasprintf fails, the content of str is undefined,
+                        * and we shouldn't try to free it.
+                        */
+                       str = NULL;
+                       goto end;
+               }
+
+               ret = strutils_append_str(&str, ": ");
+               if (ret < 0) {
+                       goto end;
+               }
+       }
+
+       switch (argpar_error_type(error))
+       {
+       case ARGPAR_ERROR_TYPE_MISSING_OPT_ARG:
+       {
+               int orig_index = argpar_error_orig_index(error);
+               const char *arg = argv[orig_index];
+
+               ret = strutils_appendf(&str,
+                       WHILE_PARSING_ARG_N_ARG_FMT "Missing required argument for option `%s`",
+                       orig_index + 1, arg, arg);
+               if (ret < 0) {
+                       goto end;
+               }
+
+               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) {
+                       ret = strutils_appendf(&str,
+                               WHILE_PARSING_ARG_N_ARG_FMT "Unexpected argument for option `-%c`",
+                               orig_index + 1, arg, descr->short_name);
+               } else {
+                       ret = strutils_appendf(&str,
+                               WHILE_PARSING_ARG_N_ARG_FMT "Unexpected argument for option `--%s`",
+                               orig_index + 1, arg, descr->long_name);
+               }
+
+               if (ret < 0) {
+                       goto end;
+               }
+
+               break;
+       }
+       case ARGPAR_ERROR_TYPE_UNKNOWN_OPT:
+       {
+               const char *unknown_opt = argpar_error_unknown_opt_name(error);
+
+               ret = strutils_appendf(&str,
+                       "Unknown option `%s`", unknown_opt);
+
+               if (ret < 0) {
+                       goto end;
+               }
+
+               break;
+       }
+       default:
+               abort ();
+       }
+
+       str_ret = str;
+       str = NULL;
+
+end:
+       free(str);
+       return str_ret;
+}
+
+enum parse_next_item_status parse_next_item(struct argpar_iter *iter,
+               const struct argpar_item **item, const char **argv,
+               bool unknown_opt_is_error, const char *context_fmt, ...)
+{
+       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:
+               ERR("Failed to get next argpar item.");
+               ret = PARSE_NEXT_ITEM_STATUS_ERROR;
+               break;
+       case ARGPAR_ITER_NEXT_STATUS_ERROR:
+       {
+               va_list args;
+               char *err_str;
+
+               if (argpar_error_type(error) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT &&
+                               !unknown_opt_is_error) {
+                       ret = PARSE_NEXT_ITEM_STATUS_END;
+                       break;
+               }
+
+               va_start(args, context_fmt);
+               err_str = format_arg_error_v(error, argv, context_fmt, args);
+               va_end(args);
+
+               if (err_str) {
+                       ERR("%s", err_str);
+                       free(err_str);
+               } else {
+                       ERR("%s", "Failed to format argpar error.");
+               }
+
+               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:
+               abort();
+       }
+
+       argpar_error_destroy(error);
+
+       return ret;
+}
diff --git a/src/common/argpar-utils/argpar-utils.h b/src/common/argpar-utils/argpar-utils.h
new file mode 100644 (file)
index 0000000..b9ae510
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 Simon Marchi <simon.marchi@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#ifndef COMMON_ARGPAR_UTILS_H
+#define COMMON_ARGPAR_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <common/macros.h>
+#include <common/argpar/argpar.h>
+#include <common/string-utils/format.h>
+
+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 argpar item using `iter`.
+ *
+ * The item in `*item` is always freed and cleared on entry.
+ *
+ * If an item is parsed successfully, return the new item in `*item` and return
+ * PARSE_NEXT_ITEM_STATUS_OK.
+ *
+ * If the end of the argument list is reached, return
+ * PARSE_NEXT_ITEM_STATUS_END.
+ *
+ * On error, print a descriptive error message and return
+ * PARSE_NEXT_ITEM_STATUS_ERROR.  If `context_fmt` is non-NULL, it is formatted
+ * using the following arguments and prepended to the error message.
+ *
+ * If `unknown_opt_is_error` is true, an unknown option is considered an error.
+ * Otherwise, it is considered as the end of the argument list.
+ */
+ATTR_FORMAT_PRINTF(5, 6)
+enum parse_next_item_status parse_next_item(struct argpar_iter *iter,
+               const struct argpar_item **item, const char **argv,
+               bool unknown_opt_is_error, const char *context_fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
index 21edec0ab7a85b23c26ab38ee2956c00450a1626..82b561ceaae707002f36754c55e828d35b882206 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 <stdarg.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)  ((void) sizeof((void) (_cond), 0))
+#define ARGPAR_CALLOC(_type, _nmemb)                                   \
+       ((_type *) calloc((_nmemb), sizeof(_type)))
 
-#ifdef __MINGW_PRINTF_FORMAT
-# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
+#define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1)
+
+#ifdef NDEBUG
+/*
+ * Force usage of the assertion condition to prevent unused variable warnings
+ * when `assert()` are disabled by the `NDEBUG` definition.
+ */
+# define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
 #else
-# define ARGPAR_PRINTF_FORMAT printf
+# include <assert.h>
+# define ARGPAR_ASSERT(_cond) assert(_cond)
 #endif
 
 /*
- * Structure holding the argpar state between successive argpar_state_parse_next
- * calls.
+ * An argpar iterator.
  *
- * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`.
+ * Such a structure contains the state of an iterator between calls to
+ * argpar_iter_next().
  */
-struct argpar_state {
+struct argpar_iter {
        /*
-        * Data provided by the user in argpar_state_create, does not change
+        * Data provided by the user to argpar_iter_create(); immutable
         * afterwards.
         */
-       unsigned int argc;
-       const char * const *argv;
-       const struct argpar_opt_descr *descrs;
+       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_state_parse_next
-        * call.
+        * Index of the argument to process in the next
+        * argpar_iter_next() call.
         */
        unsigned int i;
 
-       /* Counter of non-option arguments. */
+       /* Counter of non-option arguments */
        int non_opt_index;
 
        /*
-        * Short option state: if set, we are in the middle of a short option
-        * group, so we should resume there at the next argpar_state_parse_next
+        * 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_ch;
-};
+       const char *short_opt_group_ch;
 
-static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
-char *argpar_vasprintf(const char *fmt, va_list args)
-{
-       int len1, len2;
-       char *str;
-       va_list args2;
-
-       va_copy(args2, args);
-
-       len1 = vsnprintf(NULL, 0, fmt, args);
-       if (len1 < 0) {
-               str = NULL;
-               goto end;
-       }
-
-       str = malloc(len1 + 1);
-       if (!str) {
-               goto end;
-       }
-
-       len2 = vsnprintf(str, len1 + 1, fmt, args2);
-
-       ARGPAR_ASSERT(len1 == len2);
+       /* 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;
 }
 
 ARGPAR_HIDDEN
-void argpar_item_destroy(struct argpar_item *item)
+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++) {
-                       argpar_item_destroy(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;
@@ -262,13 +235,20 @@ 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;
@@ -283,6 +263,126 @@ end:
        return non_opt_item;
 }
 
+/*
+ * If `error` is not `NULL`, sets the error `error` to a new parsing
+ * error object, setting its `unknown_opt_name`, `opt_descr`, and
+ * `is_short` members from the parameters.
+ *
+ * `unknown_opt_name` is the unknown option name without any `-` or `--`
+ * prefix: `is_short` controls which type of unknown option it is.
+ *
+ * Returns 0 on success (including if `error` is `NULL`) or -1 on memory
+ * error.
+ */
+static
+int set_error(struct argpar_error ** const error,
+               enum argpar_error_type type,
+               const char * const unknown_opt_name,
+               const struct argpar_opt_descr * const opt_descr,
+               const bool is_short)
+{
+       int ret = 0;
+
+       if (!error) {
+               goto end;
+       }
+
+       *error = ARGPAR_ZALLOC(struct argpar_error);
+       if (!*error) {
+               goto error;
+       }
+
+       (*error)->type = type;
+
+       if (unknown_opt_name) {
+               (*error)->unknown_opt_name = ARGPAR_CALLOC(char,
+                       strlen(unknown_opt_name) + 1 + (is_short ? 1 : 2));
+               if (!(*error)->unknown_opt_name) {
+                       goto error;
+               }
+
+               if (is_short) {
+                       strcpy((*error)->unknown_opt_name, "-");
+               } else {
+                       strcpy((*error)->unknown_opt_name, "--");
+               }
+
+               strcat((*error)->unknown_opt_name, unknown_opt_name);
+       }
+
+       (*error)->opt_descr = opt_descr;
+       (*error)->is_short = is_short;
+       goto end;
+
+error:
+       argpar_error_destroy(*error);
+       ret = -1;
+
+end:
+       return ret;
+}
+
+ARGPAR_HIDDEN
+enum argpar_error_type argpar_error_type(
+               const struct argpar_error * const error)
+{
+       ARGPAR_ASSERT(error);
+       return error->type;
+}
+
+ARGPAR_HIDDEN
+unsigned int argpar_error_orig_index(const struct argpar_error * const error)
+{
+       ARGPAR_ASSERT(error);
+       return error->orig_index;
+}
+
+ARGPAR_HIDDEN
+const char *argpar_error_unknown_opt_name(
+               const struct argpar_error * const error)
+{
+       ARGPAR_ASSERT(error);
+       ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_UNKNOWN_OPT);
+       ARGPAR_ASSERT(error->unknown_opt_name);
+       return error->unknown_opt_name;
+}
+
+ARGPAR_HIDDEN
+const struct argpar_opt_descr *argpar_error_opt_descr(
+               const struct argpar_error * const error, bool * const is_short)
+{
+       ARGPAR_ASSERT(error);
+       ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_MISSING_OPT_ARG ||
+               error->type == ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG);
+       ARGPAR_ASSERT(error->opt_descr);
+
+       if (is_short) {
+               *is_short = error->is_short;
+       }
+
+       return error->opt_descr;
+}
+
+ARGPAR_HIDDEN
+void argpar_error_destroy(const struct argpar_error * const error)
+{
+       if (error) {
+               free(error->unknown_opt_name);
+               free((void *) error);
+       }
+}
+
+/*
+ * Finds and returns the _first_ descriptor having the short option name
+ * `short_name` or the long option name `long_name` within the option
+ * descriptors `descrs`.
+ *
+ * `short_name` may be `'\0'` to not consider it.
+ *
+ * `long_name` may be `NULL` to not consider it.
+ *
+ * Returns `NULL` if no descriptor is found.
+ */
 static
 const struct argpar_opt_descr *find_descr(
                const struct argpar_opt_descr * const descrs,
@@ -306,49 +406,63 @@ 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_state *state,
-               char **error,
-               struct argpar_item **item)
+               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;
        bool used_next_orig_arg = false;
-
-       if (strlen(short_opts) == 0) {
-               argpar_string_append_printf(error, "Invalid argument");
-               goto error;
-       }
-
-       if (!state->short_opt_ch) {
-               state->short_opt_ch = short_opts;
-       }
-
        const char *opt_arg = NULL;
        const struct argpar_opt_descr *descr;
        struct argpar_item_opt *opt_item;
 
+       ARGPAR_ASSERT(strlen(short_opt_group) != 0);
+
+       if (!iter->short_opt_group_ch) {
+               iter->short_opt_group_ch = short_opt_group;
+       }
+
        /* Find corresponding option descriptor */
-       descr = find_descr(descrs, *state->short_opt_ch, NULL);
+       descr = find_descr(descrs, *iter->short_opt_group_ch, NULL);
        if (!descr) {
-               ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
-               argpar_string_append_printf(error,
-                       "Unknown option `-%c`", *state->short_opt_ch);
+               const char unknown_opt_name[] =
+                       {*iter->short_opt_group_ch, '\0'};
+
+               ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
+
+               if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT,
+                               unknown_opt_name, NULL, true)) {
+                       ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+               }
+
                goto error;
        }
 
        if (descr->with_arg) {
-               if (state->short_opt_ch[1]) {
+               if (iter->short_opt_group_ch[1]) {
                        /* `-oarg` form */
-                       opt_arg = &state->short_opt_ch[1];
+                       opt_arg = &iter->short_opt_group_ch[1];
                } else {
                        /* `-o arg` form */
                        opt_arg = next_orig_arg;
@@ -356,15 +470,18 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
                }
 
                /*
-                       * We accept `-o ''` (empty option's argument),
-                       * but not `-o` alone if an option's argument is
-                       * expected.
-                       */
-               if (!opt_arg || (state->short_opt_ch[1] && strlen(opt_arg) == 0)) {
-                       argpar_string_append_printf(error,
-                               "Missing required argument for option `-%c`",
-                               *state->short_opt_ch);
-                       used_next_orig_arg = false;
+                * 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;
                }
        }
@@ -372,44 +489,49 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
        /* 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++;
 
-       state->short_opt_ch++;
-
-       if (descr->with_arg || !*state->short_opt_ch) {
+       if (descr->with_arg || !*iter->short_opt_group_ch) {
                /* Option has an argument: no more options */
-               state->short_opt_ch = NULL;
+               iter->short_opt_group_ch = NULL;
 
                if (used_next_orig_arg) {
-                       state->i += 2;
+                       iter->i += 2;
                } else {
-                       state->i += 1;
+                       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_state *state,
-               char **error,
-               struct argpar_item **item)
+               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;
@@ -421,17 +543,10 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
        /* Position of first `=`, if any */
        const char *eq_pos;
 
-       /* Buffer holding option name when `long_opt_arg` contains `=` */
-       char buf[max_len + 1];
-
        /* Option name */
        const char *long_opt_name = long_opt_arg;
 
-       if (strlen(long_opt_arg) == 0) {
-               argpar_string_append_printf(error,
-                       "Invalid argument");
-               goto error;
-       }
+       ARGPAR_ASSERT(strlen(long_opt_arg) != 0);
 
        /* Find the first `=` in original argument */
        eq_pos = strchr(long_opt_arg, '=');
@@ -439,23 +554,31 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
                const size_t long_opt_name_size = eq_pos - long_opt_arg;
 
                /* Isolate the option name */
-               if (long_opt_name_size > max_len) {
-                       argpar_string_append_printf(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(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;
        }
 
@@ -467,9 +590,13 @@ 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(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;
                        }
 
@@ -481,9 +608,13 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
                 * Unexpected `--opt=arg` style for a long option which
                 * doesn't accept an argument.
                 */
-               argpar_string_append_printf(error,
-                       "Unexpected argument for option `--%s`",
-                       long_opt_name);
+               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;
        }
 
@@ -494,30 +625,36 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
        }
 
        if (used_next_orig_arg) {
-               state->i += 2;
+               iter->i += 2;
        } else {
-               state->i += 1;
+               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_state *state,
-               char **error,
-               struct argpar_item **item)
+               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;
 
@@ -526,122 +663,115 @@ 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, state, error, item);
+                       next_orig_arg, descrs, iter, error, item);
        } else {
                /* Short option */
-               ret = parse_short_opts(&orig_arg[1],
-                       next_orig_arg, descrs, state, error, item);
+               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)
-{
-       char *new_error;
-       bool success;
-
-       ARGPAR_ASSERT(error);
-       ARGPAR_ASSERT(*error);
-
-       new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
-               i + 1, arg, *error);
-       if (!new_error) {
-               success = false;
-               goto end;
-       }
-
-       free(*error);
-       *error = new_error;
-       success = true;
-
-end:
-       return success;
-}
-
 ARGPAR_HIDDEN
-struct argpar_state *argpar_state_create(
-               unsigned int argc,
-               const char * const *argv,
+struct argpar_iter *argpar_iter_create(const unsigned int argc,
+               const char * const * const argv,
                const struct argpar_opt_descr * const descrs)
 {
-       struct argpar_state *state;
+       struct argpar_iter *iter = ARGPAR_ZALLOC(struct argpar_iter);
 
-       state = argpar_zalloc(struct argpar_state);
-       if (!state) {
+       if (!iter) {
                goto end;
        }
 
-       state->argc = argc;
-       state->argv = argv;
-       state->descrs = descrs;
+       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 state;
+       return iter;
 }
 
 ARGPAR_HIDDEN
-void argpar_state_destroy(struct argpar_state *state)
+void argpar_iter_destroy(struct argpar_iter * const iter)
 {
-       free(state);
+       if (iter) {
+               free(iter->tmp_buf.data);
+               free(iter);
+       }
 }
 
 ARGPAR_HIDDEN
-enum argpar_state_parse_next_status argpar_state_parse_next(
-               struct argpar_state *state,
-               struct argpar_item **item,
-               char **error)
+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_state_parse_next_status status;
+       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;
 
-       ARGPAR_ASSERT(state->i <= state->argc);
+       ARGPAR_ASSERT(iter->i <= iter->user.argc);
 
-       *error = NULL;
+       if (error) {
+               *nc_error = NULL;
+       }
 
-       if (state->i == state->argc) {
-               status = ARGPAR_STATE_PARSE_NEXT_STATUS_END;
+       if (iter->i == iter->user.argc) {
+               status = ARGPAR_ITER_NEXT_STATUS_END;
                goto end;
        }
 
-       enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
-       const char * const orig_arg = state->argv[state->i];
-       const char * const next_orig_arg =
-               state->i < (state->argc - 1) ? state->argv[state->i + 1] : NULL;
+       orig_arg = iter->user.argv[iter->i];
+       next_orig_arg =
+               iter->i < (iter->user.argc - 1) ?
+                       iter->user.argv[iter->i + 1] : NULL;
 
-       if (orig_arg[0] != '-') {
+       if (strcmp(orig_arg, "-") == 0 || strcmp(orig_arg, "--") == 0 ||
+                       orig_arg[0] != '-') {
                /* Non-option argument */
-               struct argpar_item_non_opt *non_opt_item =
-                       create_non_opt_item(orig_arg, state->i, state->non_opt_index);
+               const struct argpar_item_non_opt * const non_opt_item =
+                       create_non_opt_item(orig_arg, iter->i,
+                               iter->non_opt_index);
 
                if (!non_opt_item) {
-                       status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
+                       status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY;
                        goto end;
                }
 
-               state->non_opt_index++;
-               state->i++;
-
+               iter->non_opt_index++;
+               iter->i++;
                *item = &non_opt_item->base;
-               status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK;
+               status = ARGPAR_ITER_NEXT_STATUS_OK;
                goto end;
        }
 
        /* Option argument */
        parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
-               next_orig_arg, state->descrs, state, error, item);
+               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_STATE_PARSE_NEXT_STATUS_OK;
+               status = ARGPAR_ITER_NEXT_STATUS_OK;
                break;
-       case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
-               status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT;
-               break;;
        case PARSE_ORIG_ARG_OPT_RET_ERROR:
-               prepend_while_parsing_arg_to_error(
-                       error, state->i, orig_arg);
-               status = ARGPAR_STATE_PARSE_NEXT_STATUS_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();
@@ -652,93 +782,8 @@ end:
 }
 
 ARGPAR_HIDDEN
-int argpar_state_get_ingested_orig_args(struct argpar_state *state)
-{
-       return state->i;
-}
-
-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)
+unsigned int argpar_iter_ingested_orig_args(
+               const struct argpar_iter * const iter)
 {
-       struct argpar_parse_ret parse_ret = { 0 };
-       struct argpar_item *item = NULL;
-       struct argpar_state *state = NULL;
-
-       parse_ret.items = new_item_array();
-       if (!parse_ret.items) {
-               parse_ret.error = strdup("Failed to create items array.");
-               ARGPAR_ASSERT(parse_ret.error);
-               goto error;
-       }
-
-       state = argpar_state_create(argc, argv, descrs);
-       if (!state) {
-               parse_ret.error = strdup("Failed to create argpar state.");
-               ARGPAR_ASSERT(parse_ret.error);
-               goto error;
-       }
-
-       while (true) {
-               enum argpar_state_parse_next_status status;
-
-               status = argpar_state_parse_next(state, &item, &parse_ret.error);
-               if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
-                       goto error;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
-                       break;
-               } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       if (fail_on_unknown_opt) {
-                               parse_ret.ingested_orig_args =
-                                       argpar_state_get_ingested_orig_args(state);
-                               prepend_while_parsing_arg_to_error(
-                                       &parse_ret.error, parse_ret.ingested_orig_args,
-                                       argv[parse_ret.ingested_orig_args]);
-                               status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
-                               goto error;
-                       }
-
-                       free(parse_ret.error);
-                       parse_ret.error = NULL;
-                       break;
-               }
-
-               ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
-
-               if (!push_item(parse_ret.items, item)) {
-                       goto error;
-               }
-               item = NULL;
-       }
-
-       ARGPAR_ASSERT(!parse_ret.error);
-       parse_ret.ingested_orig_args =
-               argpar_state_get_ingested_orig_args(state);
-       goto end;
-
-error:
-       ARGPAR_ASSERT(parse_ret.error);
-
-       /* That's how we indicate that an error occurred */
-       destroy_item_array(parse_ret.items);
-       parse_ret.items = NULL;
-
-end:
-       argpar_state_destroy(state);
-       argpar_item_destroy(item);
-       return parse_ret;
-}
-
-ARGPAR_HIDDEN
-void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
-{
-       ARGPAR_ASSERT(ret);
-
-       destroy_item_array(ret->items);
-       ret->items = NULL;
-
-       free(ret->error);
-       ret->error = NULL;
+       return iter->i;
 }
index 4bfc51d2b3b395b9968b3ae163d8dc9d05891915..c528cc4412272700990a0a61a480b6859ce57c18 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>
-
-#if defined(__cplusplus)
+#ifdef __cplusplus
 extern "C" {
 #endif
 
-/*
- * argpar is a library that provides facilities for argument parsing.
- *
- * Two APIs are available:
- *
- *   - The iterator-style API, where you initialize a state object with
- *     `argpar_state_create`, then repeatedly call `argpar_state_parse_next` to
- *     get the arguments, until (1) there are no more arguments, (2) the parser
- *     encounters an error (e.g. unknown option) or (3) you get bored.  This
- *     API gives you more control on when to stop parsing the arguments.
- *
- *   - The parse-everything-in-one-shot-API, where you call `argpar_parse`,
- *     which parses the arguments until (1) there are not more arguments or
- *     (2) it encounters a parser error.  It returns you a list of all the
- *     arguments it was able to parse, which you can consult at your leisure.
- *
- * The following describes how arguments are parsed, and applies to both APIs.
- *
- * argpar parses the arguments `argv` of which the count is `argc` using the
- * sentinel-terminated (use `ARGPAR_OPT_DESCR_SENTINEL`) option
- * descriptor array `descrs`.
- *
- * argpar 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 parser 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 parser accepts duplicate options (it will output one item for each
- * instance).
- *
- * The returned items are of the type `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 items are returned 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
- *
- * found items are returned in this order: option item (--hello), option item
- * (--meow=23), non-option item (/path/to/file) and option item (-b).
- */
+#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
 
-/* Forward-declaration for the opaque type. */
-struct argpar_state;
+struct argpar_opt_descr;
 
-/* Option descriptor */
-struct argpar_opt_descr {
-       /* Numeric ID for this option */
-       const int id;
-
-       /* Short option character, or `\0` */
-       const char short_name;
-
-       /* Long option name (without `--`), or `NULL` */
-       const char * const long_name;
+/*!
+@name Item API
+@{
+*/
 
-       /* True if this option has an argument */
-       const bool with_arg;
-};
-
-/* Item type */
+/*!
+@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 */
+       /// Option
        ARGPAR_ITEM_TYPE_OPT,
 
-       /* Non-option */
+       /// Non-option
        ARGPAR_ITEM_TYPE_NON_OPT,
 };
 
-/* Base item */
-struct argpar_item {
-       enum argpar_item_type type;
-};
+/*!
+@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.
 
-/* Option item */
-struct argpar_item_opt {
-       struct argpar_item base;
+@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.
 
-       /* Corresponding descriptor */
-       const struct argpar_opt_descr *descr;
+@returns
+    Non-option index of \p item.
 
-       /* Argument, or `NULL` if none */
-       const char *arg;
+@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,
 };
 
-/* Non-option item */
-struct argpar_item_non_opt {
-       struct argpar_item base;
+/*!
+@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.
 
-       /*
-        * Complete argument, pointing to one of the entries of the
-        * original arguments (`argv`).
-        */
-       const char *arg;
+@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
+@{
+*/
 
-       /* Index of this argument amongst all original arguments (`argv`) */
-       unsigned int orig_index;
+/*!
+@brief
+    Option descriptor
 
-       /* Index of this argument amongst other non-option arguments */
-       unsigned int non_opt_index;
+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, to uniquely identify this descriptor
+       const int id;
 
-struct argpar_item_array {
-       /* Array of `struct argpar_item *`, or `NULL` on error */
-       struct argpar_item **items;
+       /// Short option character, or <code>'\0'</code>
+       const char short_name;
 
-       /* Number of used slots in `items`. */
-       unsigned int n_items;
+       /// Long option name (without the <code>\--</code> prefix), or \c NULL
+       const char * const long_name;
 
-       /* Number of allocated slots in `items`. */
-       unsigned int n_alloc;
+       /// \c true if this option has an argument
+       const bool with_arg;
 };
 
-/* What is returned by argpar_parse() */
-struct argpar_parse_ret {
-       /* Array of `struct argpar_item *`, or `NULL` on error */
-       struct argpar_item_array *items;
+/*!
+@brief
+    Sentinel for an option descriptor array
 
-       /* Error string, or `NULL` if none */
-       char *error;
+The typical usage is, for example:
 
-       /* Number of original arguments (`argv`) ingested */
-       unsigned int ingested_orig_args;
+@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 }
 
-/*
- * Parses arguments in `argv` until the end is reached or an error is
- * encountered.
- *
- * On success, this function returns an array of items
- * (field `items` of `struct argpar_parse_ret`) corresponding to each parsed
- * argument.
- *
- * 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().
- */
+/*!
+@struct argpar_iter
+
+@brief
+    Opaque argpar iterator type
+
+argpar_iter_create() returns a pointer to such a type.
+*/
+struct argpar_iter;
+
+/*!
+@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.
+
+This function initializes the returned structure, but doesn't actually
+start parsing the arguments.
+
+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().
+
+\p *argv and \p *descrs must \em not change for all of:
+
+- The lifetime of the returned iterator (until you call
+  argpar_iter_destroy()).
+
+- The lifetime of any parsing item (until you call
+  argpar_item_destroy()) which argpar_iter_next() creates from the
+  returned iterator.
+
+- The lifetime of any parsing error (until you call
+  argpar_error_destroy()) which argpar_iter_next() creates from the
+  returned iterator.
+
+@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.
+
+    May contain duplicate entries.
+    @endparblock
+
+@returns
+    New argument parsing iterator, or \c NULL on memory error.
+
+@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);
 
-/*
- * Finalizes what is returned by argpar_parse().
- *
- * It is safe to call argpar_parse() multiple times with the same
- * structure.
- */
-ARGPAR_HIDDEN
-void argpar_parse_ret_fini(struct argpar_parse_ret *ret);
+/*!
+@brief
+    Destroys the argument parsing iterator \p iter.
 
-/*
- * Creates an instance of `struct argpar_state`.
- *
- * This sets up the argpar_state structure, but does not actually
- * start parsing the arguments.
- *
- * When you are done with it, the state must be freed with
- * `argpar_state_destroy`.
- */
-ARGPAR_HIDDEN
-struct argpar_state *argpar_state_create(
-               unsigned int argc,
-               const char * const *argv,
-               const struct argpar_opt_descr * const descrs);
+@param[in] iter
+    Argument parsing iterator to destroy (may be \c NULL).
 
-/*
- * Destroys an instance of `struct argpar_state`.
- */
+@sa
+    argpar_iter_create() -- Creates an argument parsing iterator.
+*/
+/// @cond hidden_macro
 ARGPAR_HIDDEN
-void argpar_state_destroy(struct argpar_state *state);
+/// @endcond
+void argpar_iter_destroy(struct argpar_iter *iter);
 
+/*!
+@brief
+    Return type of argpar_iter_next().
 
-enum argpar_state_parse_next_status {
-       ARGPAR_STATE_PARSE_NEXT_STATUS_OK,
-       ARGPAR_STATE_PARSE_NEXT_STATUS_END,
-       ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT,
-       ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR,
-};
+Error status enumerators have a negative value.
+*/
+enum argpar_iter_next_status {
+       /// Success
+       ARGPAR_ITER_NEXT_STATUS_OK,
 
-/*
- * Parses and returns the next argument from `state`.
- *
- * On success, an item describing the argument is returned in `*item` and
- * ARGPAR_STATE_PARSE_NEXT_STATUS_OK is returned.  The item must be freed with
- * `argpar_item_destroy`.
- *
- * If there are no more arguments to parse, ARGPAR_STATE_PARSE_NEXT_STATUS_END
- * is returned.
- *
- * On failure (status codes ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT and
- * ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR), an error string is returned in `*error`.
- * This string must be freed with `free`.
- */
-enum argpar_state_parse_next_status argpar_state_parse_next(
-               struct argpar_state *state,
-               struct argpar_item **item,
-               char **error);
+       /// End of iteration (no more original arguments to parse)
+       ARGPAR_ITER_NEXT_STATUS_END,
 
-/*
- * Return the number of ingested elements from argv that were required to
- * produce the previously returned items.
- */
+       /// 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
-int argpar_state_get_ingested_orig_args(struct argpar_state *state);
+/// @endcond
+enum argpar_iter_next_status argpar_iter_next(
+               struct argpar_iter *iter, const struct argpar_item **item,
+               const struct argpar_error **error);
 
 /*
- * Destroy an instance of `struct argpar_item`, as returned by
- * argpar_state_parse_next.
+ * 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_item_destroy(struct argpar_item *item);
+/// @endcond
+unsigned int argpar_iter_ingested_orig_args(const struct argpar_iter *iter);
 
-#define ARGPAR_ITEM_DESTROY_AND_RESET(_item)   \
-       {                                       \
-               argpar_item_destroy(_item);     \
-               _item = NULL;                   \
-       }
+/// @}
+
+/// @}
 
-#if defined(__cplusplus)
+#ifdef __cplusplus
 }
 #endif
 
-#endif /* BABELTRACE_ARGPAR_H */
+#endif /* ARGPAR_ARGPAR_H */
index a061f6680e12e33b41f9db79053a3e4be874c458..bcbb51b16c1a1910229bd2820070e92ed108db28 100644 (file)
@@ -108,7 +108,7 @@ static inline bool __lttng_print_check_opt(enum lttng_error_level type)
        return true;
 }
 
-void lttng_abort_on_error(void);
+C_LINKAGE void lttng_abort_on_error(void);
 
 static inline void __lttng_print_check_abort(enum lttng_error_level type)
 {
index af9cb7e19bca6ac8eb632e2a5b6941d04aff285c..6ac24fa01157b79cbf1debc58fbcb49438288af2 100644 (file)
@@ -101,6 +101,13 @@ void *zmalloc(size_t len)
        _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
 #endif
 
+/* Used to make specific C++ functions to C code. */
+#ifdef __cplusplus
+#define C_LINKAGE extern "C"
+#else
+#define C_LINKAGE
+#endif
+
 /*
  * lttng_strncpy returns 0 on success, or nonzero on failure.
  * It checks that the @src string fits into @dst_len before performing
index 8dcc6b2aae9ce9fab7b3beeefbea0b495fc15db4..d23cf794cb34b0edb7da097f4393b4bced4fb8bb 100644 (file)
@@ -7,11 +7,13 @@
 
 #define _LGPL_SOURCE
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
 #include <type_traits>
 #include <assert.h>
 #include <errno.h>
+#include <stdarg.h>
 
 #include "string-utils.h"
 #include "../macros.h"
@@ -395,3 +397,49 @@ int strutils_append_str(char **s, const char *append)
        free(old);
        return 0;
 }
+
+int strutils_appendf(char **s, const char *fmt, ...)
+{
+       char *new_str;
+       size_t oldlen = (*s) ? strlen(*s) : 0;
+       int ret;
+       va_list args;
+
+       /* Compute length of formatted string we append. */
+       va_start(args, fmt);
+       ret = vsnprintf(NULL, 0, fmt, args);
+       va_end(args);
+
+       if (ret == -1) {
+               goto end;
+       }
+
+       /* Allocate space for old string + new string + \0. */
+       new_str = (char *) zmalloc(oldlen + ret + 1);
+       if (!new_str) {
+               ret = -ENOMEM;
+               goto end;
+       }
+
+       /* Copy old string, if there was one. */
+       if (oldlen) {
+               strcpy(new_str, *s);
+       }
+
+       /* Format new string in-place. */
+       va_start(args, fmt);
+       ret = vsprintf(&new_str[oldlen], fmt, args); 
+       va_end(args);
+
+       if (ret == -1) {
+               ret = -1;
+               goto end;
+       }
+
+       free(*s);
+       *s = new_str;
+       new_str = NULL;
+
+end:
+       return ret;
+}
index 8f47abbc302109131fbc52704949d6c4447c66e6..57cf4ecbfb5ae2316b2af392be9a348bd18bf655 100644 (file)
@@ -35,6 +35,13 @@ size_t strutils_array_of_strings_len(char * const *array);
  *
  * Return 0 on success, -ENOMEM on failure.
  */
-int strutils_append_str(char **str, const char *append);
+C_LINKAGE int strutils_append_str(char **str, const char *append);
+
+/*
+ * Like `strutils_append_str`, but the appended string is formatted using
+ * `fmt` and the following arguments.
+ */
+C_LINKAGE ATTR_FORMAT_PRINTF(2, 3)
+int strutils_appendf(char **s, const char *fmt, ...);
 
 #endif /* _STRING_UTILS_H */
This page took 0.068048 seconds and 4 git commands to generate.