Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Unreleased
Colorama is no longer a dependency and is not used. {issue}`2986` {pr}`3505`
- {class}`Argument` accepts a `help` parameter, and help output includes
a `Positional arguments` section when argument help is available. {issue}`2983` {pr}`3473`
- Add {func}`custom_version_option`, a `--version` option whose output is
produced by a callback, covering cases {func}`version_option` intentionally
does not. The feature set of {func}`version_option` is now frozen; see
[discussion #3527](https://github.com/pallets/click/discussions/3527). {pr}`3581`

## Version 8.4.2

Expand Down
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ classes and functions.
.. autofunction:: version_option
```

```{eval-rst}
.. autofunction:: custom_version_option
```

```{eval-rst}
.. autofunction:: help_option
```
Expand Down
1 change: 1 addition & 0 deletions src/click/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .decorators import argument as argument
from .decorators import command as command
from .decorators import confirmation_option as confirmation_option
from .decorators import custom_version_option as custom_version_option
from .decorators import group as group
from .decorators import help_option as help_option
from .decorators import make_pass_decorator as make_pass_decorator
Expand Down
52 changes: 52 additions & 0 deletions src/click/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,16 @@ def version_option(
:func:`importlib.metadata.packages_distributions`, so e.g. ``PIL``
resolves to the ``Pillow`` distribution.

.. note::
The parameters and message variables accepted by this option are
frozen: no new slots will be added, to keep the common case simple
and predictable. If you need values it does not expose, such as a
file path, the Python version, or git metadata, use
:func:`custom_version_option` to render the output yourself.

Rationale: `discussion #3527
<https://github.com/pallets/click/discussions/3527>`_.

:param version: The version number to show. If not provided, Click
will try to detect it.
:param param_decls: One or more option names. Defaults to the single
Expand Down Expand Up @@ -548,6 +558,48 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
return option(*param_decls, **kwargs)


def custom_version_option(
callback: t.Callable[[Context], str],
*param_decls: str,
**kwargs: t.Any,
) -> t.Callable[[FC], FC]:
"""Add a ``--version`` option whose output is produced by ``callback``.

This is the customizable companion to :func:`version_option`. Where
:func:`version_option` is intentionally limited to a fixed message and
a small set of values, this option calls ``callback`` to build the
whole string to print. Use it when you need values that
:func:`version_option` does not expose, such as a file path, the
Python version, or git metadata.

:param callback: Called with the current :class:`Context` when the
option is invoked. Its return value is printed, then the program
exits.
:param param_decls: One or more option names. Defaults to the single
value ``--version``.
:param kwargs: Extra arguments are passed to :func:`option`.

.. versionadded:: 8.5.0
"""

def show_version(ctx: Context, param: Parameter, value: bool) -> None:
if not value or ctx.resilient_parsing:
return

echo(callback(ctx), color=ctx.color)
ctx.exit()

if not param_decls:
param_decls = ("--version",)

kwargs.setdefault("is_flag", True)
kwargs.setdefault("expose_value", False)
kwargs.setdefault("is_eager", True)
kwargs.setdefault("help", _("Show the version and exit."))
kwargs["callback"] = show_version
return option(*param_decls, **kwargs)


def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
"""Pre-configured ``--help`` option which immediately prints the help page
and exits the program.
Expand Down
23 changes: 23 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,3 +858,26 @@ def cli():
result = runner.invoke(cli, ["--version"])
assert result.exit_code != 0
assert "not installed" in str(result.exception)


@pytest.mark.parametrize("args", [["--version"], ["-V"]])
def test_custom_version_option(runner, args):
@click.command()
@click.custom_version_option(lambda ctx: "custom 9.9.9", "-V", "--version")
def cli():
pass

result = runner.invoke(cli, args)
assert result.exit_code == 0
assert result.output == "custom 9.9.9\n"


def test_custom_version_option_receives_context(runner):
@click.command()
@click.custom_version_option(lambda ctx: f"{ctx.info_name} 1.0")
def cli():
pass

result = runner.invoke(cli, ["--version"], prog_name="mytool")
assert result.exit_code == 0
assert result.output == "mytool 1.0\n"
Loading