diff --git a/Documentation/technical/api-parse-options.adoc b/Documentation/technical/api-parse-options.adoc index 95b7924e84..f59d8e90a5 100644 --- a/Documentation/technical/api-parse-options.adoc +++ b/Documentation/technical/api-parse-options.adoc @@ -197,6 +197,11 @@ are the bitwise-or of: Internal flag, set on options that were expanded from a configured alias. It should not be set by callers. +`PARSE_OPT_EARLY`:: + Report this option to `early_scan_options()`, which looks at a + few options before parsing the command line for real. Ignored + by `parse_options()` itself. + `PARSE_OPT_NOCOMPLETE`:: Do not offer this option for completion. diff --git a/parse-options.c b/parse-options.c index d94d6fed93..7e8b406129 100644 --- a/parse-options.c +++ b/parse-options.c @@ -730,6 +730,8 @@ static void parse_options_check(const struct option *opts) opts->long_name)) optbug(opts, "uses feature " "not supported for dashless options"); + if ((opts->flags & PARSE_OPT_EARLY) && !opts->long_name) + optbug(opts, "uses PARSE_OPT_EARLY, which needs a long name"); if (opts->type == OPTION_SET_INT && !opts->defval && opts->long_name && !(opts->flags & PARSE_OPT_NONEG)) optbug(opts, "OPTION_SET_INT 0 should not be negatable"); @@ -767,6 +769,8 @@ static void parse_options_check(const struct option *opts) case OPTION_SUBCOMMAND: if (!opts->value || !opts->subcommand_fn) optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function"); + if (opts->flags & PARSE_OPT_EARLY) + optbug(opts, "OPTION_SUBCOMMAND does not support PARSE_OPT_EARLY"); if (!subcommand_value) subcommand_value = opts->value; else if (subcommand_value != opts->value) @@ -1314,6 +1318,141 @@ int parse_options(int argc, const char **argv, return parse_options_end(&ctx); } +/* + * Look for `name`, a long option without its leading "--", among + * `option`, the way parse_options() would. Return true if it names an + * option exactly, or as an unambiguous abbreviation that is not + * disallowed, with `found` set to that option and its flags, and + * `stuck` to the value stuck to it, if any, or to NULL. + */ +static bool find_early_scan_option(struct parse_opt_ctx_t *p, + const char *name, + const struct option *option, + bool disallow_abbrev, + struct parsed_option *found, + const char **stuck) +{ + struct parsed_option other; + enum long_opt_lookup result = lookup_long_opt(p, name, option, + found, &other, stuck); + + switch (result) { + case LONG_OPT_EXACT: + return true; + case LONG_OPT_ABBREV: + return !disallow_abbrev; + case LONG_OPT_AMBIGUOUS: + case LONG_OPT_UNKNOWN: + return false; + } + + BUG("unknown enum long_opt_lookup value '%d'", result); +} + +struct early_scan_state { + struct parse_opt_ctx_t ctx; + const struct option *option; + enum early_scan_flags flags; + early_scan_fn *fn; + void *data; + bool disallow_abbrev; +}; + +/* + * Look at argv[*i]. Return 'true' to go on scanning, with *i on the + * last argument consumed. Return 'false' to stop, with *i set to the + * first argument not consumed. + */ +static bool early_scan_one_option(struct early_scan_state *s, + int argc, const char **argv, int *i) +{ + const char *name; + const char *value; + struct parsed_option found; + bool unset; + int pos = *i; + const char *arg = argv[pos]; + + /* + * parse_options() always stops parsing options at these, + * whatever its flags, so nothing after them is an option. + */ + if (!strcmp(arg, "--") || !strcmp(arg, "--end-of-options")) + return false; + + /* non-option */ + if (*arg != '-' || !arg[1]) + return !(s->flags & EARLY_SCAN_STOP_AT_NON_OPTION); + + /* short option */ + if (!skip_prefix(arg, "--", &name)) + return false; + + /* unknown, ambiguous or disallowed abbreviation */ + if (!find_early_scan_option(&s->ctx, name, s->option, + s->disallow_abbrev, &found, &value)) + return false; + + /* A low-level callback may consume any number of arguments. */ + if (found.option->ll_callback) + return false; + + unset = !!(found.flags & OPT_UNSET); + + /* parse_options() rejects these, so don't go further. */ + if (value && (unset || (found.option->flags & PARSE_OPT_NOARG))) + return false; + + /* + * Like get_arg(), take the next argument as the value when it is + * not stuck to the option. When there is no next argument, + * parse_options() uses the default value of an option with + * PARSE_OPT_LASTARG_DEFAULT, but errors out for other options. + */ + if (!value && !unset && parse_options_takes_argument(found.option)) { + if (pos + 1 < argc) + value = argv[++*i]; + else if (!(found.option->flags & PARSE_OPT_LASTARG_DEFAULT)) + return false; + } + + if ((found.option->flags & PARSE_OPT_EARLY) && + s->fn(found.option, value, unset, pos, s->data)) { + /* the option, and its value if any, have been consumed */ + ++*i; + return false; + } + + return true; +} + +int early_scan_options(int argc, const char **argv, + const struct option *option, + enum early_scan_flags flags, + early_scan_fn *fn, void *data) +{ + struct early_scan_state s = { + .flags = flags, + .fn = fn, + .data = data, + .disallow_abbrev = + git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0), + }; + struct option *real_option = preprocess_options(&s.ctx, option); + int i; + + s.option = real_option ? real_option : option; + parse_options_check(s.option); + + for (i = 0; i < argc; i++) + if (!early_scan_one_option(&s, argc, argv, &i)) + break; + + free_preprocessed_options(real_option); + free(s.ctx.alias_groups); + return i; +} + static int usage_argh(const struct option *opts, FILE *outfile) { const char *s; diff --git a/parse-options.h b/parse-options.h index 589f8d9008..14e8568989 100644 --- a/parse-options.h +++ b/parse-options.h @@ -51,6 +51,7 @@ enum parse_opt_option_flags { PARSE_OPT_NODASH = 1 << 5, PARSE_OPT_LITERAL_ARGHELP = 1 << 6, PARSE_OPT_FROM_ALIAS = 1 << 7, + PARSE_OPT_EARLY = 1 << 8, /* only for early_scan_options() */ PARSE_OPT_NOCOMPLETE = 1 << 9, PARSE_OPT_COMP_ARG = 1 << 10, PARSE_OPT_CMDMODE = 1 << 11, @@ -502,6 +503,103 @@ static inline void die_for_incompatible_opt2(int opt1, const char *opt1_name, BUG("option callback expects an argument"); \ } while(0) +/*----- Early scan: scanning argv before the actual option parsing -----*/ + +/* + * Some commands need to look at a few options before they can parse + * their command line for real, for example because the result decides + * whether a repository is needed at all. + * + * Such an early scan has to know which options take their value as a + * separate argument, or it could mistake such a value for an + * option. The functions below allow performing such early scans + * without being fooled by option values. + */ + +/* + * Called by early_scan_options() for each argument matching a + * `struct option` with PARSE_OPT_EARLY set. + * + * - `option` is the matching option, (may point into a temporary + * copy of the options, so must not be used after the scan,) + * - `value` its value, or NULL if it doesn't take one or if its value + * is optional and wasn't given, as with PARSE_OPT_OPTARG, or with + * PARSE_OPT_LASTARG_DEFAULT when the option is the last argument, + * - `unset` is 'true' for the negated form of the option (like + * for parse_opt_cb()), in which case `value` is always NULL, and + * - `pos` the index of the option in argv. + * + * Returning a non-zero value stops the scan right after the option and + * its value, if any. + */ +typedef int early_scan_fn(const struct option *option, const char *value, + bool unset, int pos, void *data); + +enum early_scan_flags { + EARLY_SCAN_STOP_AT_NON_OPTION = 1 << 0, /* Stop at any non option */ +}; + +/* + * Scan `argv` for the options described by `option`, calling `fn` for + * each of those that have PARSE_OPT_EARLY set. `argv` is not + * modified. + * + * `fn` may be NULL when no option has PARSE_OPT_EARLY set, which is + * useful to only find out where the scan stops. + * + * The scan always stops at "--" and at "--end-of-options", as + * parse_options() always stops parsing options there too, whatever its + * flags. PARSE_OPT_KEEP_DASHDASH and PARSE_OPT_KEEP_UNKNOWN_OPT only + * decide if the terminator is left in argv, not if it terminates. + * + * Returns the index of the first argument not consumed by the scan, + * which is `argc` when the whole array was scanned. + * + * Long options are looked up the same way as parse_options() does, + * so their negated forms, their unambiguous abbreviations, unless + * GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS is set, and the OPTION_ALIAS + * entries standing for them are all recognized, and their value is + * skipped when they take one. + * + * To never misinterpret an option value as an option though, the scan + * stops at any argument it cannot reliably skip, that is: + * + * - a short option, or a bundle of them, + * + * - an unknown option, or an ambiguous or disallowed abbreviation, + * + * - an option with a low-level callback, as such a callback may + * consume any number of arguments, + * + * - an option with a value stuck to it that parse_options() would + * reject, like a negated option or a PARSE_OPT_NOARG one, + * + * - an option requiring a value when it is the last argument, which + * parse_options() would also reject. + * + * In all these cases, the scan can fail to see an option that + * parse_options() would accept, but it doesn't report an option that + * parse_options() would not, so that callers err on the safe side. + * + * This requires the scan to be told about the way the later + * parse_options() call treats its arguments though: + * + * - when parse_options() is called with PARSE_OPT_STOP_AT_NON_OPTION + * or with subcommands, callers should pass + * EARLY_SCAN_STOP_AT_NON_OPTION, + * + * - when parse_options() is called with PARSE_OPT_KEEP_UNKNOWN_OPT, + * which disables abbreviations there but not here, callers should + * not use the scan. + * + * Passing EARLY_SCAN_STOP_AT_NON_OPTION in other cases is safe, and + * useful when the caller rejects non-option arguments itself. + */ +int early_scan_options(int argc, const char **argv, + const struct option *option, + enum early_scan_flags flags, + early_scan_fn *fn, void *data); + /*----- incremental advanced APIs -----*/ struct parse_opt_cmdmode_list; diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index f181f0c02d..de3dd5751c 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -383,3 +383,99 @@ int cmd__parse_subcommand(int argc, const char **argv) return parse_subcommand__cmd(argc, argv, test_flags); } + +static enum parse_opt_result early_ll_callback(struct parse_opt_ctx_t *ctx UNUSED, + const struct option *opt UNUSED, + const char *arg UNUSED, + int unset UNUSED) +{ + return 0; +} + +static int show_early_option(const struct option *opt, const char *value, + bool unset, int pos, void *data) +{ + int *stop_at_found = data; + + printf("found: %s at %d", opt->long_name, pos); + if (unset) + printf(" unset"); + if (value) + printf(" value: %s", value); + putchar('\n'); + return *stop_at_found; +} + +int cmd__early_scan_options(int argc, const char **argv) +{ + char *a_string = NULL; + int an_int = 0, a_bool = 0, a_short = 0; + + const struct option option[] = { + OPT_GROUP("early scan test options"), + OPT_BOOL_F(0, "wanted", &a_bool, + "wanted option taking no value", + PARSE_OPT_EARLY), + OPT_STRING_F(0, "wanted-value", &a_string, "str", + "wanted option taking a value", + PARSE_OPT_EARLY), + OPT_STRING(0, "skipped-value", &a_string, "str", + "option whose value has to be skipped"), + OPT_ALIAS(0, "alias-of-skipped", "skipped-value"), + OPT_INTEGER(0, "number", &an_int, + "option taking an integer value"), + OPT_STRING_F(0, "optarg", &a_string, "str", + "option with an optional value", + PARSE_OPT_OPTARG), + OPT_STRING_F(0, "lastarg", &a_string, "str", + "option with a last argument default", + PARSE_OPT_LASTARG_DEFAULT), + OPT_STRING_F(0, "early-optarg", &a_string, "str", + "early option with an optional value", + PARSE_OPT_EARLY | PARSE_OPT_OPTARG), + OPT_STRING_F(0, "early-lastarg", &a_string, "str", + "early option with a last argument default", + PARSE_OPT_EARLY | PARSE_OPT_LASTARG_DEFAULT), + OPT_BOOL_F(0, "no-early", &a_bool, + "early option whose name starts with 'no-'", + PARSE_OPT_EARLY), + OPT_BOOL('s', NULL, &a_short, "short only option"), + { + .type = OPTION_LOWLEVEL_CALLBACK, + .long_name = "low-level", + .help = "option with a low-level callback", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .ll_callback = early_ll_callback, + }, + { + .type = OPTION_CALLBACK, + .long_name = "ll-value", + .argh = "str", + .help = "option with a value and a low-level callback", + .flags = PARSE_OPT_NONEG, + .ll_callback = early_ll_callback, + }, + OPT_END() + }; + + enum early_scan_flags flags = 0; + int stop_at_found = 0; + int stopped; + + while (argc > 1 && *argv[1] == '-') { + if (!strcmp(argv[1], "--stop-at-non-option")) + flags |= EARLY_SCAN_STOP_AT_NON_OPTION; + else if (!strcmp(argv[1], "--stop-at-found")) + stop_at_found = 1; + else + break; + argc--; + argv++; + } + + stopped = early_scan_options(argc - 1, argv + 1, option, flags, + show_early_option, &stop_at_found); + printf("stopped at: %d of %d\n", stopped, argc - 1); + + return 0; +} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index b71a22b43b..5d2f5877d9 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -50,6 +50,7 @@ static struct test_cmd cmds[] = { { "pack-mtimes", cmd__pack_mtimes }, { "parse-options", cmd__parse_options }, { "parse-options-flags", cmd__parse_options_flags }, + { "early-scan-options", cmd__early_scan_options }, { "parse-pathspec-file", cmd__parse_pathspec_file }, { "parse-subcommand", cmd__parse_subcommand }, { "partial-clone", cmd__partial_clone }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index f2885b33d5..071306d52d 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -43,6 +43,7 @@ int cmd__pack_deltas(int argc, const char **argv); int cmd__pack_mtimes(int argc, const char **argv); int cmd__parse_options(int argc, const char **argv); int cmd__parse_options_flags(int argc, const char **argv); +int cmd__early_scan_options(int argc, const char **argv); int cmd__parse_pathspec_file(int argc, const char** argv); int cmd__parse_subcommand(int argc, const char **argv); int cmd__partial_clone(int argc, const char **argv); diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 449fff4d34..c44b12e46d 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -845,4 +845,325 @@ test_expect_success 'u16 limits range' ' test_grep "value 65536 for option .u16. not in range \[0,65535\]" err ' +test_expect_success 'early_scan_options() finds a wanted option' ' + test-tool early-scan-options --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 0 + stopped at: 1 of 1 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() reads a stuck or separate value' ' + test-tool early-scan-options --wanted-value=one >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 value: one + stopped at: 1 of 1 + EOF + test_cmp expect actual && + test-tool early-scan-options --wanted-value two >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 value: two + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() skips the value of other options' ' + test-tool early-scan-options --skipped-value --wanted >actual && + cat >expect <<-\EOF && + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --skipped-value one --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 2 + stopped at: 3 of 3 + EOF + test_cmp expect actual && + test-tool early-scan-options --number --wanted >actual && + cat >expect <<-\EOF && + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at a last option missing its value' ' + test-tool early-scan-options --wanted-value >actual && + cat >expect <<-\EOF && + stopped at: 0 of 1 + EOF + test_cmp expect actual && + test-tool early-scan-options --wanted --skipped-value >actual && + cat >expect <<-\EOF && + found: wanted at 0 + stopped at: 1 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() always stops at "--"' ' + test-tool early-scan-options -- --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --stop-at-non-option -- --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --skipped-value -- --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 2 + stopped at: 3 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() always stops at "--end-of-options"' ' + test-tool early-scan-options --end-of-options --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --stop-at-non-option \ + --end-of-options --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() can stop at a non-option' ' + test-tool early-scan-options --stop-at-non-option \ + arg --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --stop-at-non-option \ + --skipped-value arg --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 2 + stopped at: 3 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() matches unambiguous abbreviations' ' + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool early-scan-options --wanted-v=x >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 value: x + stopped at: 1 of 1 + EOF + test_cmp expect actual && + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool early-scan-options --wanted-v x --wanted >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 value: x + found: wanted at 2 + stopped at: 3 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() skips the value of an abbreviation' ' + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool early-scan-options --skipped --wanted >actual && + cat >expect <<-\EOF && + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at an ambiguous abbreviation' ' + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool early-scan-options --want --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at disallowed abbreviations' ' + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=true \ + test-tool early-scan-options --wanted-v=x >actual && + cat >expect <<-\EOF && + stopped at: 0 of 1 + EOF + test_cmp expect actual && + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=true \ + test-tool early-scan-options --skipped --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at an unknown option' ' + test-tool early-scan-options --bogus --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() resolves aliases' ' + test-tool early-scan-options --alias-of-skipped --wanted >actual && + cat >expect <<-\EOF && + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --alias-of-skipped x --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 2 + stopped at: 3 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() does not skip an optional value' ' + test-tool early-scan-options --optarg --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 1 + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --optarg=5 --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 1 + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() skips a last argument default value' ' + test-tool early-scan-options --lastarg --wanted >actual && + cat >expect <<-\EOF && + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --wanted --lastarg >actual && + cat >expect <<-\EOF && + found: wanted at 0 + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() matches a stuck optional value' ' + test-tool early-scan-options --early-optarg=one >actual && + cat >expect <<-\EOF && + found: early-optarg at 0 value: one + stopped at: 1 of 1 + EOF + test_cmp expect actual && + test-tool early-scan-options --early-lastarg=two >actual && + cat >expect <<-\EOF && + found: early-lastarg at 0 value: two + stopped at: 1 of 1 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() does not take a separate optional value' ' + test-tool early-scan-options --early-optarg --wanted >actual && + cat >expect <<-\EOF && + found: early-optarg at 0 + found: wanted at 1 + stopped at: 2 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() takes a last argument default value' ' + test-tool early-scan-options --early-lastarg --wanted >actual && + cat >expect <<-\EOF && + found: early-lastarg at 0 value: --wanted + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --early-lastarg >actual && + cat >expect <<-\EOF && + found: early-lastarg at 0 + stopped at: 1 of 1 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at short options' ' + test-tool early-scan-options -s --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops at low-level callbacks' ' + test-tool early-scan-options --low-level --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --ll-value x --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() stops after what the callback consumed' ' + test-tool early-scan-options --stop-at-found --wanted --wanted >actual && + cat >expect <<-\EOF && + found: wanted at 0 + stopped at: 1 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --stop-at-found \ + --wanted-value x --wanted >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 value: x + stopped at: 2 of 3 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() reports negated options' ' + test-tool early-scan-options --no-wanted >actual && + cat >expect <<-\EOF && + found: wanted at 0 unset + stopped at: 1 of 1 + EOF + test_cmp expect actual && + test-tool early-scan-options --early >actual && + cat >expect <<-\EOF && + found: no-early at 0 unset + stopped at: 1 of 1 + EOF + test_cmp expect actual && + test-tool early-scan-options --no-early >actual && + cat >expect <<-\EOF && + found: no-early at 0 + stopped at: 1 of 1 + EOF + test_cmp expect actual +' + +test_expect_success 'early_scan_options() does not take a value for negated options' ' + test-tool early-scan-options --no-wanted-value --wanted >actual && + cat >expect <<-\EOF && + found: wanted-value at 0 unset + found: wanted at 1 + stopped at: 2 of 2 + EOF + test_cmp expect actual && + test-tool early-scan-options --no-wanted-value=x --wanted >actual && + cat >expect <<-\EOF && + stopped at: 0 of 2 + EOF + test_cmp expect actual +' + test_done