Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split generation of help extra items and rendering #2517

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -31,6 +31,9 @@ Unreleased
- When generating a command's name from a decorated function's name, the
suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed.
:issue:`2322`
- Add ``get_help_extra`` method on ``Option`` to fetch the generated extra
items used in ``get_help_record`` to render help text. :issue:`2516`
:pr:`2517`


Version 8.1.7
Expand Down
45 changes: 30 additions & 15 deletions src/click/core.py
Expand Up @@ -2668,7 +2668,28 @@ def _write_opts(opts: cabc.Sequence[str]) -> str:
rv.append(_write_opts(self.secondary_opts))

help = self.help or ""
extra = []

extra = self.get_help_extra(ctx)
extra_items = []
if "envvars" in extra:
extra_items.append(
_("env var: {var}").format(var=", ".join(extra["envvars"]))
)
if "default" in extra:
extra_items.append(_("default: {default}").format(default=extra["default"]))
if "range" in extra:
extra_items.append(extra["range"])
if "required" in extra:
extra_items.append(_(extra["required"]))

if extra_items:
extra_str = "; ".join(extra_items)
help = f"{help} [{extra_str}]" if help else f"[{extra_str}]"

return ("; " if any_prefix_is_slash else " / ").join(rv), help

def get_help_extra(self, ctx: Context) -> dict[str, t.Any]:
extra: dict[str, t.Any] = {}

if self.show_envvar:
envvar = self.envvar
Expand All @@ -2682,12 +2703,10 @@ def _write_opts(opts: cabc.Sequence[str]) -> str:
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"

if envvar is not None:
var_str = (
envvar
if isinstance(envvar, str)
else ", ".join(str(d) for d in envvar)
)
extra.append(_("env var: {var}").format(var=var_str))
if isinstance(envvar, str):
extra["envvars"] = (envvar,)
else:
extra["envvars"] = tuple(str(d) for d in envvar)

# Temporarily enable resilient parsing to avoid type casting
# failing for the default. Might be possible to extend this to
Expand Down Expand Up @@ -2730,7 +2749,7 @@ def _write_opts(opts: cabc.Sequence[str]) -> str:
default_string = str(default_value)

if default_string:
extra.append(_("default: {default}").format(default=default_string))
extra["default"] = default_string

if (
isinstance(self.type, types._NumberRangeBase)
Expand All @@ -2740,16 +2759,12 @@ def _write_opts(opts: cabc.Sequence[str]) -> str:
range_str = self.type._describe_range()

if range_str:
extra.append(range_str)
extra["range"] = range_str

if self.required:
extra.append(_("required"))
extra["required"] = "required"

if extra:
extra_str = "; ".join(extra)
help = f"{help} [{extra_str}]" if help else f"[{extra_str}]"

return ("; " if any_prefix_is_slash else " / ").join(rv), help
return extra

@t.overload
def get_default(
Expand Down
35 changes: 23 additions & 12 deletions tests/test_options.py
Expand Up @@ -316,6 +316,7 @@ def __str__(self):

opt = click.Option(["-a"], default=Value(), show_default=True)
ctx = click.Context(click.Command("cli"))
assert opt.get_help_extra(ctx) == {"default": "special value"}
assert "special value" in opt.get_help_record(ctx)[1]


Expand All @@ -331,6 +332,7 @@ def __str__(self):
def test_intrange_default_help_text(type, expect):
option = click.Option(["--num"], type=type, show_default=True, default=2)
context = click.Context(click.Command("test"))
assert option.get_help_extra(context) == {"default": "2", "range": expect}
result = option.get_help_record(context)[1]
assert expect in result

Expand All @@ -339,6 +341,7 @@ def test_count_default_type_help():
"""A count option with the default type should not show >=0 in help."""
option = click.Option(["--count"], count=True, help="some words")
context = click.Context(click.Command("test"))
assert option.get_help_extra(context) == {}
result = option.get_help_record(context)[1]
assert result == "some words"

Expand All @@ -354,6 +357,7 @@ def test_file_type_help_default():
["--in"], type=click.File(), default=__file__, show_default=True
)
context = click.Context(click.Command("test"))
assert option.get_help_extra(context) == {"default": __file__}
result = option.get_help_record(context)[1]
assert __file__ in result

Expand Down Expand Up @@ -741,6 +745,7 @@ def test_show_default_boolean_flag_name(runner, default, expect):
help="Enable/Disable the cache.",
)
ctx = click.Context(click.Command("test"))
assert opt.get_help_extra(ctx) == {"default": expect}
message = opt.get_help_record(ctx)[1]
assert f"[default: {expect}]" in message

Expand All @@ -757,6 +762,7 @@ def test_show_true_default_boolean_flag_value(runner):
help="Enable the cache.",
)
ctx = click.Context(click.Command("test"))
assert opt.get_help_extra(ctx) == {"default": "True"}
message = opt.get_help_record(ctx)[1]
assert "[default: True]" in message

Expand All @@ -774,6 +780,7 @@ def test_hide_false_default_boolean_flag_value(runner, default):
help="Enable the cache.",
)
ctx = click.Context(click.Command("test"))
assert opt.get_help_extra(ctx) == {}
message = opt.get_help_record(ctx)[1]
assert "[default: " not in message

Expand All @@ -782,6 +789,7 @@ def test_show_default_string(runner):
"""When show_default is a string show that value as default."""
opt = click.Option(["--limit"], show_default="unlimited")
ctx = click.Context(click.Command("cli"))
assert opt.get_help_extra(ctx) == {"default": "(unlimited)"}
message = opt.get_help_record(ctx)[1]
assert "[default: (unlimited)]" in message

Expand All @@ -790,6 +798,7 @@ def test_do_not_show_no_default(runner):
"""When show_default is True and no default is set do not show None."""
opt = click.Option(["--limit"], show_default=True)
ctx = click.Context(click.Command("cli"))
assert opt.get_help_extra(ctx) == {}
message = opt.get_help_record(ctx)[1]
assert "[default: None]" not in message

Expand All @@ -800,28 +809,30 @@ def test_do_not_show_default_empty_multiple():
"""
opt = click.Option(["-a"], multiple=True, help="values", show_default=True)
ctx = click.Context(click.Command("cli"))
assert opt.get_help_extra(ctx) == {}
message = opt.get_help_record(ctx)[1]
assert message == "values"


@pytest.mark.parametrize(
("ctx_value", "opt_value", "expect"),
("ctx_value", "opt_value", "extra_value", "expect"),
[
(None, None, False),
(None, False, False),
(None, True, True),
(False, None, False),
(False, False, False),
(False, True, True),
(True, None, True),
(True, False, False),
(True, True, True),
(False, "one", True),
(None, None, {}, False),
(None, False, {}, False),
(None, True, {"default": "1"}, True),
(False, None, {}, False),
(False, False, {}, False),
(False, True, {"default": "1"}, True),
(True, None, {"default": "1"}, True),
(True, False, {}, False),
(True, True, {"default": "1"}, True),
(False, "one", {"default": "(one)"}, True),
],
)
def test_show_default_precedence(ctx_value, opt_value, expect):
def test_show_default_precedence(ctx_value, opt_value, extra_value, expect):
ctx = click.Context(click.Command("test"), show_default=ctx_value)
opt = click.Option("-a", default=1, help="value", show_default=opt_value)
assert opt.get_help_extra(ctx) == extra_value
help = opt.get_help_record(ctx)[1]
assert ("default:" in help) is expect

Expand Down