Skip to content

Commit 13f5085

Browse files
committed
Add @custom_version_option, freeze @version_option
Refs: #3527
1 parent 240603f commit 13f5085

5 files changed

Lines changed: 84 additions & 0 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Unreleased
66
Colorama is no longer a dependency and is not used. {issue}`2986` {pr}`3505`
77
- {class}`Argument` accepts a `help` parameter, and help output includes
88
a `Positional arguments` section when argument help is available. {issue}`2983` {pr}`3473`
9+
- Add {func}`custom_version_option`, a `--version` option whose output is
10+
produced by a callback, covering cases {func}`version_option` intentionally
11+
does not. The feature set of {func}`version_option` is now frozen; see
12+
[discussion #3527](https://github.com/pallets/click/discussions/3527). {pr}`3581`
913

1014
## Version 8.4.2
1115

docs/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ classes and functions.
4141
.. autofunction:: version_option
4242
```
4343

44+
```{eval-rst}
45+
.. autofunction:: custom_version_option
46+
```
47+
4448
```{eval-rst}
4549
.. autofunction:: help_option
4650
```

src/click/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .decorators import argument as argument
1919
from .decorators import command as command
2020
from .decorators import confirmation_option as confirmation_option
21+
from .decorators import custom_version_option as custom_version_option
2122
from .decorators import group as group
2223
from .decorators import help_option as help_option
2324
from .decorators import make_pass_decorator as make_pass_decorator

src/click/decorators.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,16 @@ def version_option(
440440
:func:`importlib.metadata.packages_distributions`, so e.g. ``PIL``
441441
resolves to the ``Pillow`` distribution.
442442
443+
.. note::
444+
The parameters and message variables accepted by this option are
445+
frozen: no new slots will be added, to keep the common case simple
446+
and predictable. If you need values it does not expose, such as a
447+
file path, the Python version, or git metadata, use
448+
:func:`custom_version_option` to render the output yourself.
449+
450+
Rationale: `discussion #3527
451+
<https://github.com/pallets/click/discussions/3527>`_.
452+
443453
:param version: The version number to show. If not provided, Click
444454
will try to detect it.
445455
:param param_decls: One or more option names. Defaults to the single
@@ -548,6 +558,48 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
548558
return option(*param_decls, **kwargs)
549559

550560

561+
def custom_version_option(
562+
callback: t.Callable[[Context], str],
563+
*param_decls: str,
564+
**kwargs: t.Any,
565+
) -> t.Callable[[FC], FC]:
566+
"""Add a ``--version`` option whose output is produced by ``callback``.
567+
568+
This is the customizable companion to :func:`version_option`. Where
569+
:func:`version_option` is intentionally limited to a fixed message and
570+
a small set of values, this option calls ``callback`` to build the
571+
whole string to print. Use it when you need values that
572+
:func:`version_option` does not expose, such as a file path, the
573+
Python version, or git metadata.
574+
575+
:param callback: Called with the current :class:`Context` when the
576+
option is invoked. Its return value is printed, then the program
577+
exits.
578+
:param param_decls: One or more option names. Defaults to the single
579+
value ``--version``.
580+
:param kwargs: Extra arguments are passed to :func:`option`.
581+
582+
.. versionadded:: 8.5.0
583+
"""
584+
585+
def show_version(ctx: Context, param: Parameter, value: bool) -> None:
586+
if not value or ctx.resilient_parsing:
587+
return
588+
589+
echo(callback(ctx), color=ctx.color)
590+
ctx.exit()
591+
592+
if not param_decls:
593+
param_decls = ("--version",)
594+
595+
kwargs.setdefault("is_flag", True)
596+
kwargs.setdefault("expose_value", False)
597+
kwargs.setdefault("is_eager", True)
598+
kwargs.setdefault("help", _("Show the version and exit."))
599+
kwargs["callback"] = show_version
600+
return option(*param_decls, **kwargs)
601+
602+
551603
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
552604
"""Pre-configured ``--help`` option which immediately prints the help page
553605
and exits the program.

tests/test_basic.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,3 +858,26 @@ def cli():
858858
result = runner.invoke(cli, ["--version"])
859859
assert result.exit_code != 0
860860
assert "not installed" in str(result.exception)
861+
862+
863+
@pytest.mark.parametrize("args", [["--version"], ["-V"]])
864+
def test_custom_version_option(runner, args):
865+
@click.command()
866+
@click.custom_version_option(lambda ctx: "custom 9.9.9", "-V", "--version")
867+
def cli():
868+
pass
869+
870+
result = runner.invoke(cli, args)
871+
assert result.exit_code == 0
872+
assert result.output == "custom 9.9.9\n"
873+
874+
875+
def test_custom_version_option_receives_context(runner):
876+
@click.command()
877+
@click.custom_version_option(lambda ctx: f"{ctx.info_name} 1.0")
878+
def cli():
879+
pass
880+
881+
result = runner.invoke(cli, ["--version"], prog_name="mytool")
882+
assert result.exit_code == 0
883+
assert result.output == "mytool 1.0\n"

0 commit comments

Comments
 (0)