changeset 502:4f90e1eb7af8

Make quote() and unquote() module globals and also export from configmix
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 18 Dec 2021 01:26:41 +0100
parents 1c83389fb8dd
children a56f1d97a3f3
files configmix/__init__.py configmix/config.py tests/data/conf_perf.py tests/test.py
diffstat 4 files changed, 79 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/__init__.py	Sat Dec 18 01:26:13 2021 +0100
+++ b/configmix/__init__.py	Sat Dec 18 01:26:41 2021 +0100
@@ -32,7 +32,7 @@
 import re
 
 from .compat import u2fs
-from .config import Configuration
+from .config import Configuration, quote, unquote          # noqa: F401
 from . import constants
 
 
--- a/configmix/config.py	Sat Dec 18 01:26:13 2021 +0100
+++ b/configmix/config.py	Sat Dec 18 01:26:41 2021 +0100
@@ -251,6 +251,72 @@
 """
 
 
+def quote(s):
+    """Replace important special characters in string `s` by replacing
+    them with ``%xNN`` where `NN` are the two hexadecimal digits of the
+    characters unicode codepoint value.
+
+    Handled are the important special chars: ``%``, ``.``, ``:``,
+    ``#``; ``'``, ``"``, ``|``, ``{``, ``}``, ``[`` and ``]``.
+
+    See also the :ref:`quoting` section.
+
+    """
+    # Quick check whether all of the chars are in _QUOTE_SAFE
+    if not s.rstrip(_QUOTE_SAFE):
+        return s
+
+    # Slow path
+    re_encode = False
+    if PY2:
+        # Use the Unicode translation variant in PY2
+        if isinstance(s, str):
+            s = s.decode("latin1")
+            re_encode = True
+    s = s.translate(_QUOTE_MAP)
+    if re_encode:
+        return s.encode("latin1")
+    else:
+        return s
+
+
+def unquote(s):
+    """Unquote the content of `s`: handle all patterns ``%xNN``,
+    ``%uNNNN`` or ``%UNNNNNNNN``.
+
+    This is the inverse of :func:`.quote`.
+
+    """
+    if _QUOTE not in s:
+        return s
+    parts = s.split(_QUOTE)
+    res = [parts[0]]
+    res_append = res.append
+    for p in parts[1:]:
+        try:
+            qc = p[0]
+        except IndexError:
+            raise ValueError("unknown quote syntax string: {}".format(s))
+        if qc == _QUOTE_x:
+            if len(p) < 3:
+                raise ValueError("quote syntax: length too small")
+            res_append(uchr(int(p[1:3], 16)))
+            res_append(p[3:])
+        elif qc == _QUOTE_u:
+            if len(p) < 5:
+                raise ValueError("quote syntax: length too small")
+            res_append(uchr(int(p[1:5], 16)))
+            res_append(p[5:])
+        elif qc == _QUOTE_U:
+            if len(p) < 9:
+                raise ValueError("quote syntax: length too small")
+            res_append(uchr(int(p[1:9], 16)))
+            res_append(p[9:])
+        else:
+            raise ValueError("unknown quote syntax string: {}".format(s))
+    return _EMPTY_STR.join(res)
+
+
 class Configuration(CoercingMethodsMixin, _AttributeDict):
 
     """The configuration dictionary with attribute support or
@@ -410,14 +476,14 @@
         Special characters (e.g. ``:`` and ``.``) must be quoted when using
         the default namespace.
 
