Skip to content

Commit 1241aba

Browse files
kdeldyckeMr-Neutr0n
andcommitted
Use non-shadowed help option name in error hint
Fix #2790. Co-authored-by: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com>
1 parent f1f191e commit 1241aba

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Unreleased
4040
- Change :class:`ParameterSource` to an :class:`~enum.IntEnum` and reorder
4141
its members from most to least explicit, so values can be compared to
4242
check whether a parameter was explicitly provided. :issue:`2879` :pr:`3248`
43+
- The error hint now uses :meth:`Command.get_help_option_names` to pick
44+
non-shadowed help option names, so ``Try '... -h'`` no longer points to a
45+
subcommand option that shadows ``-h``. All surviving names are shown
46+
(``-h/--help``). :issue:`2790` :pr:`3208`
4347

4448
Version 8.3.2
4549
-------------

src/click/exceptions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ def show(self, file: t.IO[t.Any] | None = None) -> None:
7878
self.ctx is not None
7979
and self.ctx.command.get_help_option(self.ctx) is not None
8080
):
81+
help_names = self.ctx.command.get_help_option_names(self.ctx)
82+
# Pick the longest name (like ``--help`` over ``-h``) for
83+
# readability in error messages.
8184
hint = _("Try '{command} {option}' for help.").format(
82-
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
85+
command=self.ctx.command_path,
86+
option=max(help_names, key=len),
8387
)
8488
hint = f"{hint}\n"
8589
if self.ctx is not None:

tests/test_formatting.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
import click
24

35

@@ -246,6 +248,71 @@ def cmd(arg):
246248
]
247249

248250

251+
@pytest.mark.parametrize(
252+
("help_names", "extra_options", "expected_hint"),
253+
[
254+
# No shadowing, longest name is picked.
255+
(["-h", "--help"], [], "Try 'cli foo --help' for help."),
256+
# -h shadowed by a subcommand option, --help still available.
257+
(
258+
["-h", "--help"],
259+
[click.option("--host", "-h")],
260+
"Try 'cli foo --help' for help.",
261+
),
262+
# --help shadowed, -h still available.
263+
(
264+
["-h", "--help"],
265+
[click.option("--help-file", "--help")],
266+
"Try 'cli foo -h' for help.",
267+
),
268+
# Both names shadowed: no hint line at all.
269+
(
270+
["-h", "--help"],
271+
[click.option("--host", "-h"), click.option("--help-file", "--help")],
272+
None,
273+
),
274+
# Single custom help name, not shadowed.
275+
(["--man"], [], "Try 'cli foo --man' for help."),
276+
# Three help names, one shadowed, longest survivor picked.
277+
(
278+
["-h", "--help", "--info"],
279+
[click.option("--info-file", "--info")],
280+
"Try 'cli foo --help' for help.",
281+
),
282+
],
283+
)
284+
def test_formatting_usage_error_help_hint(
285+
runner, help_names, extra_options, expected_hint
286+
):
287+
"""The error hint should only show non-shadowed help option names,
288+
picking the longest for readability.
289+
290+
https://github.com/pallets/click/issues/2790
291+
"""
292+
293+
@click.group(context_settings={"help_option_names": help_names})
294+
def cli():
295+
pass
296+
297+
@cli.command()
298+
@click.argument("required_arg")
299+
def foo(required_arg, **kwargs):
300+
pass
301+
302+
for option in extra_options:
303+
option(foo)
304+
305+
result = runner.invoke(cli, ["foo"])
306+
assert result.exit_code == 2
307+
lines = result.output.splitlines()
308+
assert lines[0] == "Usage: cli foo [OPTIONS] REQUIRED_ARG"
309+
assert lines[-1] == "Error: Missing argument 'REQUIRED_ARG'."
310+
if expected_hint is not None:
311+
assert expected_hint in lines
312+
else:
313+
assert not any(line.startswith("Try ") for line in lines)
314+
315+
249316
def test_formatting_custom_type_metavar(runner):
250317
class MyType(click.ParamType):
251318
def get_metavar(self, param: click.Parameter, ctx: click.Context):

0 commit comments

Comments
 (0)