Repository: pallets/click (python)

Issue:
Consider not showing the default value of boolean flags without a secondary option (False)
Unless I'm missing something, the default value of such options should be obvious: `False`. As a consequence, `[default: False]` feels like superfluous noise. This is certainly true for the `--help` option, which should never show a default.


Pull request:
Hide single option boolean flags in "help" when the default is False
Hides the default value in help texts for single option boolean flags when the default is False

- fixes #1971

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 3efa72a..b10fc80 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,9 @@ Version 8.1.0
 
 Unreleased
 
+-   Single options boolean flags with ``show_default=True`` only show
+    the default if it is ``True``. :issue:`1971`:
+
 
 Version 8.0.2
 -------------
diff --git a/docs/options.rst b/docs/options.rst
index 277a98b..b54fbba 100644
--- a/docs/options.rst
+++ b/docs/options.rst
@@ -109,6 +109,27 @@ To show the default values when showing command help, use ``show_default=True``
 
    invoke(dots, args=['--help'])
 
+For single option boolean flags, the default remains hidden if the default
+value is False.
+
+.. click:example::
+
+    @click.command()
+    @click.option('--n', default=1, show_default=True)
+    @click.option("--gr", is_flag=True, show_default=True, default=False, help="Greet the world.")
+    @click.option("--br", is_flag=True, show_default=True, default=True, help="Add a thematic break")
+    def dots(n, gr, br):
+        if gr:
+            click.echo('Hello world!')
+        click.echo('.' * n)
+        if br:
+            click.echo('-' * n)
+
+.. click:run::
+
+   invoke(dots, args=['--help'])
+
+
 Multi Value Options
 -------------------
 
diff --git a/src/click/core.py b/src/click/core.py
index 34fae4b..8ca050c 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -2416,25 +2416,26 @@ class Option(Parameter):
 
     All other parameters are passed onwards to the parameter constructor.
 
-    :param show_default: controls if the default value should be shown on the
-                         help page. Normally, defaults are not shown. If this
-                         value is a string, it shows the string instead of the
-                         value. This is particularly useful for dynamic options.
-    :param show_envvar: controls if an environment variable should be shown on
-                        the help page.  Normally, environment variables
-                        are not shown.
-    :param prompt: if set to `True` or a non empty string then the user will be
-                   prompted for input.  If set to `True` the prompt will be the
-                   option name capitalized.
+    :param show_default: Controls if the default value should be shown
+        on the help page. Normally, defaults are not shown. If this
+        value is a string, it shows that string in parentheses instead
+        of the actual value. This is particularly useful for dynamic
+        options. For single option boolean flags, the default remains
+        hidden if its value is ``False``.
+    :param show_envvar: Controls if an environment variable should be
+        shown on the help page. Normally, environment ariables are not
+        shown.
+    :param prompt: If set to ``True`` or a non empty string then the
+        user will be prompted for input. If set to ``True`` the prompt
+        will be the option name capitalized.
     :param confirmation_prompt: Prompt a second time to confirm the
         value if it was prompted for. Can be set to a string instead of
         ``True`` to customize the message.
     :param prompt_required: If set to ``False``, the user will be
         prompted for input only when the option was specified as a flag
         without a value.
-    :param hide_input: if this is `True` then the input on the prompt will be
-                       hidden from the user.  This is useful for password
-                       input.
+    :param hide_input: If this is ``True`` then the input on the prompt
+        will be hidden from the user. This is useful for password input.
     :param is_flag: forces this option to act as a flag.  The default is
                     auto detection.
     :param flag_value: which value should be used for this flag if it's
@@ -2452,6 +2453,10 @@ class Option(Parameter):
     :param help: the help string.
     :param hidden: hide this option from help outputs.
 
+    .. versionchanged:: 8.1.0
+        The default of a single option boolean flag is not shown if the
+        default value is ``False``.
+
     .. versionchanged:: 8.0.1
         ``type`` is detected from ``flag_value`` if given.
     """
@@ -2745,6 +2750,8 @@ class Option(Parameter):
                 default_string = split_opt(
                     (self.opts if self.default else self.secondary_opts)[0]
                 )[1]
+            elif self.is_bool_flag and not self.secondary_opts and not default_value:
+                default_string = ""
             else:
                 default_string = str(default_value)
 


Test patch:
diff --git a/tests/test_formatting.py b/tests/test_formatting.py
index f957e01..1cbf32b 100644
--- a/tests/test_formatting.py
+++ b/tests/test_formatting.py
@@ -322,12 +322,13 @@ def test_global_show_default(runner):
         pass
 
     result = runner.invoke(cli, ["--help"])
+    # the default to "--help" is not shown because it is False
     assert result.output.splitlines() == [
         "Usage: cli [OPTIONS]",
         "",
         "Options:",
         "  -f TEXT  Output file name  [default: out.txt]",
-        "  --help   Show this message and exit.  [default: False]",
+        "  --help   Show this message and exit.",
     ]
 
 
diff --git a/tests/test_options.py b/tests/test_options.py
index 53179e8..efdda48 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -700,16 +700,37 @@ def test_show_default_boolean_flag_name(runner, default, expect):
     assert f"[default: {expect}]" in message
 
 
-def test_show_default_boolean_flag_value(runner):
-    """When a boolean flag only has one opt, it will show the default
-    value, not the opt name.
+def test_show_true_default_boolean_flag_value(runner):
+    """When a boolean flag only has one opt and its default is True,
+    it will show the default value, not the opt name.
     """
     opt = click.Option(
-        ("--cache",), is_flag=True, show_default=True, help="Enable the cache."
+        ("--cache",),
+        is_flag=True,
+        show_default=True,
+        default=True,
+        help="Enable the cache.",
+    )
+    ctx = click.Context(click.Command("test"))
+    message = opt.get_help_record(ctx)[1]
+    assert "[default: True]" in message
+
+
+@pytest.mark.parametrize("default", [False, None])
+def test_hide_false_default_boolean_flag_value(runner, default):
+    """When a boolean flag only has one opt and its default is False or
+    None, it will not show the default
+    """
+    opt = click.Option(
+        ("--cache",),
+        is_flag=True,
+        show_default=True,
+        default=default,
+        help="Enable the cache.",
     )
     ctx = click.Context(click.Command("test"))
     message = opt.get_help_record(ctx)[1]
-    assert "[default: False]" in message
+    assert "[default: " not in message
 
 
 def test_show_default_string(runner):