diff --git a/repro.py b/repro.py new file mode 100644 index 0000000000..4d2726c5ac --- /dev/null +++ b/repro.py @@ -0,0 +1,17 @@ +import click + + +@click.group(invoke_without_command=True) +@click.pass_context +def main(ctx): + if ctx.invoked_subcommand is not None: + return + + +@main.command() +def sub(): + pass + + +if __name__ == "__main__": + main() diff --git a/src/click/core.py b/src/click/core.py index 6adc65ccd6..1e9173d5e9 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1585,6 +1585,8 @@ def __init__( if subcommand_metavar is None: if chain: subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + elif self.invoke_without_command: + subcommand_metavar = "[COMMAND] [ARGS]..." else: subcommand_metavar = "COMMAND [ARGS]..." diff --git a/tests/test_commands.py b/tests/test_commands.py index f26529a542..e367d38244 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -299,6 +299,24 @@ def sync(): assert result.output == "no subcommand, use default\nin subcommand\n" +def test_invoke_without_command_usage_shows_optional_subcommand(runner): + import click + + @click.group(invoke_without_command=True) + @click.pass_context + def cli(ctx): + if ctx.invoked_subcommand is not None: + return + + @cli.command() + def sub(): + pass + + result = runner.invoke(cli, ["--help"]) + assert result.exit_code == 0 + assert "Usage: cli [OPTIONS] [COMMAND] [ARGS]..." in result.output + + def test_aliased_command_canonical_name(runner): class AliasedGroup(click.Group): def get_command(self, ctx, cmd_name):