changeset 705:0485a033c95d

FIX: Parsing a filter chain for the new filter-only expansions: parse them backwards and use "," as filter-chain separator here. This allows using filter chains in filter-only expansions also.
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 15 Aug 2023 09:34:49 +0200
parents 457ef358c1a0
children 59a3fb7fcac3
files configmix/_speedups.c configmix/config.py tests/test.py
diffstat 3 files changed, 70 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/_speedups.c	Mon Aug 14 13:00:19 2023 +0200
+++ b/configmix/_speedups.c	Tue Aug 15 09:34:49 2023 +0200
@@ -25,6 +25,7 @@
     PyObject *QUOTE;
     PyObject *NS_SEPARATOR;
     PyObject *FILTER_SEPARATOR;
+    PyObject *FILTER_SEPARATOR_REV;
     PyObject *EMPTY_FILTER;
     PyObject *NONE_FILTER;
     PyObject *EMPTY_STR;
@@ -606,7 +607,7 @@
 
 static
 PyObject *
-_fast_split_filters(PyObject *varname, PyObject *self, struct speedups_state *sstate)
+_fast_split_filters(PyObject *varname, PyObject *self, Py_ssize_t direction, struct speedups_state *sstate)
 {
     Py_ssize_t varname_len;
     Py_ssize_t sep;
@@ -623,7 +624,7 @@
         sep = -1;
     }
     else {
-        sep = PyUnicode_FindChar(varname, '|', 0, varname_len, 1);
+        sep = PyUnicode_FindChar(varname, '|', 0, varname_len, (int)direction);
         if (sep == -2) {
             return NULL;
         }
@@ -682,7 +683,12 @@
         }
     }
 
-    tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR, -1);
+    if (direction == 1) {
+        tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR, -1);
+    }
+    else {
+        tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR_REV, -1);
+    }
     if (tmp == NULL) {
         goto error;
     }
@@ -706,9 +712,28 @@
 
 static
 PyObject *
-fast_split_filters(PyObject *self, PyObject *varname)
+fast_split_filters(PyObject *self, PyObject *args)
 {
-    return _fast_split_filters(varname, self, NULL);
+    PyObject *varname;
+    PyObject *direction_obj;
+    Py_ssize_t direction;
+
+    if (!PyArg_UnpackTuple(args, "fast_split_filters", 2, 2, &varname, &direction_obj)) {
+        return NULL;
+    }
+    direction = PyNumber_AsSsize_t(direction_obj, PyExc_OverflowError);
+    if (direction == -1) {
+        if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    if ((direction == 1) || (direction == -1)) {
+        return _fast_split_filters(varname, self, direction, NULL);
+    }
+    else {
+        PyErr_SetString(PyExc_ValueError, "`direction' must be -1 or +1");
+        return NULL;
+    }
 }
 
 
@@ -910,7 +935,7 @@
             goto error;
         }
 
-        tmp = _fast_split_filters(varname, NULL, sstate);
+        tmp = _fast_split_filters(varname, NULL, 1, sstate);
         if (tmp == NULL) {
             goto error;
         }
@@ -1130,7 +1155,7 @@
         if (tmp == NULL) {
             return NULL;
         }
-        filters = _fast_split_filters(tmp, NULL, sstate);
+        filters = _fast_split_filters(tmp, NULL, -1, sstate);
         if (filters == NULL) {
             py_clear_ref(&tmp);
             return NULL;
@@ -1215,7 +1240,7 @@
             goto error;
         }
 
-        tmp = _fast_split_filters(varname, NULL, sstate);
+        tmp = _fast_split_filters(varname, NULL, 1, sstate);
         if (tmp == NULL) {
             goto error;
         }
@@ -1709,7 +1734,7 @@
     {"fast_unquote", fast_unquote, METH_O, PyDoc_STR("C-implementation of configmix.unquote")},
     {"fast_quote", fast_quote, METH_O, PyDoc_STR("C-implementation of configmix.quote")},
     {"fast_pathstr2path", fast_pathstr2path, METH_O, PyDoc_STR("C-implementation of configmix.pathstr2path")},
-    {"_fast_split_filters", fast_split_filters, METH_O, PyDoc_STR("C-implementation of configmix.config._split_filters")},
+    {"_fast_split_filters", fast_split_filters, METH_VARARGS, PyDoc_STR("C-implementation of configmix.config._split_filters")},
     {"_fast_split_ns", fast_split_ns, METH_O, PyDoc_STR("C-implementation of configmix.config._split_ns")},
     {"_fast_interpolate_variables", fast_interpolate_variables, METH_VARARGS, PyDoc_STR("C-implementation of configmix.config.Configuration.interpolate_variables")},
     {"_fast_getvarl", (PyCFunction)fast_getvarl, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvarl")},
@@ -1767,6 +1792,12 @@
     }
     PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR));
 
