Add parsing error API
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 2 Jun 2021 15:40:32 +0000 (11:40 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 4 Jun 2021 17:59:12 +0000 (13:59 -0400)
This patch adds the `struct argpar_error` type which contains details
about a parsing error. Get said details with the new dedicated property
getters:

argpar_error_orig_index():
    Returns the index of the original argument for which the error
    occurred.

argpar_error_unknown_opt_name():
    Returns the name of the unknown option for which the error occurred.

argpar_error_opt_descr():
    Returns the descriptor of the option for which the error occurred.

    Also sets an output boolean parameter to whether said option is
    short or long.

Destroy a parsing error with argpar_error_destroy().

argpar_iter_next() now sets such a parsing error object on parsing error
instead of an error string. You can build a corresponding error string
from the properties of the parsing error object.

See the updated user documentation in `argpar.h` for more details.

In `test_argpar.c`, test_fail() now ensures that argpar_iter_next() sets
an parsing error object and that, depending on the returned status code,
its properties have expected values. I moved the "unknown option" tests
from succeed_tests() to fail_tests() as this was a vestige of the
`fail_on_unknown_opt` parameter of argpar_parse().

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: I7b988253f653f96e6bf6859ae1b48bbcf6fb406d

argpar/argpar.c
argpar/argpar.h
tests/test_argpar.c

index 9112e4c359b8a4bfaeae625ae6bc7cebd8b1c406..79a22f565bf99add9bb16b1eda572cd4b525526b 100644 (file)
@@ -105,83 +105,20 @@ struct argpar_item_non_opt {
        unsigned int non_opt_index;
 };
 
-static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
-char *argpar_vasprintf(const char * const 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);
-
-end:
-       va_end(args2);
-       return str;
-}
-
-
-static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
-char *argpar_asprintf(const char * const fmt, ...)
-{
-       va_list args;
-       char *str;
-
-       va_start(args, fmt);
-       str = argpar_vasprintf(fmt, args);
-       va_end(args);
-       return str;
-}
-
-static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
-bool try_append_string_printf(char ** const str, const char *fmt, ...)
-{
-       char *new_str = NULL;
-       char *addendum = NULL;
-       bool success;
-       va_list args;
-
-       if (!str) {
-               success = true;
-               goto end;
-       }
-
-       ARGPAR_ASSERT(str);
-       va_start(args, fmt);
-       addendum = argpar_vasprintf(fmt, args);
-       va_end(args);
-
-       if (!addendum) {
-               success = false;
-               goto end;
-       }
+/* Parsing error */
+struct argpar_error {
+       /* Original argument index */
+       unsigned int orig_index;
 
-       new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum);
-       if (!new_str) {
-               success = false;
-               goto end;
-       }
+       /* Name of unknown option; owned by this */
+       char *unknown_opt_name;
 
-       free(*str);
-       *str = new_str;
-       success = true;
+       /* Option descriptor */
+       const struct argpar_opt_descr *opt_descr;
 
-end:
-       free(addendum);
-       return success;
-}
+       /* `true` if a short option caused the error */
+       bool is_short;
+};
 
 ARGPAR_HIDDEN
 enum argpar_item_type argpar_item_type(const struct argpar_item * const item)
