chiark / gitweb /
peerdb/tripe-newpeers.in (ConfigSection): Fix handling of `@inherits'.
[tripe] / peerdb / tripe-newpeers.in
index 8b4d800c41f92acb01605845c53a1bb3a23a7dfb..1213b904aad2877afe380846c8c06dea4139554e 100644 (file)
@@ -180,8 +180,25 @@ class ConfigSection (object):
 
   def __init__(me, name, cp):
     """Initialize a new, empty section with a given NAME and parent CP."""
+
+    ## The cache maps item keys to entries, which consist of a pair of
+    ## objects.  There are four possible states for a cache entry:
+    ##
+    ##   * missing -- there is no entry at all with this key, so we must
+    ##     search for it;
+    ##
+    ##   * None, None -- we are actively trying to resolve this key, so if we
+    ##     encounter this state, we have found a cycle in the inheritance
+    ##     graph;
+    ##
+    ##   * None, [] -- we know that this key isn't reachable through any of
+    ##     our parents;
+    ##
+    ##   * VALUE, PATH -- we know that the key resolves to VALUE, along the
+    ##     PATH from us (exclusive) to the defining parent (inclusive).
     me.name = name
     me._itemmap = dict()
+    me._cache = dict()
     me._cp = cp
 
   def _expand(me, string, resolvep):
@@ -206,7 +223,7 @@ class ConfigSection (object):
     for name in names.replace(',', ' ').split():
       yield me._cp.section(name)
 
-  def _get(me, key, map = None, path = None):
+  def _get(me, key, path = None):
     """
     Low-level option-fetching method.
 
@@ -218,9 +235,7 @@ class ConfigSection (object):
     Returns None if no value could be found.
     """
 
-    ## If we weren't given a memoization map or path, then we'd better make
-    ## one.
-    if map is None: map = {}
+    ## If we weren't given a path, then we'd better make one.
     if path is None: path = []
 
     ## Extend the path to cover us, but remember to remove us again when
@@ -229,29 +244,33 @@ class ConfigSection (object):
     path.append(me.name)
     try:
 
-      ## If we've been this way before on another pass through then return
-      ## the value we found then.  If we're still thinking about it then
-      ## we've found a cycle.
-      try: threadp, value = map[me.name]
+      ## If we've been this way before on another pass through then return the
+      ## value we found then.  If we're still thinking about it then we've
+      ## found a cycle.
+      try: v, p = me._cache[key]
       except KeyError: pass
       else:
-        if threadp: raise InheritanceCycleError(key, path[:])
+        if p is None: raise InheritanceCycleError(key, path[:])
+        else: return v, path + p
 
       ## See whether the answer is ready waiting for us.
       try: v = me._itemmap[key]
       except KeyError: pass
-      else: return v, path[:]
+      else:
+        p = path[:]
+        me._cache[key] = v, []
+        return v, p
 
       ## Initially we have no idea.
       value = None
-      winner = None
+      winner = []
 
       ## Go through our parents and ask them what they think.
-      map[me.name] = True, None
+      me._cache[key] = None, None
       for p in me._parents():
 
         ## See whether we get an answer.  If not, keep on going.
-        v, pp = p._get(key, map, path)
+        v, pp = p._get(key, path)
         if v is None: continue
 
         ## If we got an answer, check that it matches any previous ones.
@@ -262,7 +281,7 @@ class ConfigSection (object):
           raise AmbiguousOptionError(key, winner, value, pp, v)
 
       ## That's the best we could manage.
-      map[me.name] = False, value
+      me._cache[key] = value, winner[len(path):]
       return value, winner
 
     finally:
@@ -277,6 +296,9 @@ class ConfigSection (object):
     ## Special handling for the `name' key.
     if key == 'name':
       value = me._itemmap.get('name', me.name)
+    elif key == '@inherits':
+      try: return me._itemmap['@inherits']
+      except KeyError: raise MissingKeyException(me.name, key)
     else:
       value, _ = me._get(key)
       if value is None:
@@ -291,23 +313,22 @@ class ConfigSection (object):
     """
 
     ## Initialize for a depth-first walk of the inheritance graph.
-    d = {}
-    visited = {}
+    seen = {}
+    visiting = { me.name: True }
     stack = [me]
 
     ## Visit nodes, collecting their keys.  Don't believe the values:
     ## resolving inheritance is too hard to do like this.
     while stack:
       sec = stack.pop()
-      if sec.name in visited: continue
-      visited[sec.name] = True
-      stack += sec._parents()
+      for p in sec._parents():
+        if p.name not in visiting:
+          stack.append(p); visiting[p.name] = True
 
-      for key in sec._itemmap.iterkeys():
-        if key != '@inherit': d[key] = None
+      for key in sec._itemmap.iterkeys(): seen[key] = None
 
     ## And we're done.
-    return d.iterkeys()
+    return seen.iterkeys()
 
 class MyConfigParser (object):
   """