From d716512870317692129b2b4d706a82b393ffbe6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:37:40 +0200 Subject: [PATCH 1/8] subtree: disallow --no-{help,quiet,debug,branch,message} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "git subtree" only handles the negated variant of the options annotate, prefix, onto, rejoin, ignore-joins and squash explicitly. help is handled by "git rev-parse --parseopt" implicitly, but not its negated form. Disable negation for it and the for the rest of the options to get a helpful error message when trying them. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- contrib/subtree/git-subtree.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 7db4c45676..e0c5d3b0de 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -33,19 +33,19 @@ git subtree split --prefix= [] git subtree pull --prefix= git subtree push --prefix= -- -h,help show the help -q,quiet quiet -d,debug show debug messages +h,help! show the help +q,quiet! quiet +d,debug! show debug messages P,prefix= the name of the subdir to split out options for 'split' (also: 'push') annotate= add a prefix to commit message of new commits -b,branch= create a new branch from the split subtree +b,branch!= create a new branch from the split subtree ignore-joins ignore prior --rejoin commits onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin') squash merge subtree changes as a single commit -m,message= use the given message as the commit message for the merge commit +m,message!= use the given message as the commit message for the merge commit " indent=0 From aa43619bdf690c8ed44746030552a35244f97af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:37:55 +0200 Subject: [PATCH 2/8] t1502, docs: disallow --no-help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "git rev-parse --parseopt" handles the built-in options -h and --help, but not --no-help. Make test definitions and documentation examples more realistic by disabling negation. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 2 +- t/t1502-rev-parse-parseopt.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index f26a7591e3..6e8ff9ace1 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -398,7 +398,7 @@ some-command [] ... some-command does foo and bar! -- -h,help show the help +h,help! show the help foo some nifty option --foo bar= some cool option --bar with an argument diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index dd811b7fb4..0cdc6eb8b3 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -9,7 +9,7 @@ test_expect_success 'setup optionspec' ' | |some-command does foo and bar! |-- -|h,help show the help +|h,help! show the help | |foo some nifty option --foo |bar= some cool option --bar with an argument @@ -288,7 +288,7 @@ test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" | [--another-option] |cmd [--yet-another-option] |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && @@ -322,7 +322,7 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l |line |blurb |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && From 8dcb49021e0134a1be1f533596e2bcf8313dea33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:38:56 +0200 Subject: [PATCH 3/8] t1502: move optionspec help output to a file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "git rev-parse --parseopt" shows the short help with its description of all recognized options twice: When called with -h or --help, and after reporting an unknown option. Move the one for optionspec into a file and use it in two tests to deduplicate that part. "git rev-parse --parseopt -- --h" wraps the help text in "cat <<\EOF" and "EOF". Keep that part in the file to use it as is in the test that needs it and simply remove it in the other one using sed. Disable whitespace checking for the file using an attribute, as we need to keep its spaces intact and wouldn't want a stray --whitespace=fix turn them into tabs. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- t/t1502-rev-parse-parseopt.sh | 79 ++++------------------------------- t/t1502/.gitattributes | 1 + t/t1502/optionspec.help | 34 +++++++++++++++ 3 files changed, 42 insertions(+), 72 deletions(-) create mode 100644 t/t1502/.gitattributes create mode 100755 t/t1502/optionspec.help diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 0cdc6eb8b3..813ee5872f 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -58,44 +58,8 @@ EOF ' test_expect_success 'test --parseopt help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|cat <<\EOF -|usage: some-command [options] ... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B short option required argument -| --bar2 long option required argument -| -e, --fuz -| short and long option required argument -| -s[] short option optional argument -| --long[=] long option optional argument -| -g, --fluf[=] short and long option optional argument -| --longest -| a very long argument hint -| --pair with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -|EOF -END_EXPECT test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec && - test_cmp expect output + test_cmp "$TEST_DIRECTORY/t1502/optionspec.help" output ' test_expect_success 'test --parseopt help output no switches' ' @@ -140,41 +104,12 @@ END_EXPECT ' test_expect_success 'test --parseopt invalid switch help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|error: unknown option `does-not-exist'\'' -|usage: some-command [options] ... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B short option required argument -| --bar2 long option required argument -| -e, --fuz -| short and long option required argument -| -s[] short option optional argument -| --long[=] long option optional argument -| -g, --fluf[=] short and long option optional argument -| --longest -| a very long argument hint -| --pair with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -END_EXPECT + { + cat <<-\EOF && + error: unknown option `does-not-exist'\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/optionspec.help" + } >expect && test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec && test_cmp expect output ' diff --git a/t/t1502/.gitattributes b/t/t1502/.gitattributes new file mode 100644 index 0000000000..562b12e16e --- /dev/null +++ b/t/t1502/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t1502/optionspec.help b/t/t1502/optionspec.help new file mode 100755 index 0000000000..844eac6704 --- /dev/null +++ b/t/t1502/optionspec.help @@ -0,0 +1,34 @@ +cat <<\EOF +usage: some-command [options] ... + + some-command does foo and bar! + + -h, --help show the help + --foo some nifty option --foo + --bar ... some cool option --bar with an argument + -b, --baz a short and long option + +An option group Header + -C[...] option C with an optional argument + -d, --data[=...] short and long option with an optional argument + +Argument hints + -B short option required argument + --bar2 long option required argument + -e, --fuz + short and long option required argument + -s[] short option optional argument + --long[=] long option optional argument + -g, --fluf[=] short and long option optional argument + --longest + a very long argument hint + --pair with an equals sign in the hint + --aswitch help te=t contains? fl*g characters!` + --bswitch hint has trailing tab character + --cswitch switch has trailing tab character + --short-hint with a one symbol hint + +Extras + --extra1 line above used to cause a segfault but no longer does + +EOF From d5dc68f73041f95c1179fb092005e2326bdd8a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:39:52 +0200 Subject: [PATCH 4/8] t1502: test option negation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests for checking the "git rev-parse --parseopt" flag "!" and whether options can be negated with a "no-" prefix. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- t/t1502-rev-parse-parseopt.sh | 44 +++++++++++++++++++++++++++++++++++ t/t1502/optionspec-neg | 8 +++++++ t/t1502/optionspec-neg.help | 11 +++++++++ 3 files changed, 63 insertions(+) create mode 100644 t/t1502/optionspec-neg create mode 100644 t/t1502/optionspec-neg.help diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 813ee5872f..0f7c2db4c0 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -3,6 +3,22 @@ test_description='test git rev-parse --parseopt' . ./test-lib.sh +check_invalid_long_option () { + spec="$1" + opt="$2" + test_expect_success "test --parseopt invalid switch $opt help output for $spec" ' + { + cat <<-\EOF && + error: unknown option `'${opt#--}\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/$spec.help" + } >expect && + test_expect_code 129 git rev-parse --parseopt -- $opt \ + 2>output <"$TEST_DIRECTORY/t1502/$spec" && + test_cmp expect output + ' +} + test_expect_success 'setup optionspec' ' sed -e "s/^|//" >optionspec <<\EOF |some-command [options] ... @@ -278,4 +294,32 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l test_cmp expect actual ' +test_expect_success 'test --parseopt help output for optionspec-neg' ' + test_expect_code 129 git rev-parse --parseopt -- \ + -h >output <"$TEST_DIRECTORY/t1502/optionspec-neg" && + test_cmp "$TEST_DIRECTORY/t1502/optionspec-neg.help" output +' + +test_expect_success 'test --parseopt valid options for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --foo --no-foo --no-bar --positive-only --no-negative -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --foo --no-foo --no-bar --positive-only --no-negative && + test_cmp expect output +' + +test_expect_success 'test --parseopt positivated option for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --no-no-bar --no-no-bar -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --no-no-bar --bar && + test_cmp expect output +' + +check_invalid_long_option optionspec-neg --no-positive-only +check_invalid_long_option optionspec-neg --negative +check_invalid_long_option optionspec-neg --no-no-negative + test_done diff --git a/t/t1502/optionspec-neg b/t/t1502/optionspec-neg new file mode 100644 index 0000000000..392f43eb0b --- /dev/null +++ b/t/t1502/optionspec-neg @@ -0,0 +1,8 @@ +some-command [options] ... + +some-command does foo and bar! +-- +foo can be negated +no-bar can be positivated +positive-only! cannot be negated +no-negative! cannot be positivated diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help new file mode 100644 index 0000000000..54eba10afc --- /dev/null +++ b/t/t1502/optionspec-neg.help @@ -0,0 +1,11 @@ +cat <<\EOF +usage: some-command [options] ... + + some-command does foo and bar! + + --foo can be negated + --no-bar can be positivated + --positive-only cannot be negated + --no-negative cannot be positivated + +EOF From e8e5d294dc6e3b6b32132cc8018d01ce35ad0af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:40:59 +0200 Subject: [PATCH 5/8] parse-options: show negatability of options in short help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "[no-]" prefix to options without the flag PARSE_OPT_NONEG to document the fact that you can negate them. This looks a bit strange for options that already start with "no-", e.g. for the option --no-name of git show-branch: --[no-]no-name suppress naming strings You can actually use --no-no-name as an alias of --name, so the short help is not wrong. If we strip off any of the "no-"s, we lose either the ability to see if the remaining one belongs to the documented variant or to see if it can be negated. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 8 +++--- contrib/subtree/t/t7900-subtree.sh | 2 +- parse-options.c | 10 +++++-- t/t0040-parse-options.sh | 45 +++++++++++++++++------------- t/t1502-rev-parse-parseopt.sh | 2 +- t/t1502/optionspec-neg.help | 4 +-- t/t1502/optionspec.help | 32 +++++++++++---------- 7 files changed, 58 insertions(+), 45 deletions(-) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 6e8ff9ace1..6a4968f68a 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -424,10 +424,10 @@ usage: some-command [] ... some-command does foo and bar! -h, --help show the help - --foo some nifty option --foo - --bar ... some cool option --bar with an argument - --baz another cool option --baz with a named argument - --qux[=] qux may take a path argument but has meaning by itself + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + --[no-]baz another cool option --baz with a named argument + --[no-]qux[=] qux may take a path argument but has meaning by itself An option group Header -C[...] option C with an optional argument diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 341c169eca..49a21dd7c9 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -71,7 +71,7 @@ test_expect_success 'shows short help text for -h' ' test_expect_code 129 git subtree -h >out 2>err && test_must_be_empty err && grep -e "^ *or: git subtree pull" out && - grep -e --annotate out + grep -F -e "--[no-]annotate" out ' # diff --git a/parse-options.c b/parse-options.c index 87c9fae634..b750bf91cd 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1137,8 +1137,14 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t } if (opts->long_name && opts->short_name) pos += fprintf(outfile, ", "); - if (opts->long_name) - pos += fprintf(outfile, "--%s", opts->long_name); + if (opts->long_name) { + const char *long_name = opts->long_name; + if (opts->flags & PARSE_OPT_NONEG) + pos += fprintf(outfile, "--%s", long_name); + else + pos += fprintf(outfile, "--[no-]%s", long_name); + } + if (opts->type == OPTION_NUMBER) pos += utf8_fprintf(outfile, _("-NUM")); diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index e19a199636..1dfc431d52 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -13,30 +13,34 @@ usage: test-tool parse-options A helper function for the parse-options API. - --yes get a boolean - -D, --no-doubt begins with 'no-' + --[no-]yes get a boolean + -D, --[no-]no-doubt begins with 'no-' -B, --no-fear be brave - -b, --boolean increment by one - -4, --or4 bitwise-or boolean with ...0100 - --neg-or4 same as --no-or4 + -b, --[no-]boolean increment by one + -4, --[no-]or4 bitwise-or boolean with ...0100 + --[no-]neg-or4 same as --no-or4 - -i, --integer get a integer + -i, --[no-]integer + get a integer -j get a integer, too -m, --magnitude get a magnitude - --set23 set integer to 23 + --[no-]set23 set integer to 23 --mode1 set integer to 1 (cmdmode option) --mode2 set integer to 2 (cmdmode option) - -L, --length get length of - -F, --file set file to + -L, --[no-]length + get length of + -F, --[no-]file + set file to String options - -s, --string get a string - --string2 get another string - --st get another string (pervert ordering) + -s, --[no-]string + get a string + --[no-]string2 get another string + --[no-]st get another string (pervert ordering) -o get another string --longhelp help text of this entry spans multiple lines - --list add str to list + --[no-]list add str to list Magic arguments -NUM set integer to NUM @@ -45,16 +49,17 @@ Magic arguments --no-ambiguous negative ambiguity Standard options - --abbrev[=] use digits to display object names - -v, --verbose be verbose - -n, --dry-run dry run - -q, --quiet be quiet - --expect expected output in the variable dump + --[no-]abbrev[=] use digits to display object names + -v, --[no-]verbose be verbose + -n, --[no-]dry-run dry run + -q, --[no-]quiet be quiet + --[no-]expect + expected output in the variable dump Alias - -A, --alias-source + -A, --[no-]alias-source get a string - -Z, --alias-target + -Z, --[no-]alias-target alias of --alias-source EOF diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 0f7c2db4c0..f0737593c3 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -111,7 +111,7 @@ test_expect_success 'test --parseopt help-all output hidden switches' ' | | some-command does foo and bar! | -| --hidden1 A hidden switch +| --[no-]hidden1 A hidden switch | |EOF END_EXPECT diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help index 54eba10afc..60ff3cdd00 100644 --- a/t/t1502/optionspec-neg.help +++ b/t/t1502/optionspec-neg.help @@ -3,8 +3,8 @@ usage: some-command [options] ... some-command does foo and bar! - --foo can be negated - --no-bar can be positivated + --[no-]foo can be negated + --[no-]no-bar can be positivated --positive-only cannot be negated --no-negative cannot be positivated diff --git a/t/t1502/optionspec.help b/t/t1502/optionspec.help index 844eac6704..cbdd54d41b 100755 --- a/t/t1502/optionspec.help +++ b/t/t1502/optionspec.help @@ -4,31 +4,33 @@ usage: some-command [options] ... some-command does foo and bar! -h, --help show the help - --foo some nifty option --foo - --bar ... some cool option --bar with an argument - -b, --baz a short and long option + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + -b, --[no-]baz a short and long option An option group Header -C[...] option C with an optional argument - -d, --data[=...] short and long option with an optional argument + -d, --[no-]data[=...] short and long option with an optional argument Argument hints -B short option required argument - --bar2 long option required argument - -e, --fuz + --[no-]bar2 long option required argument + -e, --[no-]fuz short and long option required argument -s[] short option optional argument - --long[=] long option optional argument - -g, --fluf[=] short and long option optional argument - --longest + --[no-]long[=] long option optional argument + -g, --[no-]fluf[=] + short and long option optional argument + --[no-]longest a very long argument hint - --pair with an equals sign in the hint - --aswitch help te=t contains? fl*g characters!` - --bswitch hint has trailing tab character - --cswitch switch has trailing tab character - --short-hint with a one symbol hint + --[no-]pair + with an equals sign in the hint + --[no-]aswitch help te=t contains? fl*g characters!` + --[no-]bswitch hint has trailing tab character + --[no-]cswitch switch has trailing tab character + --[no-]short-hint with a one symbol hint Extras - --extra1 line above used to cause a segfault but no longer does + --[no-]extra1 line above used to cause a segfault but no longer does EOF From 652a6b15bc1cd0f837bc969e87fd31f3e88413f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:43:04 +0200 Subject: [PATCH 6/8] parse-options: factor out usage_indent() and usage_padding() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract functions for printing spaces before and after options. We'll need them in the next commit. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/parse-options.c b/parse-options.c index b750bf91cd..4b76fc81e9 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1020,9 +1020,28 @@ static int usage_argh(const struct option *opts, FILE *outfile) return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); } +static int usage_indent(FILE *outfile) +{ + return fprintf(outfile, " "); +} + #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 +static void usage_padding(FILE *outfile, size_t pos) +{ + int pad; + if (pos == USAGE_OPTS_WIDTH + 1) + pad = -1; + else if (pos <= USAGE_OPTS_WIDTH) + pad = USAGE_OPTS_WIDTH - pos; + else { + fputc('\n', outfile); + pad = USAGE_OPTS_WIDTH; + } + fprintf(outfile, "%*s", pad + USAGE_GAP, ""); +} + static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, const char * const *usagestr, const struct option *opts, @@ -1108,7 +1127,6 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (; opts->type != OPTION_END; opts++) { size_t pos; - int pad; const char *cp, *np; if (opts->type == OPTION_SUBCOMMAND) @@ -1128,7 +1146,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t need_newline = 0; } - pos = fprintf(outfile, " "); + pos = usage_indent(outfile); if (opts->short_name) { if (opts->flags & PARSE_OPT_NODASH) pos += fprintf(outfile, "%c", opts->short_name); @@ -1152,16 +1170,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t !(opts->flags & PARSE_OPT_NOARG)) pos += usage_argh(opts, outfile); - if (pos == USAGE_OPTS_WIDTH + 1) - pad = -1; - else if (pos <= USAGE_OPTS_WIDTH) - pad = USAGE_OPTS_WIDTH - pos; - else { - fputc('\n', outfile); - pad = USAGE_OPTS_WIDTH; - } if (opts->type == OPTION_ALIAS) { - fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + usage_padding(outfile, pos); fprintf_ln(outfile, _("alias of --%s"), (const char *)opts->value); continue; @@ -1169,12 +1179,11 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (cp = _(opts->help); *cp; cp = np) { np = strchrnul(cp, '\n'); - fprintf(outfile, - "%*s%.*s\n", pad + USAGE_GAP, "", - (int)(np - cp), cp); + usage_padding(outfile, pos); + fprintf(outfile, "%.*s\n", (int)(np - cp), cp); if (*np) np++; - pad = USAGE_OPTS_WIDTH; + pos = 0; } } fputc('\n', outfile); From 2a409a1d1250c8a9fcac7beaa58ae80881dda2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:44:45 +0200 Subject: [PATCH 7/8] parse-options: no --[no-]no-... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid showing an optional "no-" for options that already start with a "no-" in the short help, as that double negation is confusing. Document the opposite variant on its own line with a generated help text instead, unless it's defined and documented explicitly already. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 25 ++++++++++++++++++++++++- t/t0040-parse-options.sh | 3 ++- t/t1502/optionspec-neg.help | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/parse-options.c b/parse-options.c index 4b76fc81e9..4a8d380ceb 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1042,11 +1042,22 @@ static void usage_padding(FILE *outfile, size_t pos) fprintf(outfile, "%*s", pad + USAGE_GAP, ""); } +static const struct option *find_option_by_long_name(const struct option *opts, + const char *long_name) +{ + for (; opts->type != OPTION_END; opts++) { + if (opts->long_name && !strcmp(opts->long_name, long_name)) + return opts; + } + return NULL; +} + static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, const char * const *usagestr, const struct option *opts, int full, int err) { + const struct option *all_opts = opts; FILE *outfile = err ? stderr : stdout; int need_newline; @@ -1128,6 +1139,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (; opts->type != OPTION_END; opts++) { size_t pos; const char *cp, *np; + const char *positive_name = NULL; if (opts->type == OPTION_SUBCOMMAND) continue; @@ -1157,7 +1169,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t pos += fprintf(outfile, ", "); if (opts->long_name) { const char *long_name = opts->long_name; - if (opts->flags & PARSE_OPT_NONEG) + if ((opts->flags & PARSE_OPT_NONEG) || + skip_prefix(long_name, "no-", &positive_name)) pos += fprintf(outfile, "--%s", long_name); else pos += fprintf(outfile, "--[no-]%s", long_name); @@ -1185,6 +1198,16 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t np++; pos = 0; } + + if (positive_name) { + if (find_option_by_long_name(all_opts, positive_name)) + continue; + pos = usage_indent(outfile); + pos += fprintf(outfile, "--%s", positive_name); + usage_padding(outfile, pos); + fprintf_ln(outfile, _("opposite of --no-%s"), + positive_name); + } } fputc('\n', outfile); diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 1dfc431d52..a0ad6192d6 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -14,7 +14,8 @@ usage: test-tool parse-options A helper function for the parse-options API. --[no-]yes get a boolean - -D, --[no-]no-doubt begins with 'no-' + -D, --no-doubt begins with 'no-' + --doubt opposite of --no-doubt -B, --no-fear be brave -b, --[no-]boolean increment by one -4, --[no-]or4 bitwise-or boolean with ...0100 diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help index 60ff3cdd00..7a29f8cb03 100644 --- a/t/t1502/optionspec-neg.help +++ b/t/t1502/optionspec-neg.help @@ -4,7 +4,8 @@ usage: some-command [options] ... some-command does foo and bar! --[no-]foo can be negated - --[no-]no-bar can be positivated + --no-bar can be positivated + --bar opposite of --no-bar --positive-only cannot be negated --no-negative cannot be positivated From 311c8ff11cebef1219e110743d9a57cb9831ab06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 5 Aug 2023 16:52:42 +0200 Subject: [PATCH 8/8] parse-options: simplify usage_padding() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit c512643e67 (short help: allow a gap smaller than USAGE_GAP, 2023-07-18) effectively did away with the two-space gap between options and their description; one space is enough now. Incorporate USAGE_GAP into USAGE_OPTS_WIDTH, merge the two cases with enough space on the line and incorporate the newline into the format for the remaining case. The output remains the same. Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- parse-options.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/parse-options.c b/parse-options.c index 4a8d380ceb..b00d868816 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1025,21 +1025,14 @@ static int usage_indent(FILE *outfile) return fprintf(outfile, " "); } -#define USAGE_OPTS_WIDTH 24 -#define USAGE_GAP 2 +#define USAGE_OPTS_WIDTH 26 static void usage_padding(FILE *outfile, size_t pos) { - int pad; - if (pos == USAGE_OPTS_WIDTH + 1) - pad = -1; - else if (pos <= USAGE_OPTS_WIDTH) - pad = USAGE_OPTS_WIDTH - pos; - else { - fputc('\n', outfile); - pad = USAGE_OPTS_WIDTH; - } - fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + if (pos < USAGE_OPTS_WIDTH) + fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, ""); + else + fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, ""); } static const struct option *find_option_by_long_name(const struct option *opts,