From: Simon Marchi Date: Wed, 4 Dec 2019 16:51:08 +0000 (-0500) Subject: Initial commit X-Git-Url: http://git.efficios.com/?p=argpar.git;a=commitdiff_plain;h=903a5b8ab5ab38d3b200b1d692ba0d29d080c92c Initial commit Copy source and tests from the babeltrace repository, commit: 23256fd15c8ac7d1faaa3e37d0df2e225e90a6c6 bt2: reverse order of printed causes in _Error.__str__ Signed-off-by: Simon Marchi --- 903a5b8ab5ab38d3b200b1d692ba0d29d080c92c diff --git a/argpar/argpar.c b/argpar/argpar.c new file mode 100644 index 0000000..7f4e46b --- /dev/null +++ b/argpar/argpar.c @@ -0,0 +1,465 @@ +/* + * Copyright 2019 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/common.h" + +#include "argpar.h" + +static +void destroy_item(struct bt_argpar_item * const item) +{ + if (!item) { + goto end; + } + + if (item->type == BT_ARGPAR_ITEM_TYPE_OPT) { + struct bt_argpar_item_opt * const opt_item = (void *) item; + + g_free((void *) opt_item->arg); + } + + g_free(item); + +end: + return; +} + +static +struct bt_argpar_item_opt *create_opt_item( + const struct bt_argpar_opt_descr * const descr, + const char * const arg) +{ + struct bt_argpar_item_opt *opt_item = + g_new0(struct bt_argpar_item_opt, 1); + + if (!opt_item) { + goto end; + } + + opt_item->base.type = BT_ARGPAR_ITEM_TYPE_OPT; + opt_item->descr = descr; + + if (arg) { + opt_item->arg = g_strdup(arg); + if (!opt_item->arg) { + goto error; + } + } + + goto end; + +error: + destroy_item(&opt_item->base); + opt_item = NULL; + +end: + return opt_item; +} + +static +struct bt_argpar_item_non_opt *create_non_opt_item(const char * const arg, + const unsigned int orig_index, + const unsigned int non_opt_index) +{ + struct bt_argpar_item_non_opt * const non_opt_item = + g_new0(struct bt_argpar_item_non_opt, 1); + + if (!non_opt_item) { + goto end; + } + + non_opt_item->base.type = BT_ARGPAR_ITEM_TYPE_NON_OPT; + non_opt_item->arg = arg; + non_opt_item->orig_index = orig_index; + non_opt_item->non_opt_index = non_opt_index; + +end: + return non_opt_item; +} + +static +const struct bt_argpar_opt_descr *find_descr( + const struct bt_argpar_opt_descr * const descrs, + const char short_name, const char * const long_name) +{ + const struct bt_argpar_opt_descr *descr; + + for (descr = descrs; descr->short_name || descr->long_name; descr++) { + if (short_name && descr->short_name && + short_name == descr->short_name) { + goto end; + } + + if (long_name && descr->long_name && + strcmp(long_name, descr->long_name) == 0) { + goto end; + } + } + +end: + return !descr->short_name && !descr->long_name ? NULL : descr; +} + +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, +}; + +static +enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, + const char * const next_orig_arg, + const struct bt_argpar_opt_descr * const descrs, + struct bt_argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + const char *short_opt_ch = short_opts; + + if (strlen(short_opts) == 0) { + g_string_append(parse_ret->error, "Invalid argument"); + goto error; + } + + while (*short_opt_ch) { + const char *opt_arg = NULL; + const struct bt_argpar_opt_descr *descr; + struct bt_argpar_item_opt *opt_item; + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *short_opt_ch, NULL); + if (!descr) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + g_string_append_printf(parse_ret->error, + "Unknown option `-%c`", *short_opt_ch); + goto error; + } + + if (descr->with_arg) { + if (short_opt_ch[1]) { + /* `-oarg` form */ + opt_arg = &short_opt_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + *used_next_orig_arg = true; + } + + /* + * We accept `-o ''` (empty option's argument), + * but not `-o` alone if an option's argument is + * expected. + */ + if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) { + g_string_append_printf(parse_ret->error, + "Missing required argument for option `-%c`", + *short_opt_ch); + *used_next_orig_arg = false; + goto error; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + g_ptr_array_add(parse_ret->items, opt_item); + + if (descr->with_arg) { + /* Option has an argument: no more options */ + break; + } + + /* Go to next short option */ + short_opt_ch++; + } + + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, + const char * const next_orig_arg, + const struct bt_argpar_opt_descr * const descrs, + struct bt_argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + const size_t max_len = 127; + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + const struct bt_argpar_opt_descr *descr; + struct bt_argpar_item_opt *opt_item; + + /* Option's argument, if any */ + const char *opt_arg = NULL; + + /* 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) { + g_string_append(parse_ret->error, "Invalid argument"); + goto error; + } + + /* Find the first `=` in original argument */ + eq_pos = strchr(long_opt_arg, '='); + if (eq_pos) { + const size_t long_opt_name_size = eq_pos - long_opt_arg; + + /* Isolate the option name */ + if (long_opt_name_size > max_len) { + g_string_append_printf(parse_ret->error, + "Invalid argument `--%s`", long_opt_arg); + goto error; + } + + memcpy(buf, long_opt_arg, long_opt_name_size); + buf[long_opt_name_size] = '\0'; + long_opt_name = buf; + } + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, '\0', long_opt_name); + if (!descr) { + g_string_append_printf(parse_ret->error, + "Unknown option `--%s`", long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + goto error; + } + + /* Find option's argument if any */ + if (descr->with_arg) { + if (eq_pos) { + /* `--long-opt=arg` style */ + opt_arg = eq_pos + 1; + } else { + /* `--long-opt arg` style */ + if (!next_orig_arg) { + g_string_append_printf(parse_ret->error, + "Missing required argument for option `--%s`", + long_opt_name); + goto error; + } + + opt_arg = next_orig_arg; + *used_next_orig_arg = true; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + g_ptr_array_add(parse_ret->items, opt_item); + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, + const char * const next_orig_arg, + const struct bt_argpar_opt_descr * const descrs, + struct bt_argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + + BT_ASSERT(orig_arg[0] == '-'); + + if (orig_arg[1] == '-') { + /* Long option */ + ret = parse_long_opt(&orig_arg[2], + next_orig_arg, descrs, parse_ret, + used_next_orig_arg); + } else { + /* Short option */ + ret = parse_short_opts(&orig_arg[1], + next_orig_arg, descrs, parse_ret, + used_next_orig_arg); + } + + return ret; +} + +static +void prepend_while_parsing_arg_to_error(GString * const error, + const unsigned int i, const char * const arg) +{ + /* 🙁 There's no g_string_prepend_printf()! */ + GString * const tmp_str = g_string_new(NULL); + + BT_ASSERT(error); + BT_ASSERT(arg); + + if (!tmp_str) { + goto end; + } + + g_string_append_printf(tmp_str, "While parsing argument #%u (`%s`): %s", + i + 1, arg, error->str); + g_string_assign(error, tmp_str->str); + g_string_free(tmp_str, TRUE); + +end: + return; +} + +BT_HIDDEN +struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, + const char * const *argv, + const struct bt_argpar_opt_descr * const descrs, + bool fail_on_unknown_opt) +{ + struct bt_argpar_parse_ret parse_ret = { 0 }; + unsigned int i; + unsigned int non_opt_index = 0; + + parse_ret.error = g_string_new(NULL); + if (!parse_ret.error) { + goto error; + } + + parse_ret.items = g_ptr_array_new_with_free_func( + (GDestroyNotify) destroy_item); + if (!parse_ret.items) { + goto error; + } + + for (i = 0; i < argc; i++) { + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + bool used_next_orig_arg = false; + const char * const orig_arg = argv[i]; + const char * const next_orig_arg = + i < argc - 1 ? argv[i + 1] : NULL; + + if (orig_arg[0] != '-') { + /* Non-option argument */ + struct bt_argpar_item_non_opt *non_opt_item = + create_non_opt_item(orig_arg, i, non_opt_index); + + if (!non_opt_item) { + goto error; + } + + non_opt_index++; + g_ptr_array_add(parse_ret.items, non_opt_item); + continue; + } + + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, + next_orig_arg, descrs, &parse_ret, &used_next_orig_arg); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: + BT_ASSERT(!used_next_orig_arg); + + if (fail_on_unknown_opt) { + prepend_while_parsing_arg_to_error( + parse_ret.error, i, orig_arg); + goto error; + } + + /* + * The current original argument is not + * considered ingested because it triggered an + * unknown option. + */ + parse_ret.ingested_orig_args = i; + g_string_free(parse_ret.error, TRUE); + parse_ret.error = NULL; + goto end; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + prepend_while_parsing_arg_to_error( + parse_ret.error, i, orig_arg); + goto error; + default: + bt_common_abort(); + } + + if (used_next_orig_arg) { + i++; + } + } + + parse_ret.ingested_orig_args = argc; + g_string_free(parse_ret.error, TRUE); + parse_ret.error = NULL; + goto end; + +error: + if (parse_ret.items) { + /* That's how we indicate that an error occured */ + g_ptr_array_free(parse_ret.items, TRUE); + parse_ret.items = NULL; + } + +end: + return parse_ret; +} + +BT_HIDDEN +void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret) +{ + BT_ASSERT(ret); + + if (ret->items) { + g_ptr_array_free(ret->items, TRUE); + ret->items = NULL; + } + + if (ret->error) { + g_string_free(ret->error, TRUE); + ret->error = NULL; + } +} diff --git a/argpar/argpar.h b/argpar/argpar.h new file mode 100644 index 0000000..85a663d --- /dev/null +++ b/argpar/argpar.h @@ -0,0 +1,212 @@ +#ifndef BABELTRACE_ARGPAR_H +#define BABELTRACE_ARGPAR_H + +/* + * Copyright 2019 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include "common/macros.h" + +/* Sentinel for an option descriptor array */ +#define BT_ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } + +/* Option descriptor */ +struct bt_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; + + /* True if this option has an argument */ + const bool with_arg; +}; + +/* Item type */ +enum bt_argpar_item_type { + /* Option */ + BT_ARGPAR_ITEM_TYPE_OPT, + + /* Non-option */ + BT_ARGPAR_ITEM_TYPE_NON_OPT, +}; + +/* Base item */ +struct bt_argpar_item { + enum bt_argpar_item_type type; +}; + +/* Option item */ +struct bt_argpar_item_opt { + struct bt_argpar_item base; + + /* Corresponding descriptor */ + const struct bt_argpar_opt_descr *descr; + + /* Argument, or `NULL` if none */ + const char *arg; +}; + +/* Non-option item */ +struct bt_argpar_item_non_opt { + struct bt_argpar_item base; + + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; + + /* Index of this argument amongst all original arguments (`argv`) */ + unsigned int orig_index; + + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +}; + +/* What is returned by bt_argpar_parse() */ +struct bt_argpar_parse_ret { + /* Array of `struct bt_argpar_item *`, or `NULL` on error */ + GPtrArray *items; + + /* Error string, or `NULL` if none */ + GString *error; + + /* Number of original arguments (`argv`) ingested */ + unsigned int ingested_orig_args; +}; + +/* + * Parses the arguments `argv` of which the count is `argc` using the + * sentinel-terminated (use `BT_ARGPAR_OPT_DESCR_SENTINEL`) option + * descriptor array `descrs`. + * + * This function considers ALL the elements of `argv`, including the + * first one, so that you would typically pass `argc - 1` and + * `&argv[1]` from what main() receives. + * + * This argument parser supports: + * + * * Short options without an argument, possibly tied together: + * + * -f -auf -n + * + * * Short options with argument: + * + * -b 45 -f/mein/file -xyzhello + * + * * Long options without an argument: + * + * --five-guys --burger-king --pizza-hut --subway + * + * * Long options with arguments: + * + * --security enable --time=18.56 + * + * * Non-option arguments (anything else). + * + * This function does not accept `-` or `--` as arguments. The latter + * means "end of options" for many command-line tools, but this function + * is all about keeping the order of the arguments, so it does not mean + * much to put them at the end. This has the side effect that a + * non-option argument cannot have the form of an option, for example if + * you need to pass the exact relative path `--component`. In that case, + * you would need to pass `./--component`. There's no generic way to + * escape `-` for the moment. + * + * This function accepts duplicate options (the resulting array of items + * contains one entry for each instance). + * + * On success, this function returns an array of items + * (`struct bt_argpar_item *`). Each item is to be casted to the + * appropriate type (`struct bt_argpar_item_opt *` or + * `struct bt_argpar_item_non_opt *`) depending on its type. + * + * The returned array contains the items in the same order that the + * arguments were parsed, including non-option arguments. This means, + * for example, that for + * + * --hello --meow=23 /path/to/file -b + * + * the function returns an array of four items: two options, one + * non-option, and one option. + * + * In the returned structure, `ingested_orig_args` is the number of + * ingested arguments within `argv` to produce the resulting array of + * items. If `fail_on_unknown_opt` is true, then on success + * `ingested_orig_args` is equal to `argc`. Otherwise, + * `ingested_orig_args` contains the number of original arguments until + * an unknown _option_ occurs. For example, with + * + * --great --white contact nuance --shark nuclear + * + * if `--shark` is not described within `descrs` and + * `fail_on_unknown_opt` is false, then `ingested_orig_args` is 4 (two + * options, two non-options), whereas `argc` is 6. + * + * This makes it possible to know where a command name is, for example. + * With those arguments: + * + * --verbose --stuff=23 do-something --specific-opt -f -b + * + * and the descriptors for `--verbose` and `--stuff` only, the function + * returns the `--verbose` and `--stuff` option items, the + * `do-something` non-option item, and that three original arguments + * were ingested. This means you can start the next argument parsing + * stage, with option descriptors depending on the command name, at + * `&argv[3]`. + * + * Note that `ingested_orig_args` is not always equal to the number of + * returned items, as + * + * --hello -fdw + * + * for example contains two ingested original arguments, but four + * resulting items. + * + * On failure, the returned structure's `items` member is `NULL`, and + * the `error` string member contains details about the error. + * + * You can finalize the returned structure with + * bt_argpar_parse_ret_fini(). + */ +BT_HIDDEN +struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, + const char * const *argv, + const struct bt_argpar_opt_descr *descrs, + bool fail_on_unknown_opt); + +/* + * Finalizes what is returned by bt_argpar_parse(). + * + * It is safe to call bt_argpar_parse() multiple times with the same + * structure. + */ +BT_HIDDEN +void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret); + +#endif /* BABELTRACE_ARGPAR_H */ diff --git a/tests/test_argpar.c b/tests/test_argpar.c new file mode 100644 index 0000000..fa915d8 --- /dev/null +++ b/tests/test_argpar.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2019 Philippe Proulx + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "tap/tap.h" +#include "common/assert.h" + +#include "argpar/argpar.h" + +/* + * Tests that the command line `cmdline`, with non-quoted + * space-delimited arguments, once parsed given the option descriptors + * `descrs` and the option `fail_on_unknown_opt`, succeeds and gives the + * expected command line `expected_cmd_line` and number of ingested + * original arguments `expected_ingested_orig_args`. + * + * The resulting command-line is built from the resulting arguments, + * space-delimiting each argument, preferring the `--long-opt=arg` style + * over the `-s arg` style, and using the `arg` form for non-option + * arguments where `A` is the original argument index and `B` is the + * non-option argument index. + */ +static +void test_succeed(const char *cmdline, + const char *expected_cmd_line, + const struct bt_argpar_opt_descr *descrs, + unsigned int expected_ingested_orig_args) +{ + struct bt_argpar_parse_ret parse_ret; + GString *res_str = g_string_new(NULL); + gchar **argv = g_strsplit(cmdline, " ", 0); + unsigned int i; + + BT_ASSERT(argv); + BT_ASSERT(res_str); + parse_ret = bt_argpar_parse(g_strv_length(argv), + (const char * const *) argv, descrs, false); + ok(parse_ret.items, + "bt_argpar_parse() succeeds for command line `%s`", cmdline); + ok(!parse_ret.error, + "bt_argpar_parse() does not write an error for command line `%s`", cmdline); + ok(parse_ret.ingested_orig_args == expected_ingested_orig_args, + "bt_argpar_parse() returns the correct number of ingested " + "original arguments for command line `%s`", cmdline); + if (parse_ret.ingested_orig_args != expected_ingested_orig_args) { + diag("Expected: %u Got: %u", expected_ingested_orig_args, + parse_ret.ingested_orig_args); + } + + if (!parse_ret.items) { + fail("bt_argpar_parse() returns the expected parsed arguments " + "for command line `%s`", cmdline); + goto end; + } + + for (i = 0; i < parse_ret.items->len; i++) { + const struct bt_argpar_item *arg = parse_ret.items->pdata[i]; + + switch (arg->type) { + case BT_ARGPAR_ITEM_TYPE_OPT: + { + const struct bt_argpar_item_opt *arg_opt = + (const void *) arg; + + if (arg_opt->descr->long_name) { + g_string_append_printf(res_str, "--%s", + arg_opt->descr->long_name); + + if (arg_opt->arg) { + g_string_append_printf(res_str, "=%s", + arg_opt->arg); + } + + g_string_append_c(res_str, ' '); + } else if (arg_opt->descr->short_name) { + g_string_append_printf(res_str, "-%c", + arg_opt->descr->short_name); + + if (arg_opt->arg) { + g_string_append_printf(res_str, " %s", + arg_opt->arg); + } + + g_string_append_c(res_str, ' '); + } + + break; + } + case BT_ARGPAR_ITEM_TYPE_NON_OPT: + { + const struct bt_argpar_item_non_opt *arg_non_opt = + (const void *) arg; + + g_string_append_printf(res_str, "%s<%u,%u> ", + arg_non_opt->arg, arg_non_opt->orig_index, + arg_non_opt->non_opt_index); + break; + } + default: + abort(); + } + } + + if (res_str->len > 0) { + g_string_truncate(res_str, res_str->len - 1); + } + + ok(strcmp(expected_cmd_line, res_str->str) == 0, + "bt_argpar_parse() returns the expected parsed arguments " + "for command line `%s`", cmdline); + if (strcmp(expected_cmd_line, res_str->str) != 0) { + diag("Expected: `%s`", expected_cmd_line); + diag("Got: `%s`", res_str->str); + } + +end: + bt_argpar_parse_ret_fini(&parse_ret); + g_string_free(res_str, TRUE); + g_strfreev(argv); +} + +static +void succeed_tests(void) +{ + /* No arguments */ + { + const struct bt_argpar_opt_descr descrs[] = { + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "", + "", + descrs, 0); + } + + /* Single long option */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "salut", false }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--salut", + "--salut", + descrs, 1); + } + + /* Single short option */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'f', NULL, false }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-f", + "-f", + descrs, 1); + } + + /* Short and long option (aliases) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'f', "flaw", false }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-f --flaw", + "--flaw --flaw", + descrs, 2); + } + + /* Long option with argument (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "tooth", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--tooth 67", + "--tooth=67", + descrs, 2); + } + + /* Long option with argument (equal form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "polish", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--polish=brick", + "--polish=brick", + descrs, 1); + } + + /* Short option with argument (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'c', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-c chilly", + "-c chilly", + descrs, 2); + } + + /* Short option with argument (glued form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'c', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-cchilly", + "-c chilly", + descrs, 1); + } + + /* Short and long option (aliases) with argument (all forms) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', "dry", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--dry=rate -dthing --dry street --dry=shape", + "--dry=rate --dry=thing --dry=street --dry=shape", + descrs, 5); + } + + /* Many short options, last one with argument (glued form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 0, 'e', NULL, false }, + { 0, 'f', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-defmeow", + "-d -e -f meow", + descrs, 1); + } + + /* Many options */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 0, 'e', "east", true }, + { 0, '\0', "mind", false }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-d --mind -destart --mind --east cough -d --east=itch", + "-d --mind -d --east=start --mind --east=cough -d --east=itch", + descrs, 8); + } + + /* Single non-option argument */ + { + const struct bt_argpar_opt_descr descrs[] = { + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "kilojoule", + "kilojoule<0,0>", + descrs, 1); + } + + /* Two non-option arguments */ + { + const struct bt_argpar_opt_descr descrs[] = { + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "kilojoule mitaine", + "kilojoule<0,0> mitaine<1,1>", + descrs, 2); + } + + /* Single non-option argument mixed with options */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 0, '\0', "squeeze", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-d sprout yes --squeeze little bag -d", + "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d", + descrs, 7); + } + + /* Unknown short option (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-d salut -e -d meow", + "-d salut", + descrs, 2); + } + + /* Unknown short option (glued form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'd', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-dsalut -e -d meow", + "-d salut", + descrs, 1); + } + + /* Unknown long option (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "sink", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--sink party --food --sink impulse", + "--sink=party", + descrs, 2); + } + + /* Unknown long option (equal form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "sink", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--sink=party --food --sink=impulse", + "--sink=party", + descrs, 1); + } + + /* Unknown option before non-option argument */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "thumb", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--thumb=party --food bateau --thumb waves", + "--thumb=party", + descrs, 1); + } + + /* Unknown option after non-option argument */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "thumb", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--thumb=party wound --food --thumb waves", + "--thumb=party wound<1,0>", + descrs, 2); + } + + /* Valid `---opt` */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "-fuel", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "---fuel=three", + "---fuel=three", + descrs, 1); + } + + /* Long option containing `=` in argument (equal form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "zebra", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--zebra=three=yes", + "--zebra=three=yes", + descrs, 1); + } + + /* Short option's argument starting with `-` (glued form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'z', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-z-will", + "-z -will", + descrs, 1); + } + + /* Short option's argument starting with `-` (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'z', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-z -will", + "-z -will", + descrs, 2); + } + + /* Long option's argument starting with `-` (space form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "janine", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--janine -sutto", + "--janine=-sutto", + descrs, 2); + } + + /* Long option's argument starting with `-` (equal form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "janine", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "--janine=-sutto", + "--janine=-sutto", + descrs, 1); + } + + /* Long option's empty argument (equal form) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'f', NULL, false }, + { 0, '\0', "yeah", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_succeed( + "-f --yeah= -f", + "-f --yeah= -f", + descrs, 3); + } +} + +/* + * Tests that the command line `cmdline`, with non-quoted + * space-delimited arguments, once parsed given the option descriptors + * `descrs`, fails and gives the expected error `expected_error`. + */ +static +void test_fail(const char *cmdline, const char *expected_error, + const struct bt_argpar_opt_descr *descrs) +{ + struct bt_argpar_parse_ret parse_ret; + gchar **argv = g_strsplit(cmdline, " ", 0); + + parse_ret = bt_argpar_parse(g_strv_length(argv), + (const char * const *) argv, descrs, true); + ok(!parse_ret.items, + "bt_argpar_parse() fails for command line `%s`", cmdline); + ok(parse_ret.error, + "bt_argpar_parse() writes an error string for command line `%s`", + cmdline); + if (parse_ret.items) { + fail("bt_argpar_parse() writes the expected error string"); + goto end; + } + + ok(strcmp(expected_error, parse_ret.error->str) == 0, + "bt_argpar_parse() writes the expected error string " + "for command line `%s`", cmdline); + if (strcmp(expected_error, parse_ret.error->str) != 0) { + diag("Expected: `%s`", expected_error); + diag("Got: `%s`", parse_ret.error->str); + } + +end: + bt_argpar_parse_ret_fini(&parse_ret); + g_strfreev(argv); +} + +static +void fail_tests(void) +{ + /* Unknown long option */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "thumb", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "--thumb=party --meow", + "While parsing argument #2 (`--meow`): Unknown option `--meow`", + descrs); + } + + /* Unknown short option */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "thumb", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "--thumb=party -x", + "While parsing argument #2 (`-x`): Unknown option `-x`", + descrs); + } + + /* Missing long option argument */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, '\0', "thumb", true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "--thumb", + "While parsing argument #1 (`--thumb`): Missing required argument for option `--thumb`", + descrs); + } + + /* Missing short option argument */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'k', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "-k", + "While parsing argument #1 (`-k`): Missing required argument for option `-k`", + descrs); + } + + /* Missing short option argument (multiple glued) */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'a', NULL, false }, + { 0, 'b', NULL, false }, + { 0, 'c', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "-abc", + "While parsing argument #1 (`-abc`): Missing required argument for option `-c`", + descrs); + } + + /* Invalid `-` */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'a', NULL, false }, + { 0, 'b', NULL, false }, + { 0, 'c', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "-ab - -c", + "While parsing argument #2 (`-`): Invalid argument", + descrs); + } + + /* Invalid `--` */ + { + const struct bt_argpar_opt_descr descrs[] = { + { 0, 'a', NULL, false }, + { 0, 'b', NULL, false }, + { 0, 'c', NULL, true }, + BT_ARGPAR_OPT_DESCR_SENTINEL + }; + + test_fail( + "-ab -- -c", + "While parsing argument #2 (`--`): Invalid argument", + descrs); + } +} + +int main(void) +{ + plan_tests(129); + succeed_tests(); + fail_tests(); + return exit_status(); +}