diff --git a/CHANGES.rst b/CHANGES.rst index 39ad6ee8d..7f506d05a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,9 @@ Unreleased - Improve type hinting for decorators and give all generic types parameters. :issue:`2398` +- 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.3 diff --git a/src/click/core.py b/src/click/core.py index bdb4b9618..72a33028f 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -2730,7 +2730,28 @@ def _write_opts(opts: t.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) -> t.Dict[str, t.Any]: + extra: t.Dict[str, t.Any] = {} if self.show_envvar: envvar = self.envvar @@ -2744,12 +2765,10 @@ def _write_opts(opts: t.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 @@ -2792,7 +2811,7 @@ def _write_opts(opts: t.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) @@ -2802,16 +2821,12 @@ def _write_opts(opts: t.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( diff --git a/tests/test_options.py b/tests/test_options.py index d4090c725..8ea32d301 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -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] @@ -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 @@ -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" @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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