-        See also :meth:`~.quote`.
+        See also :func:`.quote`.
 
         """
         varns, varname = self._split_ns(varname)
         if not varns:
             if varname:
                 varnameparts = [
-                    self.unquote(vp)
+                    unquote(vp)
                     for vp in varname.split(_HIER_SEPARATOR)
                 ]
             else:
@@ -564,7 +630,7 @@
     def _split_ns(self, s):
         ns, sep, rest = s.partition(_NS_SEPARATOR)
         if sep:
-            return (self.unquote(ns), rest)
+            return (unquote(ns), rest)
         else:
             return (None, ns)
 
@@ -737,72 +803,6 @@
                 value = filterfn(self, value)
         return value
 
-    @staticmethod
-    def quote(s):
-        """Replace important special characters in string `s` by replacing
-        them with ``%xNN`` where `NN` are the two hexadecimal digits of the
-        characters unicode codepoint value.
-
-        Handled are the important special chars: ``%``, ``.``, ``:``,
-        ``#``; ``'``, ``"``, ``|``, ``{``, ``}``, ``[`` and ``]``.
-
-        See also the :ref:`quoting` section.
-
-        """
-        # Quick check whether all of the chars are in _QUOTE_SAFE
-        if not s.rstrip(_QUOTE_SAFE):
-            return s
-
-        # Slow path
-        re_encode = False
-        if PY2:
-            # Use the Unicode translation variant in PY2
-            if isinstance(s, str):
-                s = s.decode("latin1")
-                re_encode = True
-        s = s.translate(_QUOTE_MAP)
-        if re_encode:
-            return s.encode("latin1")
-        else:
-            return s
-
-    @staticmethod
-    def unquote(s):
-        """Unquote the content of `s`: handle all patterns ``%xNN``,
-        ``%uNNNN`` or ``%UNNNNNNNN``.
-
-        This is the inverse of :meth:`~.quote`.
-
-        """
-        if _QUOTE not in s:
-            return s
-        parts = s.split(_QUOTE)
-        res = [parts[0]]
-        res_append = res.append
-        for p in parts[1:]:
-            try:
-                qc = p[0]
-            except IndexError:
-                raise ValueError("unknown quote syntax string: {}".format(s))
-            if qc == _QUOTE_x:
-                if len(p) < 3:
-                    raise ValueError("quote syntax: length too small")
-                res_append(uchr(int(p[1:3], 16)))
-                res_append(p[3:])
-            elif qc == _QUOTE_u:
-                if len(p) < 5:
-                    raise ValueError("quote syntax: length too small")
-                res_append(uchr(int(p[1:5], 16)))
-                res_append(p[5:])
-            elif qc == _QUOTE_U:
-                if len(p) < 9:
-                    raise ValueError("quote syntax: length too small")
-                res_append(uchr(int(p[1:9], 16)))
-                res_append(p[9:])
-            else:
-                raise ValueError("unknown quote syntax string: {}".format(s))
-        return _EMPTY_STR.join(res)
-
     def jailed(self, rootpath=None, root=None, bind_root=True):
         """Return a "jailed" configuration of the current configuration.
 
@@ -836,8 +836,7 @@
                     "jailed configurations do not support namespaces")
             if varname:
                 rootpath = [
-                    self.unquote(p) for p in root.split(
-                        _HIER_SEPARATOR)
+                    unquote(p) for p in root.split(_HIER_SEPARATOR)
                 ]
             else:
                 rootpath = tuple()
@@ -880,7 +879,7 @@
         if path:
             self._pathstr = \
                 _HIER_SEPARATOR.join(
-                    [Configuration.quote(p) for p in path]) \
+                    [quote(p) for p in path]) \
                 + _HIER_SEPARATOR
         else:
             self._pathstr = _EMPTY_STR
@@ -1054,8 +1053,7 @@
                     "sub-jails do not support namespaces")
             if varname:
                 rootpath = [
-                    self._base.unquote(p) for p in varname.split(
-                        _HIER_SEPARATOR)
+                    unquote(p) for p in varname.split(_HIER_SEPARATOR)
                 ]
             else:
                 rootpath = tuple()
--- a/tests/data/conf_perf.py	Sat Dec 18 01:26:13 2021 +0100
+++ b/tests/data/conf_perf.py	Sat Dec 18 01:26:41 2021 +0100
@@ -26,5 +26,6 @@
             u"{{key1}}"
         ]),
         (u'key9', OrderedDict()),
-        (u'key10', u"1{{key1}}2{{key2}}{{key1}}3{{tree1.tree2.key4}}"),        
+        (u'key10', u"1{{key1}}2{{key2}}{{key1}}3{{tree1.tree2.key4}}"),
+        (u"key11", u"{{tree1.tree2.key6}}"),
     ]))])
--- a/tests/test.py	Sat Dec 18 01:26:13 2021 +0100
+++ b/tests/test.py	Sat Dec 18 01:26:41 2021 +0100
@@ -1207,16 +1207,16 @@
 
     def test_quoting_and_unquoting_are_inverse(self):
         for c in """%.:#|"'{}[]""":
-            qc = self._cfg.quote(c)
+            qc = configmix.quote(c)
             self.assertTrue(qc.startswith("%x") and len(qc) == 4)
-            self.assertEqual(c, self._cfg.unquote(qc))
+            self.assertEqual(c, configmix.unquote(qc))
 
     def test_quoting_and_unquoting_are_identical(self):
         # other characters
         for c in """abc09/""":
-            qc = self._cfg.quote(c)
+            qc = configmix.quote(c)
             self.assertEqual(c, qc)
-            self.assertEqual(c, self._cfg.unquote(qc))
+            self.assertEqual(c, configmix.unquote(qc))
 
     def test_namespace_quoting(self):
         v1 = self._cfg.getvar("PY:version")