@@ -306,6 +243,101 @@ 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,
+               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;
+       }
+
+       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
+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->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->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);
+       }
+}
+
 static
 const struct argpar_opt_descr *find_descr(
                const struct argpar_opt_descr * const descrs,
@@ -343,7 +375,8 @@ enum parse_orig_arg_opt_ret parse_short_opt_group(
                const char * const next_orig_arg,
                const struct argpar_opt_descr * const descrs,
                struct argpar_iter * const iter,
-               char ** const error, struct argpar_item ** const item)
+               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;
@@ -360,9 +393,15 @@ enum parse_orig_arg_opt_ret parse_short_opt_group(
        /* Find corresponding option descriptor */
        descr = find_descr(descrs, *iter->short_opt_group_ch, NULL);
        if (!descr) {
-               try_append_string_printf(error, "Unknown option `-%c`",
-                       *iter->short_opt_group_ch);
+               const char unknown_opt_name[] =
+                       {*iter->short_opt_group_ch, '\0'};
+
                ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
+
+               if (set_error(error, unknown_opt_name, NULL, true)) {
+                       ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+               }
+
                goto error;
        }
 
@@ -382,11 +421,12 @@ enum parse_orig_arg_opt_ret parse_short_opt_group(
                 */
                if (!opt_arg || (iter->short_opt_group_ch[1] &&
                                strlen(opt_arg) == 0)) {
-                       try_append_string_printf(error,
-                               "Missing required argument for option `-%c`",
-                               *iter->short_opt_group_ch);
-                       used_next_orig_arg = false;
                        ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG;
+
+                       if (set_error(error, NULL, descr, true)) {
+                               ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+                       }
+
                        goto error;
                }
        }
@@ -426,7 +466,8 @@ 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_iter * const iter,
-               char ** const error, struct argpar_item ** const item)
+               struct argpar_error ** const error,
+               struct argpar_item ** const item)
 {
        enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
        const struct argpar_opt_descr *descr;
@@ -468,9 +509,12 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
        /* Find corresponding option descriptor */
        descr = find_descr(descrs, '\0', long_opt_name);
        if (!descr) {
-               try_append_string_printf(error, "Unknown option `--%s`",
-                       long_opt_name);
                ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
+
+               if (set_error(error, long_opt_name, NULL, false)) {
+                       ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+               }
+
                goto error;
        }
 
@@ -482,10 +526,12 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
                } else {
                        /* `--long-opt arg` style */
                        if (!next_orig_arg) {
-                               try_append_string_printf(error,
-                                       "Missing required argument for option `--%s`",
-                                       long_opt_name);
                                ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG;
+
+                               if (set_error(error, NULL, descr, false)) {
+                                       ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+                               }
+
                                goto error;
                        }
 
@@ -497,9 +543,12 @@ 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.
                 */
-               try_append_string_printf(error,
-                       "Unexpected argument for option `--%s`", long_opt_name);
                ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG;
+
+               if (set_error(error, NULL, descr, false)) {
+                       ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY;
+               }
+
                goto error;
        }
 
@@ -529,7 +578,8 @@ 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_iter * const iter, char ** const error,
+               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;
@@ -549,34 +599,6 @@ enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
        return ret;
 }
 
