chiark / gitweb /
peerdb/tripe-newpeers.in (ConfigSection.items): Push more cautiously.
[tripe] / peerdb / tripe-newpeers.in
index ed87189fbda278904d89a993ca651b44afeac7d5..fdb91708a42105a7aa6b01efc3bd189b322a5d7a 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):
@@ -199,15 +216,14 @@ class ConfigSection (object):
                               string)
     return string
 
-  def has_option(me, key):
-    """
-    Decide whether this section has a configuration key KEY.
+  def _parents(me):
+    """Yield this section's parents."""
+    try: names = me._itemmap['@inherit']
+    except KeyError: return
+    for name in names.replace(',', ' ').split():
+      yield me._cp.section(name)
 
-    This version of the method properly handles the @inherit key.
-    """
-    return key == 'name' or me._get(key)[0] is not None
-
-  def _get(me, key, map = None, path = None):
+  def _get(me, key, path = None):
     """
     Low-level option-fetching method.
 
@@ -219,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
@@ -230,36 +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[:]
-
-      ## No, apparently, not.  Find out our list of parents.
-      try:
-        parents = [me._cp.section(p) for p in
-                   me._itemmap['@inherit'].replace(',', ' ').split()]
-      except KeyError:
-        parents = []
+      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
-      for p in parents:
+      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.
@@ -270,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:
@@ -295,33 +306,27 @@ class ConfigSection (object):
 
   def items(me, resolvep = True):
     """
-    Return a list of (NAME, VALUE) items in this section.
-
-    This extends the default method by handling the inheritance chain.
+    Yield a list of item names in the section.
     """
 
     ## Initialize for a depth-first walk of the inheritance graph.
-    d = {}
-    visited = {}
-    stack = [me.name]
+    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 = me._cp.section(stack.pop())
-      if sec.name in visited: continue
-      visited[sec.name] = True
+      sec = stack.pop()
+      for p in sec._parents():
+        if p.name not in visiting:
+          stack.append(p); visiting[p.name] = True
 
-      for key, value in sec._itemmap.iteritems():
-        if key == '@inherit': stack += value.replace(',', ' ').split()
-        else: d[key] = None
-
-    ## Now collect the values for the known keys, one by one.
-    items = []
-    for key in d: items.append((key, me.get(key, resolvep)))
+      for key in sec._itemmap.iterkeys():
+        if key != '@inherit': seen[key] = None
 
     ## And we're done.
-    return items
+    return seen.iterkeys()
 
 class MyConfigParser (object):
   """
@@ -447,7 +452,8 @@ class MyConfigParser (object):
     resolve hostnames will fail!
     """
     for sec in me.sections():
-      for key, value in sec.items(resolvep = False):
+      for key in sec.items():
+        value = sec.get(key, resolvep = False)
         for match in RX_RESOLVE.finditer(value):
           me._resolver.prepare(match.group(1))
     me._resolver.run()
@@ -525,15 +531,17 @@ def output(conf, cdb):
       label = sec.name
     else:
       label = 'P%s' % sec.name
-      if sec.has_option('auto') and \
-         sec.get('auto') in ('y', 'yes', 't', 'true', '1', 'on'):
-        auto.append(sec.name)
-      if sec.has_option('user'):
-        cdb.add('U%s' % sec.get('user'))
-    url = M.URLEncode(laxp = True, semip = True)
-    for key, value in sorted(sec.items(), key = lambda (k, v): k):
+      try: a = sec.get('auto')
+      except MissingKeyException: pass
+      else:
+        if a in ('y', 'yes', 't', 'true', '1', 'on'): auto.append(sec.name)
+      try: u = sec.get('user')
+      except MissingKeyException: pass
+      else: cdb.add('U%s' % u)
+    url = M.URLEncode(semip = True)
+    for key in sorted(sec.items()):
       if not key.startswith('@'):
-        url.encode(key, ' '.join(M.split(value)[0]))
+        url.encode(key, sec.get(key))
     cdb.add(label, url.result)
   cdb.add('%AUTO', ' '.join(auto))
   cdb.finish()