From d622b0b51272eaac81f1aff81af0b488a043c5ba Mon Sep 17 00:00:00 2001
From: Matthias Diener <mdiener@illinois.edu>
Date: Thu, 20 Oct 2022 19:03:30 -0500
Subject: [PATCH] add strtobool() (#150)

* add strtobool()

Based on the now-deprecated distutils.util.strtobool.

* add quotes

* only use default if val is None

* update function signature

Co-authored-by: Alex Fikl <alexfikl@gmail.com>

* clarify messages

Co-authored-by: Alex Fikl <alexfikl@gmail.com>
---
 pytools/__init__.py  | 43 +++++++++++++++++++++++++++++++++++++++++++
 test/test_pytools.py | 23 +++++++++++++++++++++++
 2 files changed, 66 insertions(+)

diff --git a/pytools/__init__.py b/pytools/__init__.py
index bfe6cee..e018f94 100644
--- a/pytools/__init__.py
+++ b/pytools/__init__.py
@@ -179,6 +179,11 @@ Sampling
 .. autofunction:: sphere_sample_equidistant
 .. autofunction:: sphere_sample_fibonacci
 
+String utilities
+----------------
+
+.. autofunction:: strtobool
+
 Type Variables Used
 -------------------
 
@@ -2907,6 +2912,44 @@ def sphere_sample_fibonacci(
 # }}}
 
 
+# {{{ strtobool
+
+def strtobool(val: Optional[str], default: Optional[bool] = None) -> bool:
+    """Convert a string representation of truth to True or False.
+    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
+    are 'n', 'no', 'f', 'false', 'off', and '0'.  Uppercase versions are
+    also accepted. If *default* is None, raises ValueError if *val* is anything
+    else. If *val* is None and *default* is not None, returns *default*.
+    Based on :func:`distutils.util.strtobool`.
+
+    :param val: Value to convert.
+    :param default: Value to return if *val* is None.
+
+    :returns: Truth value of *val*.
+    """
+    if val is None and default is not None:
+        return default
+
+    if val is None:
+        raise ValueError(f"invalid truth value '{val}'. "
+                          "Valid values are ('y', 'yes', 't', 'true', 'on', '1') "
+                          "for 'True' and ('n', 'no', 'f', 'false', 'off', '0') "
+                          "for 'False'. Uppercase versions are also accepted.")
+
+    val = val.lower()
+    if val in ("y", "yes", "t", "true", "on", "1"):
+        return True
+    elif val in ("n", "no", "f", "false", "off", "0"):
+        return False
+    else:
+        raise ValueError(f"invalid truth value '{val}'. "
+                          "Valid values are ('y', 'yes', 't', 'true', 'on', '1') "
+                          "for 'True' and ('n', 'no', 'f', 'false', 'off', '0') "
+                          "for 'False'. Uppercase versions are also accepted.")
+
+# }}}
+
+
 def _test():
     import doctest
     doctest.testmod()
diff --git a/test/test_pytools.py b/test/test_pytools.py
index 214e014..d89e9c9 100644
--- a/test/test_pytools.py
+++ b/test/test_pytools.py
@@ -703,6 +703,29 @@ def test_ignoredforequalitytag():
     assert hash(eq1) != hash(eq3)
 
 
+def test_strtobool():
+    from pytools import strtobool
+    assert strtobool("true") is True
+    assert strtobool("tRuE") is True
+    assert strtobool("1") is True
+    assert strtobool("t") is True
+    assert strtobool("on") is True
+
+    assert strtobool("false") is False
+    assert strtobool("FaLse") is False
+    assert strtobool("0") is False
+    assert strtobool("f") is False
+    assert strtobool("off") is False
+
+    with pytest.raises(ValueError):
+        strtobool("tru")
+        strtobool("fal")
+        strtobool("xxx")
+        strtobool(".")
+
+    assert strtobool(None, False) is False
+
+
 if __name__ == "__main__":
     if len(sys.argv) > 1:
         exec(sys.argv[1])
-- 
GitLab