chiark / gitweb /
svc/connect.in (Peer.get): Don't apply filter to default value.
[tripe] / peerdb / tripe-newpeers.in
index b8cf68101b1fe0150d1b312bca47e863010dee91..31d1e9f021363b507b33973a8813fd5ca4e96da3 100644 (file)
@@ -48,9 +48,18 @@ class CDBFake (object):
   def finish(me):
     pass
 
+class ExpectedError (Exception): pass
+
 ###--------------------------------------------------------------------------
 ### A bulk DNS resolver.
 
+class ResolverFailure (ExpectedError):
+  def __init__(me, host, msg):
+    me.host = host
+    me.msg = msg
+  def __str__(me):
+    return "failed to resolve `%s': %s" % (me.host, me.msg)
+
 class BulkResolver (object):
   """
   Resolve a number of DNS names in parallel.
@@ -92,7 +101,7 @@ class BulkResolver (object):
     """
     addr = me._namemap[host]
     if addr is None:
-      raise KeyError(host)
+      raise ResolverFailure(host, '(unknown failure)')
     return addr
 
   def _resolved(me, host, addr):
@@ -127,7 +136,7 @@ RX_REF = RX.compile(r'(?x) \$ \( ([^)]+) \)')
 ## Match a $[HOST] name resolution reference; group 1 is the HOST.
 RX_RESOLVE = RX.compile(r'(?x) \$ \[ ([^]]+) \]')
 
-class ConfigSyntaxError (Exception):
+class ConfigSyntaxError (ExpectedError):
   def __init__(me, fname, lno, msg):
     me.fname = fname
     me.lno = lno
@@ -138,7 +147,7 @@ class ConfigSyntaxError (Exception):
 def _fmt_path(path):
   return ' -> '.join(["`%s'" % hop for hop in path])
 
-class AmbiguousOptionError (Exception):
+class AmbiguousOptionError (ExpectedError):
   def __init__(me, key, patha, vala, pathb, valb):
     me.key = key
     me.patha, me.vala = patha, vala
@@ -148,7 +157,7 @@ class AmbiguousOptionError (Exception):
         "path %s yields `%s' but %s yields `%s'" % \
         (me.key, _fmt_path(me.patha), me.vala, _fmt_path(me.pathb), me.valb)
 
-class InheritanceCycleError (Exception):
+class InheritanceCycleError (ExpectedError):
   def __init__(me, key, path):
     me.key = key
     me.path = path
@@ -156,13 +165,13 @@ class InheritanceCycleError (Exception):
     return "Found a cycle %s looking up key `%s'" % \
         (_fmt_path(me.path), me.key)
 
-class MissingSectionException (Exception):
+class MissingSectionException (ExpectedError):
   def __init__(me, sec):
     me.key = key
   def __str__(me):
     return "Section `%s' not found" % (me.sec)
 
-class MissingKeyException (Exception):
+class MissingKeyException (ExpectedError):
   def __init__(me, sec, key):
     me.sec = sec
     me.key = key
@@ -180,8 +189,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,7 +225,14 @@ class ConfigSection (object):
                               string)
     return string
 
-  def _get(me, key, map = None, path = None):
+  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)
+
+  def _get(me, key, path = None):
     """
     Low-level option-fetching method.
 
@@ -211,9 +244,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
@@ -222,36 +253,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.
@@ -262,7 +290,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 +305,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 +322,22 @@ class ConfigSection (object):
     """
 
     ## Initialize for a depth-first walk of the inheritance graph.
-    d = {}
-    visited = {}
-    stack = [me.name]
+    seen = { 'name': True }
+    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
+      for key in sec._itemmap.iterkeys(): seen[key] = None
 
     ## And we're done.
-    return d.iterkeys()
+    return seen.iterkeys()
 
 class MyConfigParser (object):
   """
@@ -519,10 +549,10 @@ def output(conf, cdb):
       try: u = sec.get('user')
       except MissingKeyException: pass
       else: cdb.add('U%s' % u)
-    url = M.URLEncode(laxp = True, semip = True)
+    url = M.URLEncode(semip = True)
     for key in sorted(sec.items()):
       if not key.startswith('@'):
-        url.encode(key, ' '.join(M.split(sec.get(key))[0]))
+        url.encode(key, sec.get(key))
     cdb.add(label, url.result)
   cdb.add('%AUTO', ' '.join(auto))
   cdb.finish()
@@ -534,8 +564,12 @@ def main():
     cdb = CDB.cdbmake(opts.cdbfile, opts.cdbfile + '.new')
   else:
     cdb = CDBFake()
-  conf = getconf(args[1:])
-  output(conf, cdb)
+  try:
+    conf = getconf(args[1:])
+    output(conf, cdb)
+  except ExpectedError, e:
+    M.moan(str(e))
+    exit(2)
 
 if __name__ == '__main__':
   main()