-static
-bool try_prepend_while_parsing_arg_to_error(char ** const error,
-               const unsigned int i, const char * const arg)
-{
-       char *new_error;
-       bool success;
-
-       if (!error) {
-               success = true;
-               goto end;
-       }
-
-       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_iter *argpar_iter_create(const unsigned int argc,
                const char * const * const argv,
@@ -615,17 +637,19 @@ void argpar_iter_destroy(struct argpar_iter * const iter)
 ARGPAR_HIDDEN
 enum argpar_iter_next_status argpar_iter_next(
                struct argpar_iter * const iter,
-               const struct argpar_item ** const item, char ** const error)
+               const struct argpar_item ** const item,
+               const struct argpar_error ** const error)
 {
        enum argpar_iter_next_status status;
        enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
        const char *orig_arg;
        const char *next_orig_arg;
+       struct argpar_error ** const nc_error = (struct argpar_error **) error;
 
        ARGPAR_ASSERT(iter->i <= iter->argc);
 
        if (error) {
-               *error = NULL;
+               *nc_error = NULL;
        }
 
        if (iter->i == iter->argc) {
@@ -658,7 +682,7 @@ enum argpar_iter_next_status argpar_iter_next(
 
        /* Option argument */
        parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
-               next_orig_arg, iter->descrs, iter, error,
+               next_orig_arg, iter->descrs, iter, nc_error,
                (struct argpar_item **) item);
        switch (parse_orig_arg_opt_ret) {
        case PARSE_ORIG_ARG_OPT_RET_OK:
@@ -667,8 +691,10 @@ enum argpar_iter_next_status argpar_iter_next(
        case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
        case PARSE_ORIG_ARG_OPT_RET_ERROR_MISSING_OPT_ARG:
        case PARSE_ORIG_ARG_OPT_RET_ERROR_UNEXPECTED_OPT_ARG:
-               try_prepend_while_parsing_arg_to_error(error, iter->i,
-                       orig_arg);
+               if (error) {
+                       ARGPAR_ASSERT(*error);
+                       (*nc_error)->orig_index = iter->i;
+               }
 
                switch (parse_orig_arg_opt_ret) {
                case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
index 22ecaabe89526c98aa2259375bc79d3ed2604e42..3cd437fa043d68390a933f4f4c1e99912935027f 100644 (file)
@@ -211,7 +211,7 @@ 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 (\p argv as passed to
+    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
@@ -233,9 +233,9 @@ const char *argpar_item_non_opt_arg(const struct argpar_item *item);
 
 /*!
 @brief
-    Returns the index, within \em all the original arguments (\p argv
-    as passed to argpar_iter_create()), of the non-option parsing item
-    \p item.
+    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):
@@ -333,6 +333,123 @@ void argpar_item_destroy(const struct argpar_item *item);
 
 /// @}
 
+/*!
+@name Error API
+@{
+*/
+
+/*!
+@struct argpar_error
+
+@brief
+    Opaque parsing error type
+*/
+struct argpar_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).
+
+You may only call this function if the call to argpar_iter_next() which
+set \p error returned #ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT.
+
+@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 call to argpar_iter_next() which set \p error returned
+    #ARGPAR_ITER_NEXT_STATUS_ERROR_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.
+
+You may only call this function if the call to argpar_iter_next() which
+set \p error returned #ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG or
+#ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPG_ARG.
+
+@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 call to argpar_iter_next() which set \p error returned
+    #ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG or
+    #ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPG_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
 @{
@@ -412,10 +529,18 @@ 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 the lifetime of the
-returned iterator (until you call argpar_iter_destroy()) and for the
-lifetime of any parsing item (until you call argpar_item_destroy())
-which argpar_iter_next() creates from the returned iterator.
+\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.
@@ -512,9 +637,9 @@ If there are no more original arguments to parse, this function returns
     #ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG, or
     #ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPG_ARG, if this parameter
     is not \c NULL,
-    \p *error is a string which explains the parsing error in English.
+    \p *error contains details about the error.
 
-    Free \p *error with <code>free()</code>.
+    Destroy \p *error with argpar_error_destroy().
     @endparblock
 
 @returns
@@ -530,7 +655,7 @@ ARGPAR_HIDDEN
 /// @endcond
 enum argpar_iter_next_status argpar_iter_next(
                struct argpar_iter *iter, const struct argpar_item **item,
-               char **error);
+               const struct argpar_error **error);
 
 /*
  * Returns the number of ingested elements from `argv`, as passed to
@@ -541,7 +666,7 @@ enum argpar_iter_next_status argpar_iter_next(
 /*!
 @brief
     Returns the number of ingested original arguments (in
-    \p argv as passed to argpar_iter_create() to create \p iter) that
+    \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
index a111d423275a87cfb47b1053f2ddcf2fdc17d21b..bd94f3ff034db28c8e41aa9b117584cd8db395eb 100644 (file)
@@ -107,7 +107,7 @@ void test_succeed(const char * const cmdline,
 {
        struct argpar_iter *iter = NULL;
        const struct argpar_item *item = NULL;
-       char *error = NULL;
+       const struct argpar_error *error = NULL;
        GString * const res_str = g_string_new(NULL);
        gchar ** const argv = g_strsplit(cmdline, " ", 0);
        unsigned int i, actual_ingested_orig_args;
@@ -125,33 +125,19 @@ void test_succeed(const char * const cmdline,
                status = argpar_iter_next(iter, &item, &error);
 
                ok(status == ARGPAR_ITER_NEXT_STATUS_OK ||
-                       status == ARGPAR_ITER_NEXT_STATUS_END ||
-                       status == ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       status == ARGPAR_ITER_NEXT_STATUS_END,
                        "argpar_iter_next() returns the expected status "
                        "(%d) for command line `%s` (call %u)",
                        status, cmdline, i + 1);
+               ok(!error,
+                       "argpar_iter_next() doesn't set an error for "
+                       "command line `%s` (call %u)",
+                       cmdline, i + 1);
 
-               if (status == ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
-                       ok(error,
-                               "argpar_iter_next() sets an error for "
-                               "status `ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
-                               "and command line `%s` (call %u)",
-                               cmdline, i + 1);
-               } else {
-                       ok(!error,
-                               "argpar_iter_next() doesn't set an error "
-                               "for other status than "
-                               "`ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
-                               "and command line `%s` (call %u)",
-                               cmdline, i + 1);
-               }
-
-               if (status == ARGPAR_ITER_NEXT_STATUS_END ||
-                               status == ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
+               if (status == ARGPAR_ITER_NEXT_STATUS_END) {
                        ok(!item,
                                "argpar_iter_next() doesn't set an item "
                                "for status `ARGPAR_ITER_NEXT_STATUS_END` "
-                               "or `ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
                                "and command line `%s` (call %u)",
                                cmdline, i + 1);
                        break;
@@ -182,9 +168,9 @@ void test_succeed(const char * const cmdline,
 
        argpar_item_destroy(item);
        argpar_iter_destroy(iter);
+       assert(!error);
        g_string_free(res_str, TRUE);
        g_strfreev(argv);
-       free(error);
 }
 
 static
@@ -374,84 +360,6 @@ void succeed_tests(void)
                        descrs, 7);
        }
 
-       /* Unknown short option (space form) */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, 'd', NULL, true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "-d salut -e -d meow",
-                       "-d salut",
-                       descrs, 2);
-       }
-
-       /* Unknown short option (glued form) */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, 'd', NULL, true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "-dsalut -e -d meow",
-                       "-d salut",
-                       descrs, 1);
-       }
-
-       /* Unknown long option (space form) */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, '\0', "sink", true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "--sink party --food --sink impulse",
-                       "--sink=party",
-                       descrs, 2);
-       }
-
-       /* Unknown long option (equal form) */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, '\0', "sink", true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "--sink=party --food --sink=impulse",
-                       "--sink=party",
-                       descrs, 1);
-       }
-
-       /* Unknown option before non-option argument */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, '\0', "thumb", true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "--thumb=party --food bateau --thumb waves",
-                       "--thumb=party",
-                       descrs, 1);
-       }
-
-       /* Unknown option after non-option argument */
-       {
-               const struct argpar_opt_descr descrs[] = {
-                       { 0, '\0', "thumb", true },
-                       ARGPAR_OPT_DESCR_SENTINEL
-               };
-
-               test_succeed(
-                       "--thumb=party wound --food --thumb waves",
-                       "--thumb=party wound<1,0>",
-                       descrs, 2);
-       }
-
        /* Valid `---opt` */
        {
                const struct argpar_opt_descr descrs[] = {
@@ -602,22 +510,36 @@ void succeed_tests(void)
 /*
  * Parses `cmdline` with the argpar API using the option descriptors
  * `descrs`, and ensures that argpar_iter_next() fails with status
- * `expected_status` and that it sets an error which is equal to
- * `expected_error`.
+ * `expected_status` and that it sets an error having:
+ *
+ * * The original argument index `expected_orig_index`.
+ *
+ * * If applicable:
+ *
+ *   * The unknown option name `expected_unknown_opt_name`.
+ *
+ *   * The option descriptor at index `expected_opt_descr_index` of
+ *     `descrs`.
+ *
+ *   * The option type `expected_is_short`.
  *
  * This function splits `cmdline` on spaces to create an original
  * argument array.
  */
 static
-void test_fail(const char * const cmdline, const char * const expected_error,
+void test_fail(const char * const cmdline,
                const enum argpar_iter_next_status expected_status,
+               const unsigned int expected_orig_index,
+               const char * const expected_unknown_opt_name,
+               const unsigned int expected_opt_descr_index,
+               const bool expected_is_short,
                const struct argpar_opt_descr * const descrs)
 {
        struct argpar_iter *iter = NULL;
        const struct argpar_item *item = NULL;
        gchar ** const argv = g_strsplit(cmdline, " ", 0);
        unsigned int i;
-       char *error = NULL;
+       const struct argpar_error *error = NULL;
 
        iter = argpar_iter_create(g_strv_length(argv),
                (const char * const *) argv, descrs);
@@ -647,6 +569,35 @@ void test_fail(const char * const cmdline, const char * const expected_error,
                                " `ARGPAR_ITER_NEXT_STATUS_OK` "
                                "and command line `%s` (call %u)",
                                cmdline, i + 1);
+                       ok(argpar_error_orig_index(error) ==
+                               expected_orig_index,
+                               "argpar_iter_next() sets an error with "
+                               "the expected original argument index "
+                               "for command line `%s` (call %u)",
+                               cmdline, i + 1);
+
+                       if (status == ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
+                               ok(strcmp(argpar_error_unknown_opt_name(error),
+                                       expected_unknown_opt_name) == 0,
+                                       "argpar_iter_next() sets an error with "
+                                       "the expected unknown option name "
+                                       "for command line `%s` (call %u)",
+                                       cmdline, i + 1);
+                       } else {
+                               bool is_short;
+
+                               ok(argpar_error_opt_descr(error, &is_short) ==
+                                       &descrs[expected_opt_descr_index],
+                                       "argpar_iter_next() sets an error with "
+                                       "the expected option descriptor "
+                                       "for command line `%s` (call %u)",
+                                       cmdline, i + 1);
+                               ok(is_short == expected_is_short,
+                                       "argpar_iter_next() sets an error with "
+                                       "the expected option type "
+                                       "for command line `%s` (call %u)",
+                                       cmdline, i + 1);
+                       }
                        break;
                }
 
@@ -662,6 +613,7 @@ void test_fail(const char * const cmdline, const char * const expected_error,
                        cmdline, i + 1);
        }
 
+       /*
        ok(strcmp(expected_error, error) == 0,
                "argpar_iter_next() sets the expected error string "
                "for command line `%s`", cmdline);
@@ -670,17 +622,75 @@ void test_fail(const char * const cmdline, const char * const expected_error,
                diag("Expected: `%s`", expected_error);
                diag("Got:      `%s`", error);
        }
+       */
 
        argpar_item_destroy(item);
        argpar_iter_destroy(iter);
-       free(error);
+       argpar_error_destroy(error);
        g_strfreev(argv);
 }
 
 static
 void fail_tests(void)
 {
-       /* Unknown long option */
+
+       /* Unknown short option (space form) */
+       {
+               const struct argpar_opt_descr descrs[] = {
+                       { 0, 'd', NULL, true },
+                       ARGPAR_OPT_DESCR_SENTINEL
+               };
+
+               test_fail(
+                       "-d salut -e -d meow",
+                       ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       2, "-e", 0, false,
+                       descrs);
+       }
+
+       /* Unknown short option (glued form) */
+       {
+               const struct argpar_opt_descr descrs[] = {
+                       { 0, 'd', 0, true },
+                       ARGPAR_OPT_DESCR_SENTINEL
+               };
+
+               test_fail(
+                       "-dsalut -e -d meow",
+                       ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       1, "-e", 0, false,
+                       descrs);
+       }
+
+       /* Unknown long option (space form) */
+       {
+               const struct argpar_opt_descr descrs[] = {
+                       { 0, '\0', "sink", true },
+                       ARGPAR_OPT_DESCR_SENTINEL
+               };
+
+               test_fail(
+                       "--sink party --food --sink impulse",
+                       ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       2, "--food", 0, false,
+                       descrs);
+       }
+
+       /* Unknown long option (equal form) */
+       {
+               const struct argpar_opt_descr descrs[] = {
+                       { 0, '\0', "sink", true },
+                       ARGPAR_OPT_DESCR_SENTINEL
+               };
+
+               test_fail(
+                       "--sink=party --food --sink=impulse",
+                       ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       1, "--food", 0, false,
+                       descrs);
+       }
+
+       /* Unknown option before non-option argument */
        {
                const struct argpar_opt_descr descrs[] = {
                        { 0, '\0', "thumb", true },
@@ -688,13 +698,13 @@ void fail_tests(void)
                };
 
                test_fail(
-                       "--thumb=party --meow",
-                       "While parsing argument #2 (`--meow`): Unknown option `--meow`",
+                       "--thumb=party --food=18 bateau --thumb waves",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       1, "--food", 0, false,
                        descrs);
        }
 
-       /* Unknown short option */
+       /* Unknown option after non-option argument */
        {
                const struct argpar_opt_descr descrs[] = {
                        { 0, '\0', "thumb", true },
@@ -702,9 +712,9 @@ void fail_tests(void)
                };
 
                test_fail(
-                       "--thumb=party -x",
-                       "While parsing argument #2 (`-x`): Unknown option `-x`",
+                       "--thumb=party wound --food --thumb waves",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT,
+                       2, "--food", 0, false,
                        descrs);
        }
 
@@ -716,9 +726,9 @@ void fail_tests(void)
                };
 
                test_fail(
-                       "--thumb",
-                       "While parsing argument #1 (`--thumb`): Missing required argument for option `--thumb`",
+                       "allo --thumb",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG,
+                       1, NULL, 0, false,
                        descrs);
        }
 
@@ -730,9 +740,9 @@ void fail_tests(void)
                };
 
                test_fail(
-                       "-k",
-                       "While parsing argument #1 (`-k`): Missing required argument for option `-k`",
+                       "zoom heille -k",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG,
+                       2, NULL, 0, true,
                        descrs);
        }
 
@@ -747,8 +757,8 @@ void fail_tests(void)
 
                test_fail(
                        "-abc",
-                       "While parsing argument #1 (`-abc`): Missing required argument for option `-c`",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG,
+                       0, NULL, 2, true,
                        descrs);
        }
 
@@ -760,16 +770,16 @@ void fail_tests(void)
                };
 
                test_fail(
-                       "--chevre=fromage",
-                       "While parsing argument #1 (`--chevre=fromage`): Unexpected argument for option `--chevre`",
+                       "ambulance --chevre=fromage tar -cjv",
                        ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG,
+                       1, NULL, 0, false,
                        descrs);
        }
 }
 
 int main(void)
 {
-       plan_tests(296);
+       plan_tests(309);
        succeed_tests();
        fail_tests();
        return exit_status();
This page took 0.036048 seconds and 4 git commands to generate.