+    sstate->FILTER_SEPARATOR_REV = PyUnicode_FromStringAndSize(",", 1);
+    if (sstate->FILTER_SEPARATOR_REV == NULL) {
+        return -1;
+    }
+    PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR_REV));
+
     sstate->EMPTY_FILTER = PyUnicode_FromStringAndSize("Empty", 5);
     if (sstate->EMPTY_FILTER == NULL) {
         return -1;
@@ -1848,6 +1879,7 @@
         Py_VISIT(sstate->QUOTE);
         Py_VISIT(sstate->NS_SEPARATOR);
         Py_VISIT(sstate->FILTER_SEPARATOR);
+        Py_VISIT(sstate->FILTER_SEPARATOR_REV);
         Py_VISIT(sstate->EMPTY_FILTER);
         Py_VISIT(sstate->NONE_FILTER);
         Py_VISIT(sstate->EMPTY_STR);
@@ -1875,6 +1907,7 @@
         Py_CLEAR(sstate->QUOTE);
         Py_CLEAR(sstate->NS_SEPARATOR);
         Py_CLEAR(sstate->FILTER_SEPARATOR);
+        Py_CLEAR(sstate->FILTER_SEPARATOR_REV);
         Py_CLEAR(sstate->EMPTY_FILTER);
         Py_CLEAR(sstate->NONE_FILTER);
         Py_CLEAR(sstate->EMPTY_STR);
--- a/configmix/config.py	Mon Aug 14 13:00:19 2023 +0200
+++ b/configmix/config.py	Tue Aug 15 09:34:49 2023 +0200
@@ -240,6 +240,7 @@
 _HIER_SEPARATOR = u(b'.')
 _NS_SEPARATOR = u(b':')
 _FILTER_SEPARATOR = u(b'|')
+_FILTER_SEPARATOR_REV = u(b',')
 _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR
 _ENDTOK_REF = _ENDTOK
 _ENDTOK_FILTER = _FILTER_SEPARATOR + _ENDTOK
@@ -430,20 +431,29 @@
     _split_ns = _py_split_ns
 
 
-def _py_split_filters(varname):
+def _py_split_filters(varname, direction):
     """Split off the filter part from the `varname` string
 
-    :type varname: str
+    :param str varname: The string where to search for filters
+    :param int direction: +1 means to do a forward search, -1 a backward search
     :return: The tuple of the variable name without the filters and a list
              of filters
     :rtype: tuple(str, list)
 
     """
-    name, sep, filters = varname.partition(_FILTER_SEPARATOR)
+    if direction == 1:
+        name, sep, filters = varname.partition(_FILTER_SEPARATOR)
+    elif direction == -1:
+        name, sep, filters = varname.rpartition(_FILTER_SEPARATOR)
+    else:
+        raise ValueError("`direction' must be -1 or +1")
     if sep:
         filters = filters.strip()
         if filters:
-            return (name, filters.split(_FILTER_SEPARATOR))
+            if direction == 1:
+                return (name, filters.split(_FILTER_SEPARATOR))
+            else:
+                return (name, filters.split(_FILTER_SEPARATOR_REV))
         else:
             return (name, [])
     else:
@@ -1016,7 +1026,7 @@
                 and (start == 0)):
             if s.find(_ENDTOK_FILTER, 3) != (len_s - 3):
                 raise ValueError("`{{|' global filter interpolation must end with `|}}'")
-            new_s, filters = _split_filters(s[3:-3])
+            new_s, filters = _split_filters(s[3:-3], -1)
             try:
                 varvalue = self.py_interpolate_variables(new_s)
             except KeyError:
@@ -1039,7 +1049,7 @@
                 rest = start
                 break
             varname, filters = _split_filters(
-                s[start+2:end])     # noqa: E226
+                s[start+2:end], 1)     # noqa: E226
             try:
                 varvalue, cacheable = self._py_getvar_s_with_cache_info(varname)
             except KeyError:
--- a/tests/test.py	Mon Aug 14 13:00:19 2023 +0200
+++ b/tests/test.py	Tue Aug 15 09:34:49 2023 +0200
@@ -2211,22 +2211,23 @@
         self.assertEqual((u":", u"%x3a"), self.split_ns(u"%x3a:%x3a"))
 
     def test_split_filters_empty(self):
-        self.assertEqual((u"", []), self.split_filters(u""))
+        self.assertEqual((u"", []), self.split_filters(u"", 1))
 
     def test_split_filters_varname_only_no_stripping(self):
-        self.assertEqual((u" varname ", []), self.split_filters(u" varname "))
+        self.assertEqual((u" varname ", []),
+                         self.split_filters(u" varname ", 1))
 
     def test_split_filters_single_no_stripping(self):
         self.assertEqual((u" the-varname ", []),
-                         self.split_filters(u" the-varname |  "))
+                         self.split_filters(u" the-varname |  ", 1))
 
     def test_split_filters_one(self):
         self.assertEqual((u"the-varname", [u"None"]),
-                         self.split_filters(u"the-varname|None"))
+                         self.split_filters(u"the-varname|None", 1))
 
     def test_split_filters_many(self):
         self.assertEqual((u"the-varname", [u"Empty", u"None"]),
-                         self.split_filters(u"the-varname|Empty|None"))
+                         self.split_filters(u"the-varname|Empty|None", 1))
 
     def test_None_filter_single(self):
         cfg = configmix.load()
@@ -2421,9 +2422,14 @@
         y = getattr(cfg, self.interpolate_meth)(u"{{|{{not-existing}}|Empty|}}")
         self.assertEqual(u(""), y)
 
+    def test_global_nested_filter_empty(self):
+        cfg = configmix.load()
+        y = getattr(cfg, self.interpolate_meth)(u"{{|pre-{{not-existing|Empty}}-post|upper|}}")
+        self.assertEqual(u("PRE--POST"), y)
+
     def test_global_filter_upper(self):
         cfg1 = {
-            u("key-1"): u("value for key-1")
+            u("key-1"): u("Value for key-1")
         }
         cfg = cfg = configmix.config.Configuration(cfg1)
         y = getattr(cfg, self.interpolate_meth)(u"{{|pre_{{key-1}}_post|upper|}}")