From 9773a4d56ef518a64981facb1bc15658438a7260 Mon Sep 17 00:00:00 2001 From: Kevin Deldycke Date: Tue, 11 Jul 2023 19:57:32 +0400 Subject: [PATCH 1/3] Deduplicate help option definition --- src/click/__init__.py | 1 + src/click/core.py | 22 +++++++---------- src/click/decorators.py | 52 ++++++++++++++++++++++++----------------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/click/__init__.py b/src/click/__init__.py index cfef88ea84..46f99e951a 100644 --- a/src/click/__init__.py +++ b/src/click/__init__.py @@ -19,6 +19,7 @@ from .decorators import confirmation_option as confirmation_option from .decorators import group as group from .decorators import help_option as help_option +from .decorators import HelpOption as HelpOption from .decorators import make_pass_decorator as make_pass_decorator from .decorators import option as option from .decorators import pass_context as pass_context diff --git a/src/click/core.py b/src/click/core.py index 29e46bd707..c623764eed 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1285,25 +1285,19 @@ def get_help_option_names(self, ctx: Context) -> t.List[str]: return list(all_names) def get_help_option(self, ctx: Context) -> t.Optional["Option"]: - """Returns the help option object.""" + """Returns the help option object. + + Unless ``add_help_option`` is ``False``. + """ help_options = self.get_help_option_names(ctx) if not help_options or not self.add_help_option: return None - def show_help(ctx: Context, param: "Parameter", value: str) -> None: - if value and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit() - - return Option( - help_options, - is_flag=True, - is_eager=True, - expose_value=False, - callback=show_help, - help=_("Show this message and exit."), - ) + # Avoid circular import. + from .decorators import HelpOption + + return HelpOption(help_options) def make_parser(self, ctx: Context) -> OptionParser: """Creates the underlying option parser for this command.""" diff --git a/src/click/decorators.py b/src/click/decorators.py index c346c702ae..ea1ae1a127 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -1,3 +1,4 @@ +from collections import abc import inspect import types import typing as t @@ -522,32 +523,41 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None: return option(*param_decls, **kwargs) -def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Add a ``--help`` option which immediately prints the help page +class HelpOption(Option): + """Pre-configured ``--help`` option which immediately prints the help page and exits the program. + """ - This is usually unnecessary, as the ``--help`` option is added to - each command automatically unless ``add_help_option=False`` is - passed. + def __init__( + self, + param_decls: abc.Sequence[str] | None = None, + **kwargs: t.Any, + ) -> None: + if not param_decls: + param_decls = ("--help",) - :param param_decls: One or more option names. Defaults to the single - value ``"--help"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", self.show_help) - def callback(ctx: Context, param: Parameter, value: bool) -> None: - if not value or ctx.resilient_parsing: - return + super().__init__(param_decls, **kwargs) - echo(ctx.get_help(), color=ctx.color) - ctx.exit() + @staticmethod + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() - if not param_decls: - param_decls = ("--help",) - kwargs.setdefault("is_flag", True) - kwargs.setdefault("expose_value", False) - kwargs.setdefault("is_eager", True) - kwargs.setdefault("help", _("Show this message and exit.")) - kwargs["callback"] = callback +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Decorator for the pre-configured ``--help`` option defined above. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + kwargs.setdefault("cls", HelpOption) return option(*param_decls, **kwargs) From b762e9c8508156c30c67b4a1ed80bba7e3967af5 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 2 Nov 2024 14:39:29 +0000 Subject: [PATCH 2/3] Replace | with Union for Python 3.8. --- src/click/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/click/decorators.py b/src/click/decorators.py index ea1ae1a127..822e34de55 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -1,7 +1,7 @@ -from collections import abc import inspect import types import typing as t +from collections import abc from functools import update_wrapper from gettext import gettext as _ @@ -530,7 +530,7 @@ class HelpOption(Option): def __init__( self, - param_decls: abc.Sequence[str] | None = None, + param_decls: t.Optional[abc.Sequence[str]] = None, **kwargs: t.Any, ) -> None: if not param_decls: From 15644915e482b7c7bd7ee1aa77c9c2dbcd934330 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 2 Nov 2024 14:42:40 +0000 Subject: [PATCH 3/3] replace abc with typing for 3.7-8. --- src/click/decorators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/click/decorators.py b/src/click/decorators.py index 822e34de55..bcf8906e70 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -1,7 +1,6 @@ import inspect import types import typing as t -from collections import abc from functools import update_wrapper from gettext import gettext as _ @@ -530,7 +529,7 @@ class HelpOption(Option): def __init__( self, - param_decls: t.Optional[abc.Sequence[str]] = None, + param_decls: t.Optional[t.Sequence[str]] = None, **kwargs: t.Any, ) -> None: if not param_decls: