From 8b95d883334b188d05cb1967980eae628e295d10 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Wed, 2 Jun 2021 11:40:32 -0400 Subject: [PATCH] Add parsing error API 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 Change-Id: I7b988253f653f96e6bf6859ae1b48bbcf6fb406d --- argpar/argpar.c | 272 ++++++++++++++++++++++++-------------------- argpar/argpar.h | 149 ++++++++++++++++++++++-- tests/test_argpar.c | 248 +++++++++++++++++++++------------------- 3 files changed, 415 insertions(+), 254 deletions(-) diff --git a/argpar/argpar.c b/argpar/argpar.c index 9112e4c..79a22f5 100644 --- a/argpar/argpar.c +++ b/argpar/argpar.c @@ -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: diff --git a/argpar/argpar.h b/argpar/argpar.h index 22ecaab..3cd437f 100644 --- a/argpar/argpar.h +++ b/argpar/argpar.h @@ -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 - or \-- +prefix. + +With the long option with argument form, for example +\--mireille=deyglun, this function only returns the name +part (\--mireille in the last example). + +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 \&argv[1] as \p argv from what main() 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 free(). + 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 diff --git a/tests/test_argpar.c b/tests/test_argpar.c index a111d42..bd94f3f 100644 --- a/tests/test_argpar.c +++ b/tests/test_argpar.c @@ -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(); -- 2.34.1