Mercurial > hgrepos > Python > libs > ConfigMix
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")
