+class MyConfigParser (object):
+ """
+ A more advanced configuration parser.
+
+ This has four major enhancements over the standard ConfigParser which are
+ relevant to us.
+
+ * It recognizes `@inherits' keys and follows them when expanding a
+ value.
+
+ * It recognizes `$(VAR)' references to configuration variables during
+ expansion and processes them correctly.
+
+ * It recognizes `$[HOST]' name-resolver requests and handles them
+ correctly.
+
+ * Its parsing behaviour is well-defined.
+
+ Use:
+
+ 1. Call parse(FILENAME) to slurp in the configuration data.
+
+ 2. Call resolve() to collect the hostnames which need to be resolved and
+ actually do the name resolution.
+
+ 3. Call sections() to get a list of the configuration sections, or
+ section(NAME) to find a named section.
+
+ 4. Call get(ITEM) on a section to collect the results, or items() to
+ iterate over them.
+ """
+
+ def __init__(me):
+ """
+ Initialize a new, empty configuration parser.
+ """
+ me._sectmap = dict()
+ me._resolver = BulkResolver()
+
+ def parse(me, f):
+ """
+ Parse configuration from a file F.
+ """
+
+ ## Initial parser state.
+ sect = None
+ key = None
+ val = None
+ lno = 0
+
+ ## An unpleasant hack. Python makes it hard to capture a value in a
+ ## variable and examine it in a single action, and this is the best that
+ ## I came up with.
+ m = [None]
+ def match(rx): m[0] = rx.match(line); return m[0]
+
+ ## Commit a key's value when we've determined that there are no further
+ ## continuation lines.
+ def flush():
+ if key is not None: sect._itemmap[key] = val.getvalue()
+
+ ## Work through all of the input lines.
+ for line in f:
+ lno += 1
+
+ if match(RX_COMMENT):
+ ## A comment or a blank line. Nothing doing. (This means that we
+ ## leave out blank lines which look like they might be continuation
+ ## lines.)
+
+ pass
+
+ elif match(RX_GRPHDR):
+ ## A section header. Flush out any previous value and set up the new
+ ## group.
+
+ flush()
+ name = m[0].group(1)
+ try: sect = me._sectmap[name]
+ except KeyError: sect = me._sectmap[name] = ConfigSection(name, me)
+ key = None
+
+ elif match(RX_ASSGN):
+ ## A new assignment. Flush out the old one, and set up to store this
+ ## one.
+
+ if sect is None:
+ raise ConfigSyntaxError(f.name, lno, 'no active section to update')
+ flush()
+ key = m[0].group(1)
+ val = StringIO(); val.write(m[0].group(2))
+
+ elif match(RX_CONT):
+ ## A continuation line. Accumulate the value.
+
+ if key is None:
+ raise ConfigSyntaxError(f.name, lno, 'no config value to continue')
+ val.write('\n'); val.write(m[0].group(1))
+
+ else:
+ ## Something else.
+
+ raise ConfigSyntaxError(f.name, lno, 'incomprehensible line')
+
+ ## Don't forget to commit any final value material.
+ flush()
+
+ def section(me, name):
+ """Return a ConfigSection with the given NAME."""
+ try: return me._sectmap[name]
+ except KeyError: raise MissingSectionException(name)
+
+ def sections(me):
+ """Yield the known sections."""
+ return me._sectmap.itervalues()
+
+ def resolve(me):
+ """
+ Works out all of the hostnames which need resolving and resolves them.
+
+ Until you call this, attempts to fetch configuration items which need to
+ resolve hostnames will fail!
+ """
+ for sec in me.sections():
+ for key, value in sec.items(resolvep = False):
+ for match in RX_RESOLVE.finditer(value):
+ me._resolver.prepare(match.group(1))
+ me._resolver.run()
+