From e08af25643828d7da286e0252583600ddd058fb3 Mon Sep 17 00:00:00 2001 From: Fletcher Davis Date: Fri, 6 Mar 2020 14:30:59 -0800 Subject: [PATCH 1/3] return exit 1 if group gets no command --- src/click/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click/core.py b/src/click/core.py index 0b924ca4ab..6afdde8ee7 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1748,7 +1748,7 @@ def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: def parse_args(self, ctx: Context, args: list[str]) -> list[str]: if not args and self.no_args_is_help and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) - ctx.exit() + ctx.exit(code=1) rest = super().parse_args(ctx, args) From f25a673e8c40be8504aa333fb88d2254ef7b26df Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Aug 2020 13:20:00 -0700 Subject: [PATCH 2/3] new NoArgsIsHelpError for displaying default help --- CHANGES.rst | 3 +++ src/click/core.py | 7 +++---- src/click/exceptions.py | 8 ++++++++ tests/test_commands.py | 28 ++++++++++++++++++---------- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 387f84fe36..555caea477 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -56,6 +56,9 @@ Unreleased :issue:`2746` :pr:`2788` - Add ``Choice.get_invalid_choice_message`` method for customizing the invalid choice message. :issue:`2621` :pr:`2622` +- If help is shown because ``no_args_is_help`` is enabled (default on + for groups, off for commands), the exit code is 2 instead of 0. + :issue:`1489` Version 8.1.8 diff --git a/src/click/core.py b/src/click/core.py index 6afdde8ee7..67c31f806e 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -24,6 +24,7 @@ from .exceptions import ClickException from .exceptions import Exit from .exceptions import MissingParameter +from .exceptions import NoArgsIsHelpError from .exceptions import UsageError from .formatting import HelpFormatter from .formatting import join_options @@ -1156,8 +1157,7 @@ def make_context( def parse_args(self, ctx: Context, args: list[str]) -> list[str]: if not args and self.no_args_is_help and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit() + raise NoArgsIsHelpError(ctx) parser = self.make_parser(ctx) opts, args, param_order = parser.parse_args(args=args) @@ -1747,8 +1747,7 @@ def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: def parse_args(self, ctx: Context, args: list[str]) -> list[str]: if not args and self.no_args_is_help and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit(code=1) + raise NoArgsIsHelpError(ctx) rest = super().parse_args(ctx, args) diff --git a/src/click/exceptions.py b/src/click/exceptions.py index c7ebe81875..3352081e9b 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -255,6 +255,14 @@ class BadArgumentUsage(UsageError): """ +class NoArgsIsHelpError(UsageError): + def __init__(self, ctx): + super().__init__(ctx.get_help(), ctx=ctx) + + def show(self, file=None): + echo(self.format_message(), file=file, err=True, color=self.ctx.color) + + class FileError(ClickException): """Raised if a file cannot be opened.""" diff --git a/tests/test_commands.py b/tests/test_commands.py index 5a56799adf..295a8d804a 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -95,13 +95,9 @@ def long(): ) -def test_no_args_is_help(runner): - @click.command(no_args_is_help=True) - def cli(): - pass - - result = runner.invoke(cli, []) - assert result.exit_code == 0 +def test_command_no_args_is_help(runner): + result = runner.invoke(click.Command("test", no_args_is_help=True)) + assert result.exit_code == 2 assert "Show this message and exit." in result.output @@ -140,9 +136,21 @@ def cli(obj): def move(): click.echo("move") - result = runner.invoke(cli, args) - assert result.exit_code == exit_code - assert expect in result.output + result = runner.invoke(cli, []) + assert result.exit_code == 2 + assert "Show this message and exit." in result.output + + result = runner.invoke(cli, ["obj1"]) + assert result.exit_code == 2 + assert "Error: Missing command." in result.output + + result = runner.invoke(cli, ["obj1", "--help"]) + assert result.exit_code == 0 + assert "Show this message and exit." in result.output + + result = runner.invoke(cli, ["obj1", "move"]) + assert result.exit_code == 0 + assert result.output == "obj=obj1\nmove\n" def test_custom_parser(runner): From 27ed7e7e5f917e97af9ed0660b2ac7efc0e7a588 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sun, 3 Nov 2024 19:52:00 +0000 Subject: [PATCH 3/3] Fixed args not passing from Group.resolve_command to Command.parse_args which lead to args being empty -> --help leading to exit code 2 instead of 0. * Added PR to CHANGES. * Fixed typing. * Fixed test to be up to date with rebased changes. --- CHANGES.rst | 6 +++--- src/click/core.py | 2 +- src/click/exceptions.py | 5 +++-- tests/test_commands.py | 20 ++++---------------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 555caea477..540bbe2de9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -56,9 +56,9 @@ Unreleased :issue:`2746` :pr:`2788` - Add ``Choice.get_invalid_choice_message`` method for customizing the invalid choice message. :issue:`2621` :pr:`2622` -- If help is shown because ``no_args_is_help`` is enabled (default on - for groups, off for commands), the exit code is 2 instead of 0. - :issue:`1489` +- If help is shown because ``no_args_is_help`` is enabled (defaults to ``True`` + for groups, ``False`` for commands), the exit code is 2 instead of 0. + :issue:`1489` :pr:`1489` Version 8.1.8 diff --git a/src/click/core.py b/src/click/core.py index 67c31f806e..666ad68137 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1850,7 +1850,7 @@ def resolve_command( # place. if cmd is None and not ctx.resilient_parsing: if _split_opt(cmd_name)[0]: - self.parse_args(ctx, ctx.args) + self.parse_args(ctx, args) ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) return cmd_name if cmd else None, cmd, args[1:] diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 3352081e9b..27dd5e0102 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -256,10 +256,11 @@ class BadArgumentUsage(UsageError): class NoArgsIsHelpError(UsageError): - def __init__(self, ctx): + def __init__(self, ctx: Context) -> None: + self.ctx: Context super().__init__(ctx.get_help(), ctx=ctx) - def show(self, file=None): + def show(self, file: t.IO[t.Any] | None = None) -> None: echo(self.format_message(), file=file, err=True, color=self.ctx.color) diff --git a/tests/test_commands.py b/tests/test_commands.py index 295a8d804a..02c4a30438 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -123,7 +123,7 @@ def foo(name): (["obj1"], 2, "Error: Missing command."), (["obj1", "--help"], 0, "Show this message and exit."), (["obj1", "move"], 0, "obj=obj1\nmove\n"), - ([], 0, "Show this message and exit."), + ([], 2, "Show this message and exit."), ], ) def test_group_with_args(runner, args, exit_code, expect): @@ -136,21 +136,9 @@ def cli(obj): def move(): click.echo("move") - result = runner.invoke(cli, []) - assert result.exit_code == 2 - assert "Show this message and exit." in result.output - - result = runner.invoke(cli, ["obj1"]) - assert result.exit_code == 2 - assert "Error: Missing command." in result.output - - result = runner.invoke(cli, ["obj1", "--help"]) - assert result.exit_code == 0 - assert "Show this message and exit." in result.output - - result = runner.invoke(cli, ["obj1", "move"]) - assert result.exit_code == 0 - assert result.output == "obj=obj1\nmove\n" + result = runner.invoke(cli, args) + assert result.exit_code == exit_code + assert expect in result.output def test_custom_parser(runner):