Skip to content

Commit 569350f

Browse files
committed
Allow customization of help option right from its decorator.
Removes `click.decorators.HelpOption` class. Closes #2832.
1 parent 8a47580 commit 569350f

File tree

4 files changed

+68
-43
lines changed

4 files changed

+68
-43
lines changed

src/click/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from .decorators import confirmation_option as confirmation_option
2020
from .decorators import group as group
2121
from .decorators import help_option as help_option
22-
from .decorators import HelpOption as HelpOption
2322
from .decorators import make_pass_decorator as make_pass_decorator
2423
from .decorators import option as option
2524
from .decorators import pass_context as pass_context

src/click/core.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,25 +1014,26 @@ def get_help_option_names(self, ctx: Context) -> list[str]:
10141014
return list(all_names)
10151015

10161016
def get_help_option(self, ctx: Context) -> Option | None:
1017-
"""Returns the help option object."""
1018-
help_options = self.get_help_option_names(ctx)
1017+
"""Returns the help option object.
10191018
1020-
if not help_options or not self.add_help_option:
1019+
Unless ``add_help_option`` is ``False``.
1020+
"""
1021+
help_option_names = self.get_help_option_names(ctx)
1022+
1023+
if not help_option_names or not self.add_help_option:
10211024
return None
10221025

1023-
def show_help(ctx: Context, param: Parameter, value: str) -> None:
1024-
if value and not ctx.resilient_parsing:
1025-
echo(ctx.get_help(), color=ctx.color)
1026-
ctx.exit()
1027-
1028-
return Option(
1029-
help_options,
1030-
is_flag=True,
1031-
is_eager=True,
1032-
expose_value=False,
1033-
callback=show_help,
1034-
help=_("Show this message and exit."),
1035-
)
1026+
# Avoid circular import.
1027+
from .decorators import help_option
1028+
1029+
def dummy_func():
1030+
pass
1031+
1032+
# Call @help_option decorator to produce an help option with proper
1033+
# defaults and attach it to the dummy function defined above.
1034+
help_option(*help_option_names)(dummy_func)
1035+
1036+
return dummy_func.__click_params__.pop()
10361037

10371038
def make_parser(self, ctx: Context) -> _OptionParser:
10381039
"""Creates the underlying option parser for this command."""

src/click/decorators.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import inspect
44
import typing as t
5-
from collections import abc
65
from functools import update_wrapper
76
from gettext import gettext as _
87

@@ -525,41 +524,28 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
525524
return option(*param_decls, **kwargs)
526525

527526

528-
class HelpOption(Option):
527+
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
529528
"""Pre-configured ``--help`` option which immediately prints the help page
530529
and exits the program.
531-
"""
532-
533-
def __init__(
534-
self,
535-
param_decls: abc.Sequence[str] | None = None,
536-
**kwargs: t.Any,
537-
) -> None:
538-
if not param_decls:
539-
param_decls = ("--help",)
540530
541-
kwargs.setdefault("is_flag", True)
542-
kwargs.setdefault("expose_value", False)
543-
kwargs.setdefault("is_eager", True)
544-
kwargs.setdefault("help", _("Show this message and exit."))
545-
kwargs.setdefault("callback", self.show_help)
546-
547-
super().__init__(param_decls, **kwargs)
531+
:param param_decls: One or more option names. Defaults to the single
532+
value ``"--help"``.
533+
:param kwargs: Extra arguments are passed to :func:`option`.
534+
"""
548535

549-
@staticmethod
550536
def show_help(ctx: Context, param: Parameter, value: bool) -> None:
551537
"""Callback that print the help page on ``<stdout>`` and exits."""
552538
if value and not ctx.resilient_parsing:
553539
echo(ctx.get_help(), color=ctx.color)
554540
ctx.exit()
555541

542+
if not param_decls:
543+
param_decls = ("--help",)
556544

557-
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
558-
"""Decorator for the pre-configured ``--help`` option defined above.
545+
kwargs.setdefault("is_flag", True)
546+
kwargs.setdefault("expose_value", False)
547+
kwargs.setdefault("is_eager", True)
548+
kwargs.setdefault("help", _("Show this message and exit."))
549+
kwargs.setdefault("callback", show_help)
559550

560-
:param param_decls: One or more option names. Defaults to the single
561-
value ``"--help"``.
562-
:param kwargs: Extra arguments are passed to :func:`option`.
563-
"""
564-
kwargs.setdefault("cls", HelpOption)
565551
return option(*param_decls, **kwargs)

tests/test_options.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,45 @@ def cmd2(testoption):
724724
assert "you wont see me" not in result.output
725725

726726

727+
@pytest.mark.parametrize("custom_class", (True, False))
728+
@pytest.mark.parametrize(
729+
("name_specs", "expected"),
730+
(
731+
(
732+
("-h", "--help"),
733+
" -h, --help Show this message and exit.\n",
734+
),
735+
(
736+
("-h",),
737+
" -h Show this message and exit.\n"
738+
" --help Show this message and exit.\n",
739+
),
740+
(
741+
("--help",),
742+
" --help Show this message and exit.\n",
743+
),
744+
),
745+
)
746+
def test_help_option_custom_names_and_class(runner, custom_class, name_specs, expected):
747+
class CustomHelpOption(click.Option):
748+
pass
749+
750+
option_attrs = {}
751+
if custom_class:
752+
option_attrs["cls"] = CustomHelpOption
753+
754+
@click.command()
755+
@click.help_option(*name_specs, **option_attrs)
756+
def cmd():
757+
pass
758+
759+
for arg in name_specs:
760+
result = runner.invoke(cmd, [arg])
761+
assert not result.exception
762+
assert result.exit_code == 0
763+
assert expected in result.output
764+
765+
727766
def test_bool_flag_with_type(runner):
728767
@click.command()
729768
@click.option("--shout/--no-shout", default=False, type=bool)

0 commit comments

Comments
 (0)