diff configmix/config.py @ 305:f529ca46dd50

Implemented the "ref" namespace to get configuration tree references. BUGS: - Tests should be done more thoroughly and extensively - Interaction of tree references and variable substitution should be tested more properly - Documentation is missing yet
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 26 Apr 2021 09:42:42 +0200
parents d8361dd70d2d
children 801cd16223e4
line wrap: on
line diff
--- a/configmix/config.py	Sun Apr 25 18:05:26 2021 +0200
+++ b/configmix/config.py	Mon Apr 26 09:42:42 2021 +0200
@@ -22,9 +22,14 @@
         from ordereddict import OrderedDict as ConfigurationBase
     except ImportError:
         ConfigurationBase = dict
+try:
+    from urllib.parse import urlsplit
+except ImportError:
+    from urlparse import urlsplit
 
 from .variables import lookup_varns, lookup_filter
 from .compat import u
+from .constants import REF_NAMESPACE
 
 
 _MARKER = object()
@@ -55,6 +60,16 @@
 
     """
 
+    # Speed
+    _TEXTTYPE = type(u(""))
+    _STARTTOK = u(b"{{")
+    _ENDTOK = u(b"}}")
+    _NS_SEPARATOR = u(b':')
+    _FILTER_SEPARATOR = u(b'|')
+    _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR
+    _ENDTOK_REF = _ENDTOK
+    _DOT = u(b'.')
+
     def getvar(self, varname, default=_MARKER):
         """Get a variable of the form ``[ns:][[key1.]key2.]name`` - including
         variables from other namespaces.
@@ -67,7 +82,10 @@
             if not varns:
                 lookupfn = self._lookupvar
             else:
-                lookupfn = lookup_varns(varns)
+                if varns == REF_NAMESPACE:
+                    lookupfn = self._lookupref
+                else:
+                    lookupfn = lookup_varns(varns)
             varvalue = lookupfn(varname)
         except KeyError:
             if default is _MARKER:
@@ -139,14 +157,14 @@
             return s
 
     def _split_ns(self, s):
-        nameparts = s.split(':', 1)
+        nameparts = s.split(self._NS_SEPARATOR, 1)
         if len(nameparts) == 1:
             return (None, s, )
         else:
             return (nameparts[0], nameparts[1], )
 
     def _split_filters(self, s):
-        nameparts = s.split('|')
+        nameparts = s.split(self._FILTER_SEPARATOR)
         if len(nameparts) == 1:
             return (s, [], )
         else:
@@ -160,9 +178,9 @@
         """
         parts = key.split('.')
         try:
-            v = self[parts[0]]
+            v = self.expand_if_reference(self[parts[0]])
             for p in parts[1:]:
-                v = v[p]
+                v = self.expand_if_reference(v[p])
         except TypeError:
             raise KeyError(
                 "Configuration variable %r not found"
@@ -174,8 +192,41 @@
                 return default
         return v
 
-    # Speed
-    _TEXTTYPE = type(u(""))
+    def _lookupref(self, key, default=_MARKER):
+        """
+        `key` must be a reference URI without any (namespace) prefixes
+        and suffixes
+
+        """
+        return self.expand_ref_uri(key, default=default)
+
+    def expand_if_reference(self, v, default=_MARKER):
+        """Check whether `v` is a reference and -- if true -- then expand it.
+
+        `v` must match the pattern ``{{{ref:<REFERENCE>}}}``
+
+        All non-matching texttypes and all non-texttypes are returned
+        unchanged.
+
+        """
+        if not isinstance(v, self._TEXTTYPE):
+            return v
+        if not v.startswith(self._STARTTOK_REF) \
+                or not v.endswith(self._ENDTOK_REF):
+            return v
+        return self.expand_ref_uri(
+            v[len(self._STARTTOK_REF):-len(self._ENDTOK_REF)],
+            default=default)
+
+    def expand_ref_uri(self, uri, default=_MARKER):
+        pu = urlsplit(uri)
+        if pu.scheme or pu.netloc or pu.path or pu.query:
+            raise ValueError("only fragment-only URIs are supported")
+        if not pu.fragment:
+            return self
+        if pu.fragment.startswith(self._DOT):
+            raise ValueError("relative refs not supported")
+        return self.getvar(pu.fragment, default=default)
 
     def substitute_variables_in_obj(self, obj):
         """Recursively expand variables in the object tree `obj`."""
@@ -199,10 +250,6 @@
         else:
             return obj
 
-    # Speed
-    _STARTTOK = u(b"{{")
-    _ENDTOK = u(b"}}")
-
     def expand_variable(self, s):
         """Expand variables in the single string `s`"""
         start = s.find(self._STARTTOK, 0)