Repository: pallets/click (python)

Issue:
BOOL should accept "on" and "off"
The `BOOL` parameter type should accept & convert the strings "on" and "off".  With this change, `BOOL` would support all of the boolean strings that `configparser` supports.

Pull request description:
Make ``BOOL`` accept the strings "on" and "off"
 Closes #1629.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index cd40f78..0fce1bc 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -28,6 +28,7 @@ Unreleased
     matched name. This makes behavior such as help text and
     ``Context.invoked_subcommand`` consistent when using patterns like
     ``AliasedGroup``. :issue:`1422`
+-   The ``BOOL`` type accepts the values "on" and "off". :issue:`1629`
 
 
 Version 7.1.2
diff --git a/docs/parameters.rst b/docs/parameters.rst
index f5f181f..27c84ea 100644
--- a/docs/parameters.rst
+++ b/docs/parameters.rst
@@ -47,10 +47,10 @@ different behavior and some are supported out of the box:
     A parameter that only accepts floating point values.
 
 ``bool`` / :data:`click.BOOL`:
-    A parameter that accepts boolean values.  This is automatically used
-    for boolean flags.  If used with string values ``1``, ``yes``, ``y``, ``t``
-    and ``true`` convert to `True` and ``0``, ``no``, ``n``, ``f`` and ``false``
-    convert to `False`.
+    A parameter that accepts boolean values. This is automatically used
+    for boolean flags. The string values "1", "true", "t", "yes", "y",
+    and "on" convert to ``True``. "0", "false", "f", "no", "n", and
+    "off" convert to ``False``.
 
 :data:`click.UUID`:
     A parameter that accepts UUID values.  This is not automatically
diff --git a/src/click/types.py b/src/click/types.py
index 93cf701..d50786a 100644
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -387,11 +387,11 @@ class BoolParamType(ParamType):
         if isinstance(value, bool):
             return bool(value)
         value = value.lower()
-        if value in ("true", "t", "1", "yes", "y"):
+        if value in {"1", "true", "t", "yes", "y", "on"}:
             return True
-        elif value in ("false", "f", "0", "no", "n"):
+        elif value in {"0", "false", "f", "no", "n", "off"}:
             return False
-        self.fail(f"{value} is not a valid boolean", param, ctx)
+        self.fail(f"{value!r} is not a valid boolean value.", param, ctx)
 
     def __repr__(self):
         return "BOOL"


Test patch:
diff --git a/tests/test_basic.py b/tests/test_basic.py
index c33e3a4..9daccc2 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -1,5 +1,8 @@
 import os
 import uuid
+from itertools import chain
+
+import pytest
 
 import click
 
@@ -198,27 +201,24 @@ def test_boolean_option(runner):
         assert result.output == f"{default}\n"
 
 
-def test_boolean_conversion(runner):
-    for default in True, False:
-
-        @click.command()
-        @click.option("--flag", default=default, type=bool)
-        def cli(flag):
-            click.echo(flag)
-
-        for value in "true", "t", "1", "yes", "y":
-            result = runner.invoke(cli, ["--flag", value])
-            assert not result.exception
-            assert result.output == "True\n"
+@pytest.mark.parametrize(
+    ("value", "expect"),
+    chain(
+        ((x, "True") for x in ("1", "true", "t", "yes", "y", "on")),
+        ((x, "False") for x in ("0", "false", "f", "no", "n", "off")),
+    ),
+)
+def test_boolean_conversion(runner, value, expect):
+    @click.command()
+    @click.option("--flag", type=bool)
+    def cli(flag):
+        click.echo(flag, nl=False)
 
-        for value in "false", "f", "0", "no", "n":
-            result = runner.invoke(cli, ["--flag", value])
-            assert not result.exception
-            assert result.output == "False\n"
+    result = runner.invoke(cli, ["--flag", value])
+    assert result.output == expect
 
-        result = runner.invoke(cli, [])
-        assert not result.exception
-        assert result.output == f"{default}\n"
+    result = runner.invoke(cli, ["--flag", value.title()])
+    assert result.output == expect
 
 
 def test_file_option(runner):