From c3ba9348f5964ed07886a5945dbaa921afafaadb Mon Sep 17 00:00:00 2001
From: Matthias Diener <mdiener@illinois.edu>
Date: Tue, 21 May 2024 12:17:06 -0500
Subject: [PATCH] add to_identifier

---
 pytools/__init__.py          | 28 ++++++++++++++++++++++++++++
 pytools/test/test_pytools.py | 17 +++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/pytools/__init__.py b/pytools/__init__.py
index b43032f..28e71bb 100644
--- a/pytools/__init__.py
+++ b/pytools/__init__.py
@@ -189,6 +189,7 @@ String utilities
 ----------------
 
 .. autofunction:: strtobool
+.. autofunction:: to_identifier
 
 Sequence utilities
 ------------------
@@ -2999,6 +3000,33 @@ def strtobool(val: Optional[str], default: Optional[bool] = None) -> bool:
 # }}}
 
 
+# {{{ to_identifier
+
+def to_identifier(s: str) -> str:
+    """Convert a string to a valid Python identifier, by removing
+    non-alphanumeric, non-underscore characters, and prepending an underscore
+    if the string starts with a numeric character.
+
+    :param s: The string to convert to an identifier.
+
+    :returns: The converted string.
+    """
+    if s.isidentifier():
+        return s
+
+    s = "".join(c for c in s if c.isalnum() or c == "_")
+
+    if len(s) == 0:
+        return "_"
+
+    if s[0].isdigit():
+        s = "_" + s
+
+    return s
+
+# }}}
+
+
 # {{{ unique
 
 def unique(seq: Sequence[T]) -> Iterator[T]:
diff --git a/pytools/test/test_pytools.py b/pytools/test/test_pytools.py
index 3c1d924..453f168 100644
--- a/pytools/test/test_pytools.py
+++ b/pytools/test/test_pytools.py
@@ -740,6 +740,23 @@ def test_strtobool():
     assert strtobool(None, False) is False
 
 
+def test_to_identifier() -> None:
+    from pytools import to_identifier
+
+    assert to_identifier("_a_123_") == "_a_123_"
+    assert to_identifier("a_123") == "a_123"
+    assert to_identifier("a 123") == "a123"
+    assert to_identifier("123") == "_123"
+    assert to_identifier("_123") == "_123"
+    assert to_identifier("123A") == "_123A"
+    assert to_identifier("") == "_"
+
+    assert not "a 123".isidentifier()
+    assert to_identifier("a 123").isidentifier()
+    assert to_identifier("123").isidentifier()
+    assert to_identifier("").isidentifier()
+
+
 def test_typedump():
     from pytools import typedump
     assert typedump("") == "str"
-- 
GitLab