config/configobj.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/config/configobj.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2279 @@
     1.4 +# configobj.py
     1.5 +# A config file reader/writer that supports nested sections in config files.
     1.6 +# Copyright (C) 2005-2006 Michael Foord, Nicola Larosa
     1.7 +# E-mail: fuzzyman AT voidspace DOT org DOT uk
     1.8 +#         nico AT tekNico DOT net
     1.9 +
    1.10 +# ConfigObj 4
    1.11 +# http://www.voidspace.org.uk/python/configobj.html
    1.12 +
    1.13 +# Released subject to the BSD License
    1.14 +# Please see http://www.voidspace.org.uk/python/license.shtml
    1.15 +
    1.16 +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
    1.17 +# For information about bugfixes, updates and support, please join the
    1.18 +# ConfigObj mailing list:
    1.19 +# http://lists.sourceforge.net/lists/listinfo/configobj-develop
    1.20 +# Comments, suggestions and bug reports welcome.
    1.21 +
    1.22 +from __future__ import generators
    1.23 +
    1.24 +import sys
    1.25 +INTP_VER = sys.version_info[:2]
    1.26 +if INTP_VER < (2, 2):
    1.27 +    raise RuntimeError("Python v.2.2 or later needed")
    1.28 +
    1.29 +import os, re
    1.30 +compiler = None
    1.31 +try:
    1.32 +    import compiler
    1.33 +except ImportError:
    1.34 +    # for IronPython
    1.35 +    pass
    1.36 +from types import StringTypes
    1.37 +from warnings import warn
    1.38 +try:
    1.39 +    from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
    1.40 +except ImportError:
    1.41 +    # Python 2.2 does not have these
    1.42 +    # UTF-8
    1.43 +    BOM_UTF8 = '\xef\xbb\xbf'
    1.44 +    # UTF-16, little endian
    1.45 +    BOM_UTF16_LE = '\xff\xfe'
    1.46 +    # UTF-16, big endian
    1.47 +    BOM_UTF16_BE = '\xfe\xff'
    1.48 +    if sys.byteorder == 'little':
    1.49 +        # UTF-16, native endianness
    1.50 +        BOM_UTF16 = BOM_UTF16_LE
    1.51 +    else:
    1.52 +        # UTF-16, native endianness
    1.53 +        BOM_UTF16 = BOM_UTF16_BE
    1.54 +
    1.55 +# A dictionary mapping BOM to
    1.56 +# the encoding to decode with, and what to set the
    1.57 +# encoding attribute to.
    1.58 +BOMS = {
    1.59 +    BOM_UTF8: ('utf_8', None),
    1.60 +    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
    1.61 +    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
    1.62 +    BOM_UTF16: ('utf_16', 'utf_16'),
    1.63 +    }
    1.64 +# All legal variants of the BOM codecs.
    1.65 +# TODO: the list of aliases is not meant to be exhaustive, is there a
    1.66 +#   better way ?
    1.67 +BOM_LIST = {
    1.68 +    'utf_16': 'utf_16',
    1.69 +    'u16': 'utf_16',
    1.70 +    'utf16': 'utf_16',
    1.71 +    'utf-16': 'utf_16',
    1.72 +    'utf16_be': 'utf16_be',
    1.73 +    'utf_16_be': 'utf16_be',
    1.74 +    'utf-16be': 'utf16_be',
    1.75 +    'utf16_le': 'utf16_le',
    1.76 +    'utf_16_le': 'utf16_le',
    1.77 +    'utf-16le': 'utf16_le',
    1.78 +    'utf_8': 'utf_8',
    1.79 +    'u8': 'utf_8',
    1.80 +    'utf': 'utf_8',
    1.81 +    'utf8': 'utf_8',
    1.82 +    'utf-8': 'utf_8',
    1.83 +    }
    1.84 +
    1.85 +# Map of encodings to the BOM to write.
    1.86 +BOM_SET = {
    1.87 +    'utf_8': BOM_UTF8,
    1.88 +    'utf_16': BOM_UTF16,
    1.89 +    'utf16_be': BOM_UTF16_BE,
    1.90 +    'utf16_le': BOM_UTF16_LE,
    1.91 +    None: BOM_UTF8
    1.92 +    }
    1.93 +
    1.94 +try:
    1.95 +    from validate import VdtMissingValue
    1.96 +except ImportError:
    1.97 +    VdtMissingValue = None
    1.98 +
    1.99 +try:
   1.100 +    enumerate
   1.101 +except NameError:
   1.102 +    def enumerate(obj):
   1.103 +        """enumerate for Python 2.2."""
   1.104 +        i = -1
   1.105 +        for item in obj:
   1.106 +            i += 1
   1.107 +            yield i, item
   1.108 +
   1.109 +try:
   1.110 +    True, False
   1.111 +except NameError:
   1.112 +    True, False = 1, 0
   1.113 +
   1.114 +
   1.115 +__version__ = '4.4.0'
   1.116 +
   1.117 +__revision__ = '$Id: configobj.py,v 3.5 2007/07/02 18:20:24 benjamin%smedbergs.us Exp $'
   1.118 +
   1.119 +__docformat__ = "restructuredtext en"
   1.120 +
   1.121 +__all__ = (
   1.122 +    '__version__',
   1.123 +    'DEFAULT_INDENT_TYPE',
   1.124 +    'DEFAULT_INTERPOLATION',
   1.125 +    'ConfigObjError',
   1.126 +    'NestingError',
   1.127 +    'ParseError',
   1.128 +    'DuplicateError',
   1.129 +    'ConfigspecError',
   1.130 +    'ConfigObj',
   1.131 +    'SimpleVal',
   1.132 +    'InterpolationError',
   1.133 +    'InterpolationLoopError',
   1.134 +    'MissingInterpolationOption',
   1.135 +    'RepeatSectionError',
   1.136 +    'UnreprError',
   1.137 +    'UnknownType',
   1.138 +    '__docformat__',
   1.139 +    'flatten_errors',
   1.140 +)
   1.141 +
   1.142 +DEFAULT_INTERPOLATION = 'configparser'
   1.143 +DEFAULT_INDENT_TYPE = '    '
   1.144 +MAX_INTERPOL_DEPTH = 10
   1.145 +
   1.146 +OPTION_DEFAULTS = {
   1.147 +    'interpolation': True,
   1.148 +    'raise_errors': False,
   1.149 +    'list_values': True,
   1.150 +    'create_empty': False,
   1.151 +    'file_error': False,
   1.152 +    'configspec': None,
   1.153 +    'stringify': True,
   1.154 +    # option may be set to one of ('', ' ', '\t')
   1.155 +    'indent_type': None,
   1.156 +    'encoding': None,
   1.157 +    'default_encoding': None,
   1.158 +    'unrepr': False,
   1.159 +    'write_empty_values': False,
   1.160 +}
   1.161 +
   1.162 +
   1.163 +def getObj(s):
   1.164 +    s = "a=" + s
   1.165 +    if compiler is None:
   1.166 +        raise ImportError('compiler module not available')
   1.167 +    p = compiler.parse(s)
   1.168 +    return p.getChildren()[1].getChildren()[0].getChildren()[1]
   1.169 +
   1.170 +class UnknownType(Exception):
   1.171 +    pass
   1.172 +
   1.173 +class Builder:
   1.174 +    
   1.175 +    def build(self, o):
   1.176 +        m = getattr(self, 'build_' + o.__class__.__name__, None)
   1.177 +        if m is None:
   1.178 +            raise UnknownType(o.__class__.__name__)
   1.179 +        return m(o)
   1.180 +    
   1.181 +    def build_List(self, o):
   1.182 +        return map(self.build, o.getChildren())
   1.183 +    
   1.184 +    def build_Const(self, o):
   1.185 +        return o.value
   1.186 +    
   1.187 +    def build_Dict(self, o):
   1.188 +        d = {}
   1.189 +        i = iter(map(self.build, o.getChildren()))
   1.190 +        for el in i:
   1.191 +            d[el] = i.next()
   1.192 +        return d
   1.193 +    
   1.194 +    def build_Tuple(self, o):
   1.195 +        return tuple(self.build_List(o))
   1.196 +    
   1.197 +    def build_Name(self, o):
   1.198 +        if o.name == 'None':
   1.199 +            return None
   1.200 +        if o.name == 'True':
   1.201 +            return True
   1.202 +        if o.name == 'False':
   1.203 +            return False
   1.204 +        
   1.205 +        # An undefinted Name
   1.206 +        raise UnknownType('Undefined Name')
   1.207 +    
   1.208 +    def build_Add(self, o):
   1.209 +        real, imag = map(self.build_Const, o.getChildren())
   1.210 +        try:
   1.211 +            real = float(real)
   1.212 +        except TypeError:
   1.213 +            raise UnknownType('Add')
   1.214 +        if not isinstance(imag, complex) or imag.real != 0.0:
   1.215 +            raise UnknownType('Add')
   1.216 +        return real+imag
   1.217 +    
   1.218 +    def build_Getattr(self, o):
   1.219 +        parent = self.build(o.expr)
   1.220 +        return getattr(parent, o.attrname)
   1.221 +    
   1.222 +    def build_UnarySub(self, o):
   1.223 +        return -self.build_Const(o.getChildren()[0])
   1.224 +    
   1.225 +    def build_UnaryAdd(self, o):
   1.226 +        return self.build_Const(o.getChildren()[0])
   1.227 +
   1.228 +def unrepr(s):
   1.229 +    if not s:
   1.230 +        return s
   1.231 +    return Builder().build(getObj(s))
   1.232 +
   1.233 +def _splitlines(instring):
   1.234 +    """Split a string on lines, without losing line endings or truncating."""
   1.235 +    
   1.236 +
   1.237 +class ConfigObjError(SyntaxError):
   1.238 +    """
   1.239 +    This is the base class for all errors that ConfigObj raises.
   1.240 +    It is a subclass of SyntaxError.
   1.241 +    """
   1.242 +    def __init__(self, message='', line_number=None, line=''):
   1.243 +        self.line = line
   1.244 +        self.line_number = line_number
   1.245 +        self.message = message
   1.246 +        SyntaxError.__init__(self, message)
   1.247 +
   1.248 +class NestingError(ConfigObjError):
   1.249 +    """
   1.250 +    This error indicates a level of nesting that doesn't match.
   1.251 +    """
   1.252 +
   1.253 +class ParseError(ConfigObjError):
   1.254 +    """
   1.255 +    This error indicates that a line is badly written.
   1.256 +    It is neither a valid ``key = value`` line,
   1.257 +    nor a valid section marker line.
   1.258 +    """
   1.259 +
   1.260 +class DuplicateError(ConfigObjError):
   1.261 +    """
   1.262 +    The keyword or section specified already exists.
   1.263 +    """
   1.264 +
   1.265 +class ConfigspecError(ConfigObjError):
   1.266 +    """
   1.267 +    An error occurred whilst parsing a configspec.
   1.268 +    """
   1.269 +
   1.270 +class InterpolationError(ConfigObjError):
   1.271 +    """Base class for the two interpolation errors."""
   1.272 +
   1.273 +class InterpolationLoopError(InterpolationError):
   1.274 +    """Maximum interpolation depth exceeded in string interpolation."""
   1.275 +
   1.276 +    def __init__(self, option):
   1.277 +        InterpolationError.__init__(
   1.278 +            self,
   1.279 +            'interpolation loop detected in value "%s".' % option)
   1.280 +
   1.281 +class RepeatSectionError(ConfigObjError):
   1.282 +    """
   1.283 +    This error indicates additional sections in a section with a
   1.284 +    ``__many__`` (repeated) section.
   1.285 +    """
   1.286 +
   1.287 +class MissingInterpolationOption(InterpolationError):
   1.288 +    """A value specified for interpolation was missing."""
   1.289 +
   1.290 +    def __init__(self, option):
   1.291 +        InterpolationError.__init__(
   1.292 +            self,
   1.293 +            'missing option "%s" in interpolation.' % option)
   1.294 +
   1.295 +class UnreprError(ConfigObjError):
   1.296 +    """An error parsing in unrepr mode."""
   1.297 +
   1.298 +
   1.299 +class InterpolationEngine(object):
   1.300 +    """
   1.301 +    A helper class to help perform string interpolation.
   1.302 +
   1.303 +    This class is an abstract base class; its descendants perform
   1.304 +    the actual work.
   1.305 +    """
   1.306 +
   1.307 +    # compiled regexp to use in self.interpolate()
   1.308 +    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
   1.309 +
   1.310 +    def __init__(self, section):
   1.311 +        # the Section instance that "owns" this engine
   1.312 +        self.section = section
   1.313 +
   1.314 +    def interpolate(self, key, value):
   1.315 +        def recursive_interpolate(key, value, section, backtrail):
   1.316 +            """The function that does the actual work.
   1.317 +
   1.318 +            ``value``: the string we're trying to interpolate.
   1.319 +            ``section``: the section in which that string was found
   1.320 +            ``backtrail``: a dict to keep track of where we've been,
   1.321 +            to detect and prevent infinite recursion loops
   1.322 +
   1.323 +            This is similar to a depth-first-search algorithm.
   1.324 +            """
   1.325 +            # Have we been here already?
   1.326 +            if backtrail.has_key((key, section.name)):
   1.327 +                # Yes - infinite loop detected
   1.328 +                raise InterpolationLoopError(key)
   1.329 +            # Place a marker on our backtrail so we won't come back here again
   1.330 +            backtrail[(key, section.name)] = 1
   1.331 +
   1.332 +            # Now start the actual work
   1.333 +            match = self._KEYCRE.search(value)
   1.334 +            while match:
   1.335 +                # The actual parsing of the match is implementation-dependent,
   1.336 +                # so delegate to our helper function
   1.337 +                k, v, s = self._parse_match(match)
   1.338 +                if k is None:
   1.339 +                    # That's the signal that no further interpolation is needed
   1.340 +                    replacement = v
   1.341 +                else:
   1.342 +                    # Further interpolation may be needed to obtain final value
   1.343 +                    replacement = recursive_interpolate(k, v, s, backtrail)
   1.344 +                # Replace the matched string with its final value
   1.345 +                start, end = match.span()
   1.346 +                value = ''.join((value[:start], replacement, value[end:]))
   1.347 +                new_search_start = start + len(replacement)
   1.348 +                # Pick up the next interpolation key, if any, for next time
   1.349 +                # through the while loop
   1.350 +                match = self._KEYCRE.search(value, new_search_start)
   1.351 +
   1.352 +            # Now safe to come back here again; remove marker from backtrail
   1.353 +            del backtrail[(key, section.name)]
   1.354 +
   1.355 +            return value
   1.356 +
   1.357 +        # Back in interpolate(), all we have to do is kick off the recursive
   1.358 +        # function with appropriate starting values
   1.359 +        value = recursive_interpolate(key, value, self.section, {})
   1.360 +        return value
   1.361 +
   1.362 +    def _fetch(self, key):
   1.363 +        """Helper function to fetch values from owning section.
   1.364 +
   1.365 +        Returns a 2-tuple: the value, and the section where it was found.
   1.366 +        """
   1.367 +        # switch off interpolation before we try and fetch anything !
   1.368 +        save_interp = self.section.main.interpolation
   1.369 +        self.section.main.interpolation = False
   1.370 +
   1.371 +        # Start at section that "owns" this InterpolationEngine
   1.372 +        current_section = self.section
   1.373 +        while True:
   1.374 +            # try the current section first
   1.375 +            val = current_section.get(key)
   1.376 +            if val is not None:
   1.377 +                break
   1.378 +            # try "DEFAULT" next
   1.379 +            val = current_section.get('DEFAULT', {}).get(key)
   1.380 +            if val is not None:
   1.381 +                break
   1.382 +            # move up to parent and try again
   1.383 +            # top-level's parent is itself
   1.384 +            if current_section.parent is current_section:
   1.385 +                # reached top level, time to give up
   1.386 +                break
   1.387 +            current_section = current_section.parent
   1.388 +
   1.389 +        # restore interpolation to previous value before returning
   1.390 +        self.section.main.interpolation = save_interp
   1.391 +        if val is None:
   1.392 +            raise MissingInterpolationOption(key)
   1.393 +        return val, current_section
   1.394 +
   1.395 +    def _parse_match(self, match):
   1.396 +        """Implementation-dependent helper function.
   1.397 +
   1.398 +        Will be passed a match object corresponding to the interpolation
   1.399 +        key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
   1.400 +        key in the appropriate config file section (using the ``_fetch()``
   1.401 +        helper function) and return a 3-tuple: (key, value, section)
   1.402 +
   1.403 +        ``key`` is the name of the key we're looking for
   1.404 +        ``value`` is the value found for that key
   1.405 +        ``section`` is a reference to the section where it was found
   1.406 +
   1.407 +        ``key`` and ``section`` should be None if no further
   1.408 +        interpolation should be performed on the resulting value
   1.409 +        (e.g., if we interpolated "$$" and returned "$").
   1.410 +        """
   1.411 +        raise NotImplementedError
   1.412 +    
   1.413 +
   1.414 +class ConfigParserInterpolation(InterpolationEngine):
   1.415 +    """Behaves like ConfigParser."""
   1.416 +    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
   1.417 +
   1.418 +    def _parse_match(self, match):
   1.419 +        key = match.group(1)
   1.420 +        value, section = self._fetch(key)
   1.421 +        return key, value, section
   1.422 +
   1.423 +
   1.424 +class TemplateInterpolation(InterpolationEngine):
   1.425 +    """Behaves like string.Template."""
   1.426 +    _delimiter = '$'
   1.427 +    _KEYCRE = re.compile(r"""
   1.428 +        \$(?:
   1.429 +          (?P<escaped>\$)              |   # Two $ signs
   1.430 +          (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
   1.431 +          {(?P<braced>[^}]*)}              # ${name} format
   1.432 +        )
   1.433 +        """, re.IGNORECASE | re.VERBOSE)
   1.434 +
   1.435 +    def _parse_match(self, match):
   1.436 +        # Valid name (in or out of braces): fetch value from section
   1.437 +        key = match.group('named') or match.group('braced')
   1.438 +        if key is not None:
   1.439 +            value, section = self._fetch(key)
   1.440 +            return key, value, section
   1.441 +        # Escaped delimiter (e.g., $$): return single delimiter
   1.442 +        if match.group('escaped') is not None:
   1.443 +            # Return None for key and section to indicate it's time to stop
   1.444 +            return None, self._delimiter, None
   1.445 +        # Anything else: ignore completely, just return it unchanged
   1.446 +        return None, match.group(), None
   1.447 +
   1.448 +interpolation_engines = {
   1.449 +    'configparser': ConfigParserInterpolation,
   1.450 +    'template': TemplateInterpolation,
   1.451 +}
   1.452 +
   1.453 +class Section(dict):
   1.454 +    """
   1.455 +    A dictionary-like object that represents a section in a config file.
   1.456 +    
   1.457 +    It does string interpolation if the 'interpolation' attribute
   1.458 +    of the 'main' object is set to True.
   1.459 +    
   1.460 +    Interpolation is tried first from this object, then from the 'DEFAULT'
   1.461 +    section of this object, next from the parent and its 'DEFAULT' section,
   1.462 +    and so on until the main object is reached.
   1.463 +    
   1.464 +    A Section will behave like an ordered dictionary - following the
   1.465 +    order of the ``scalars`` and ``sections`` attributes.
   1.466 +    You can use this to change the order of members.
   1.467 +    
   1.468 +    Iteration follows the order: scalars, then sections.
   1.469 +    """
   1.470 +
   1.471 +    def __init__(self, parent, depth, main, indict=None, name=None):
   1.472 +        """
   1.473 +        * parent is the section above
   1.474 +        * depth is the depth level of this section
   1.475 +        * main is the main ConfigObj
   1.476 +        * indict is a dictionary to initialise the section with
   1.477 +        """
   1.478 +        if indict is None:
   1.479 +            indict = {}
   1.480 +        dict.__init__(self)
   1.481 +        # used for nesting level *and* interpolation
   1.482 +        self.parent = parent
   1.483 +        # used for the interpolation attribute
   1.484 +        self.main = main
   1.485 +        # level of nesting depth of this Section
   1.486 +        self.depth = depth
   1.487 +        # the sequence of scalar values in this Section
   1.488 +        self.scalars = []
   1.489 +        # the sequence of sections in this Section
   1.490 +        self.sections = []
   1.491 +        # purely for information
   1.492 +        self.name = name
   1.493 +        # for comments :-)
   1.494 +        self.comments = {}
   1.495 +        self.inline_comments = {}
   1.496 +        # for the configspec
   1.497 +        self.configspec = {}
   1.498 +        self._order = []
   1.499 +        self._configspec_comments = {}
   1.500 +        self._configspec_inline_comments = {}
   1.501 +        self._cs_section_comments = {}
   1.502 +        self._cs_section_inline_comments = {}
   1.503 +        # for defaults
   1.504 +        self.defaults = []
   1.505 +        #
   1.506 +        # we do this explicitly so that __setitem__ is used properly
   1.507 +        # (rather than just passing to ``dict.__init__``)
   1.508 +        for entry in indict:
   1.509 +            self[entry] = indict[entry]
   1.510 +
   1.511 +    def _interpolate(self, key, value):
   1.512 +        try:
   1.513 +            # do we already have an interpolation engine?
   1.514 +            engine = self._interpolation_engine
   1.515 +        except AttributeError:
   1.516 +            # not yet: first time running _interpolate(), so pick the engine
   1.517 +            name = self.main.interpolation
   1.518 +            if name == True:  # note that "if name:" would be incorrect here
   1.519 +                # backwards-compatibility: interpolation=True means use default
   1.520 +                name = DEFAULT_INTERPOLATION
   1.521 +            name = name.lower()  # so that "Template", "template", etc. all work
   1.522 +            class_ = interpolation_engines.get(name, None)
   1.523 +            if class_ is None:
   1.524 +                # invalid value for self.main.interpolation
   1.525 +                self.main.interpolation = False
   1.526 +                return value
   1.527 +            else:
   1.528 +                # save reference to engine so we don't have to do this again
   1.529 +                engine = self._interpolation_engine = class_(self)
   1.530 +        # let the engine do the actual work
   1.531 +        return engine.interpolate(key, value)
   1.532 +
   1.533 +    def __getitem__(self, key):
   1.534 +        """Fetch the item and do string interpolation."""
   1.535 +        val = dict.__getitem__(self, key)
   1.536 +        if self.main.interpolation and isinstance(val, StringTypes):
   1.537 +            return self._interpolate(key, val)
   1.538 +        return val
   1.539 +
   1.540 +    def __setitem__(self, key, value, unrepr=False):
   1.541 +        """
   1.542 +        Correctly set a value.
   1.543 +        
   1.544 +        Making dictionary values Section instances.
   1.545 +        (We have to special case 'Section' instances - which are also dicts)
   1.546 +        
   1.547 +        Keys must be strings.
   1.548 +        Values need only be strings (or lists of strings) if
   1.549 +        ``main.stringify`` is set.
   1.550 +        
   1.551 +        `unrepr`` must be set when setting a value to a dictionary, without
   1.552 +        creating a new sub-section.
   1.553 +        """
   1.554 +        if not isinstance(key, StringTypes):
   1.555 +            raise ValueError, 'The key "%s" is not a string.' % key
   1.556 +        # add the comment
   1.557 +        if not self.comments.has_key(key):
   1.558 +            self.comments[key] = []
   1.559 +            self.inline_comments[key] = ''
   1.560 +        # remove the entry from defaults
   1.561 +        if key in self.defaults:
   1.562 +            self.defaults.remove(key)
   1.563 +        #
   1.564 +        if isinstance(value, Section):
   1.565 +            if not self.has_key(key):
   1.566 +                self.sections.append(key)
   1.567 +            dict.__setitem__(self, key, value)
   1.568 +        elif isinstance(value, dict) and not unrepr:
   1.569 +            # First create the new depth level,
   1.570 +            # then create the section
   1.571 +            if not self.has_key(key):
   1.572 +                self.sections.append(key)
   1.573 +            new_depth = self.depth + 1
   1.574 +            dict.__setitem__(
   1.575 +                self,
   1.576 +                key,
   1.577 +                Section(
   1.578 +                    self,
   1.579 +                    new_depth,
   1.580 +                    self.main,
   1.581 +                    indict=value,
   1.582 +                    name=key))
   1.583 +        else:
   1.584 +            if not self.has_key(key):
   1.585 +                self.scalars.append(key)
   1.586 +            if not self.main.stringify:
   1.587 +                if isinstance(value, StringTypes):
   1.588 +                    pass
   1.589 +                elif isinstance(value, (list, tuple)):
   1.590 +                    for entry in value:
   1.591 +                        if not isinstance(entry, StringTypes):
   1.592 +                            raise TypeError, (
   1.593 +                                'Value is not a string "%s".' % entry)
   1.594 +                else:
   1.595 +                    raise TypeError, 'Value is not a string "%s".' % value
   1.596 +            dict.__setitem__(self, key, value)
   1.597 +
   1.598 +    def __delitem__(self, key):
   1.599 +        """Remove items from the sequence when deleting."""
   1.600 +        dict. __delitem__(self, key)
   1.601 +        if key in self.scalars:
   1.602 +            self.scalars.remove(key)
   1.603 +        else:
   1.604 +            self.sections.remove(key)
   1.605 +        del self.comments[key]
   1.606 +        del self.inline_comments[key]
   1.607 +
   1.608 +    def get(self, key, default=None):
   1.609 +        """A version of ``get`` that doesn't bypass string interpolation."""
   1.610 +        try:
   1.611 +            return self[key]
   1.612 +        except KeyError:
   1.613 +            return default
   1.614 +
   1.615 +    def update(self, indict):
   1.616 +        """
   1.617 +        A version of update that uses our ``__setitem__``.
   1.618 +        """
   1.619 +        for entry in indict:
   1.620 +            self[entry] = indict[entry]
   1.621 +
   1.622 +    def pop(self, key, *args):
   1.623 +        """ """
   1.624 +        val = dict.pop(self, key, *args)
   1.625 +        if key in self.scalars:
   1.626 +            del self.comments[key]
   1.627 +            del self.inline_comments[key]
   1.628 +            self.scalars.remove(key)
   1.629 +        elif key in self.sections:
   1.630 +            del self.comments[key]
   1.631 +            del self.inline_comments[key]
   1.632 +            self.sections.remove(key)
   1.633 +        if self.main.interpolation and isinstance(val, StringTypes):
   1.634 +            return self._interpolate(key, val)
   1.635 +        return val
   1.636 +
   1.637 +    def popitem(self):
   1.638 +        """Pops the first (key,val)"""
   1.639 +        sequence = (self.scalars + self.sections)
   1.640 +        if not sequence:
   1.641 +            raise KeyError, ": 'popitem(): dictionary is empty'"
   1.642 +        key = sequence[0]
   1.643 +        val =  self[key]
   1.644 +        del self[key]
   1.645 +        return key, val
   1.646 +
   1.647 +    def clear(self):
   1.648 +        """
   1.649 +        A version of clear that also affects scalars/sections
   1.650 +        Also clears comments and configspec.
   1.651 +        
   1.652 +        Leaves other attributes alone :
   1.653 +            depth/main/parent are not affected
   1.654 +        """
   1.655 +        dict.clear(self)
   1.656 +        self.scalars = []
   1.657 +        self.sections = []
   1.658 +        self.comments = {}
   1.659 +        self.inline_comments = {}
   1.660 +        self.configspec = {}
   1.661 +
   1.662 +    def setdefault(self, key, default=None):
   1.663 +        """A version of setdefault that sets sequence if appropriate."""
   1.664 +        try:
   1.665 +            return self[key]
   1.666 +        except KeyError:
   1.667 +            self[key] = default
   1.668 +            return self[key]
   1.669 +
   1.670 +    def items(self):
   1.671 +        """ """
   1.672 +        return zip((self.scalars + self.sections), self.values())
   1.673 +
   1.674 +    def keys(self):
   1.675 +        """ """
   1.676 +        return (self.scalars + self.sections)
   1.677 +
   1.678 +    def values(self):
   1.679 +        """ """
   1.680 +        return [self[key] for key in (self.scalars + self.sections)]
   1.681 +
   1.682 +    def iteritems(self):
   1.683 +        """ """
   1.684 +        return iter(self.items())
   1.685 +
   1.686 +    def iterkeys(self):
   1.687 +        """ """
   1.688 +        return iter((self.scalars + self.sections))
   1.689 +
   1.690 +    __iter__ = iterkeys
   1.691 +
   1.692 +    def itervalues(self):
   1.693 +        """ """
   1.694 +        return iter(self.values())
   1.695 +
   1.696 +    def __repr__(self):
   1.697 +        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
   1.698 +            for key in (self.scalars + self.sections)])
   1.699 +
   1.700 +    __str__ = __repr__
   1.701 +
   1.702 +    # Extra methods - not in a normal dictionary
   1.703 +
   1.704 +    def dict(self):
   1.705 +        """
   1.706 +        Return a deepcopy of self as a dictionary.
   1.707 +        
   1.708 +        All members that are ``Section`` instances are recursively turned to
   1.709 +        ordinary dictionaries - by calling their ``dict`` method.
   1.710 +        
   1.711 +        >>> n = a.dict()
   1.712 +        >>> n == a
   1.713 +        1
   1.714 +        >>> n is a
   1.715 +        0
   1.716 +        """
   1.717 +        newdict = {}
   1.718 +        for entry in self:
   1.719 +            this_entry = self[entry]
   1.720 +            if isinstance(this_entry, Section):
   1.721 +                this_entry = this_entry.dict()
   1.722 +            elif isinstance(this_entry, list):
   1.723 +                # create a copy rather than a reference
   1.724 +                this_entry = list(this_entry)
   1.725 +            elif isinstance(this_entry, tuple):
   1.726 +                # create a copy rather than a reference
   1.727 +                this_entry = tuple(this_entry)
   1.728 +            newdict[entry] = this_entry
   1.729 +        return newdict
   1.730 +
   1.731 +    def merge(self, indict):
   1.732 +        """
   1.733 +        A recursive update - useful for merging config files.
   1.734 +        
   1.735 +        >>> a = '''[section1]
   1.736 +        ...     option1 = True
   1.737 +        ...     [[subsection]]
   1.738 +        ...     more_options = False
   1.739 +        ...     # end of file'''.splitlines()
   1.740 +        >>> b = '''# File is user.ini
   1.741 +        ...     [section1]
   1.742 +        ...     option1 = False
   1.743 +        ...     # end of file'''.splitlines()
   1.744 +        >>> c1 = ConfigObj(b)
   1.745 +        >>> c2 = ConfigObj(a)
   1.746 +        >>> c2.merge(c1)
   1.747 +        >>> c2
   1.748 +        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
   1.749 +        """
   1.750 +        for key, val in indict.items():
   1.751 +            if (key in self and isinstance(self[key], dict) and
   1.752 +                                isinstance(val, dict)):
   1.753 +                self[key].merge(val)
   1.754 +            else:   
   1.755 +                self[key] = val
   1.756 +
   1.757 +    def rename(self, oldkey, newkey):
   1.758 +        """
   1.759 +        Change a keyname to another, without changing position in sequence.
   1.760 +        
   1.761 +        Implemented so that transformations can be made on keys,
   1.762 +        as well as on values. (used by encode and decode)
   1.763 +        
   1.764 +        Also renames comments.
   1.765 +        """
   1.766 +        if oldkey in self.scalars:
   1.767 +            the_list = self.scalars
   1.768 +        elif oldkey in self.sections:
   1.769 +            the_list = self.sections
   1.770 +        else:
   1.771 +            raise KeyError, 'Key "%s" not found.' % oldkey
   1.772 +        pos = the_list.index(oldkey)
   1.773 +        #
   1.774 +        val = self[oldkey]
   1.775 +        dict.__delitem__(self, oldkey)
   1.776 +        dict.__setitem__(self, newkey, val)
   1.777 +        the_list.remove(oldkey)
   1.778 +        the_list.insert(pos, newkey)
   1.779 +        comm = self.comments[oldkey]
   1.780 +        inline_comment = self.inline_comments[oldkey]
   1.781 +        del self.comments[oldkey]
   1.782 +        del self.inline_comments[oldkey]
   1.783 +        self.comments[newkey] = comm
   1.784 +        self.inline_comments[newkey] = inline_comment
   1.785 +
   1.786 +    def walk(self, function, raise_errors=True,
   1.787 +            call_on_sections=False, **keywargs):
   1.788 +        """
   1.789 +        Walk every member and call a function on the keyword and value.
   1.790 +        
   1.791 +        Return a dictionary of the return values
   1.792 +        
   1.793 +        If the function raises an exception, raise the errror
   1.794 +        unless ``raise_errors=False``, in which case set the return value to
   1.795 +        ``False``.
   1.796 +        
   1.797 +        Any unrecognised keyword arguments you pass to walk, will be pased on
   1.798 +        to the function you pass in.
   1.799 +        
   1.800 +        Note: if ``call_on_sections`` is ``True`` then - on encountering a
   1.801 +        subsection, *first* the function is called for the *whole* subsection,
   1.802 +        and then recurses into its members. This means your function must be
   1.803 +        able to handle strings, dictionaries and lists. This allows you
   1.804 +        to change the key of subsections as well as for ordinary members. The
   1.805 +        return value when called on the whole subsection has to be discarded.
   1.806 +        
   1.807 +        See  the encode and decode methods for examples, including functions.
   1.808 +        
   1.809 +        .. caution::
   1.810 +        
   1.811 +            You can use ``walk`` to transform the names of members of a section
   1.812 +            but you mustn't add or delete members.
   1.813 +        
   1.814 +        >>> config = '''[XXXXsection]
   1.815 +        ... XXXXkey = XXXXvalue'''.splitlines()
   1.816 +        >>> cfg = ConfigObj(config)
   1.817 +        >>> cfg
   1.818 +        {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
   1.819 +        >>> def transform(section, key):
   1.820 +        ...     val = section[key]
   1.821 +        ...     newkey = key.replace('XXXX', 'CLIENT1')
   1.822 +        ...     section.rename(key, newkey)
   1.823 +        ...     if isinstance(val, (tuple, list, dict)):
   1.824 +        ...         pass
   1.825 +        ...     else:
   1.826 +        ...         val = val.replace('XXXX', 'CLIENT1')
   1.827 +        ...         section[newkey] = val
   1.828 +        >>> cfg.walk(transform, call_on_sections=True)
   1.829 +        {'CLIENT1section': {'CLIENT1key': None}}
   1.830 +        >>> cfg
   1.831 +        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
   1.832 +        """
   1.833 +        out = {}
   1.834 +        # scalars first
   1.835 +        for i in range(len(self.scalars)):
   1.836 +            entry = self.scalars[i]
   1.837 +            try:
   1.838 +                val = function(self, entry, **keywargs)
   1.839 +                # bound again in case name has changed
   1.840 +                entry = self.scalars[i]
   1.841 +                out[entry] = val
   1.842 +            except Exception:
   1.843 +                if raise_errors:
   1.844 +                    raise
   1.845 +                else:
   1.846 +                    entry = self.scalars[i]
   1.847 +                    out[entry] = False
   1.848 +        # then sections
   1.849 +        for i in range(len(self.sections)):
   1.850 +            entry = self.sections[i]
   1.851 +            if call_on_sections:
   1.852 +                try:
   1.853 +                    function(self, entry, **keywargs)
   1.854 +                except Exception:
   1.855 +                    if raise_errors:
   1.856 +                        raise
   1.857 +                    else:
   1.858 +                        entry = self.sections[i]
   1.859 +                        out[entry] = False
   1.860 +                # bound again in case name has changed
   1.861 +                entry = self.sections[i]
   1.862 +            # previous result is discarded
   1.863 +            out[entry] = self[entry].walk(
   1.864 +                function,
   1.865 +                raise_errors=raise_errors,
   1.866 +                call_on_sections=call_on_sections,
   1.867 +                **keywargs)
   1.868 +        return out
   1.869 +
   1.870 +    def decode(self, encoding):
   1.871 +        """
   1.872 +        Decode all strings and values to unicode, using the specified encoding.
   1.873 +        
   1.874 +        Works with subsections and list values.
   1.875 +        
   1.876 +        Uses the ``walk`` method.
   1.877 +        
   1.878 +        Testing ``encode`` and ``decode``.
   1.879 +        >>> m = ConfigObj(a)
   1.880 +        >>> m.decode('ascii')
   1.881 +        >>> def testuni(val):
   1.882 +        ...     for entry in val:
   1.883 +        ...         if not isinstance(entry, unicode):
   1.884 +        ...             print >> sys.stderr, type(entry)
   1.885 +        ...             raise AssertionError, 'decode failed.'
   1.886 +        ...         if isinstance(val[entry], dict):
   1.887 +        ...             testuni(val[entry])
   1.888 +        ...         elif not isinstance(val[entry], unicode):
   1.889 +        ...             raise AssertionError, 'decode failed.'
   1.890 +        >>> testuni(m)
   1.891 +        >>> m.encode('ascii')
   1.892 +        >>> a == m
   1.893 +        1
   1.894 +        """
   1.895 +        warn('use of ``decode`` is deprecated.', DeprecationWarning)
   1.896 +        def decode(section, key, encoding=encoding, warn=True):
   1.897 +            """ """
   1.898 +            val = section[key]
   1.899 +            if isinstance(val, (list, tuple)):
   1.900 +                newval = []
   1.901 +                for entry in val:
   1.902 +                    newval.append(entry.decode(encoding))
   1.903 +            elif isinstance(val, dict):
   1.904 +                newval = val
   1.905 +            else:
   1.906 +                newval = val.decode(encoding)
   1.907 +            newkey = key.decode(encoding)
   1.908 +            section.rename(key, newkey)
   1.909 +            section[newkey] = newval
   1.910 +        # using ``call_on_sections`` allows us to modify section names
   1.911 +        self.walk(decode, call_on_sections=True)
   1.912 +
   1.913 +    def encode(self, encoding):
   1.914 +        """
   1.915 +        Encode all strings and values from unicode,
   1.916 +        using the specified encoding.
   1.917 +        
   1.918 +        Works with subsections and list values.
   1.919 +        Uses the ``walk`` method.
   1.920 +        """
   1.921 +        warn('use of ``encode`` is deprecated.', DeprecationWarning)
   1.922 +        def encode(section, key, encoding=encoding):
   1.923 +            """ """
   1.924 +            val = section[key]
   1.925 +            if isinstance(val, (list, tuple)):
   1.926 +                newval = []
   1.927 +                for entry in val:
   1.928 +                    newval.append(entry.encode(encoding))
   1.929 +            elif isinstance(val, dict):
   1.930 +                newval = val
   1.931 +            else:
   1.932 +                newval = val.encode(encoding)
   1.933 +            newkey = key.encode(encoding)
   1.934 +            section.rename(key, newkey)
   1.935 +            section[newkey] = newval
   1.936 +        self.walk(encode, call_on_sections=True)
   1.937 +
   1.938 +    def istrue(self, key):
   1.939 +        """A deprecated version of ``as_bool``."""
   1.940 +        warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
   1.941 +                'instead.', DeprecationWarning)
   1.942 +        return self.as_bool(key)
   1.943 +
   1.944 +    def as_bool(self, key):
   1.945 +        """
   1.946 +        Accepts a key as input. The corresponding value must be a string or
   1.947 +        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
   1.948 +        retain compatibility with Python 2.2.
   1.949 +        
   1.950 +        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
   1.951 +        ``True``.
   1.952 +        
   1.953 +        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
   1.954 +        ``False``.
   1.955 +        
   1.956 +        ``as_bool`` is not case sensitive.
   1.957 +        
   1.958 +        Any other input will raise a ``ValueError``.
   1.959 +        
   1.960 +        >>> a = ConfigObj()
   1.961 +        >>> a['a'] = 'fish'
   1.962 +        >>> a.as_bool('a')
   1.963 +        Traceback (most recent call last):
   1.964 +        ValueError: Value "fish" is neither True nor False
   1.965 +        >>> a['b'] = 'True'
   1.966 +        >>> a.as_bool('b')
   1.967 +        1
   1.968 +        >>> a['b'] = 'off'
   1.969 +        >>> a.as_bool('b')
   1.970 +        0
   1.971 +        """
   1.972 +        val = self[key]
   1.973 +        if val == True:
   1.974 +            return True
   1.975 +        elif val == False:
   1.976 +            return False
   1.977 +        else:
   1.978 +            try:
   1.979 +                if not isinstance(val, StringTypes):
   1.980 +                    raise KeyError
   1.981 +                else:
   1.982 +                    return self.main._bools[val.lower()]
   1.983 +            except KeyError:
   1.984 +                raise ValueError('Value "%s" is neither True nor False' % val)
   1.985 +
   1.986 +    def as_int(self, key):
   1.987 +        """
   1.988 +        A convenience method which coerces the specified value to an integer.
   1.989 +        
   1.990 +        If the value is an invalid literal for ``int``, a ``ValueError`` will
   1.991 +        be raised.
   1.992 +        
   1.993 +        >>> a = ConfigObj()
   1.994 +        >>> a['a'] = 'fish'
   1.995 +        >>> a.as_int('a')
   1.996 +        Traceback (most recent call last):
   1.997 +        ValueError: invalid literal for int(): fish
   1.998 +        >>> a['b'] = '1'
   1.999 +        >>> a.as_int('b')
  1.1000 +        1
  1.1001 +        >>> a['b'] = '3.2'
  1.1002 +        >>> a.as_int('b')
  1.1003 +        Traceback (most recent call last):
  1.1004 +        ValueError: invalid literal for int(): 3.2
  1.1005 +        """
  1.1006 +        return int(self[key])
  1.1007 +
  1.1008 +    def as_float(self, key):
  1.1009 +        """
  1.1010 +        A convenience method which coerces the specified value to a float.
  1.1011 +        
  1.1012 +        If the value is an invalid literal for ``float``, a ``ValueError`` will
  1.1013 +        be raised.
  1.1014 +        
  1.1015 +        >>> a = ConfigObj()
  1.1016 +        >>> a['a'] = 'fish'
  1.1017 +        >>> a.as_float('a')
  1.1018 +        Traceback (most recent call last):
  1.1019 +        ValueError: invalid literal for float(): fish
  1.1020 +        >>> a['b'] = '1'
  1.1021 +        >>> a.as_float('b')
  1.1022 +        1.0
  1.1023 +        >>> a['b'] = '3.2'
  1.1024 +        >>> a.as_float('b')
  1.1025 +        3.2000000000000002
  1.1026 +        """
  1.1027 +        return float(self[key])
  1.1028 +    
  1.1029 +
  1.1030 +class ConfigObj(Section):
  1.1031 +    """An object to read, create, and write config files."""
  1.1032 +
  1.1033 +    _keyword = re.compile(r'''^ # line start
  1.1034 +        (\s*)                   # indentation
  1.1035 +        (                       # keyword
  1.1036 +            (?:".*?")|          # double quotes
  1.1037 +            (?:'.*?')|          # single quotes
  1.1038 +            (?:[^'"=].*?)       # no quotes
  1.1039 +        )
  1.1040 +        \s*=\s*                 # divider
  1.1041 +        (.*)                    # value (including list values and comments)
  1.1042 +        $   # line end
  1.1043 +        ''',
  1.1044 +        re.VERBOSE)
  1.1045 +
  1.1046 +    _sectionmarker = re.compile(r'''^
  1.1047 +        (\s*)                     # 1: indentation
  1.1048 +        ((?:\[\s*)+)              # 2: section marker open
  1.1049 +        (                         # 3: section name open
  1.1050 +            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
  1.1051 +            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
  1.1052 +            (?:[^'"\s].*?)        # at least one non-space unquoted
  1.1053 +        )                         # section name close
  1.1054 +        ((?:\s*\])+)              # 4: section marker close
  1.1055 +        \s*(\#.*)?                # 5: optional comment
  1.1056 +        $''',
  1.1057 +        re.VERBOSE)
  1.1058 +
  1.1059 +    # this regexp pulls list values out as a single string
  1.1060 +    # or single values and comments
  1.1061 +    # FIXME: this regex adds a '' to the end of comma terminated lists
  1.1062 +    #   workaround in ``_handle_value``
  1.1063 +    _valueexp = re.compile(r'''^
  1.1064 +        (?:
  1.1065 +            (?:
  1.1066 +                (
  1.1067 +                    (?:
  1.1068 +                        (?:
  1.1069 +                            (?:".*?")|              # double quotes
  1.1070 +                            (?:'.*?')|              # single quotes
  1.1071 +                            (?:[^'",\#][^,\#]*?)    # unquoted
  1.1072 +                        )
  1.1073 +                        \s*,\s*                     # comma
  1.1074 +                    )*      # match all list items ending in a comma (if any)
  1.1075 +                )
  1.1076 +                (
  1.1077 +                    (?:".*?")|                      # double quotes
  1.1078 +                    (?:'.*?')|                      # single quotes
  1.1079 +                    (?:[^'",\#\s][^,]*?)|           # unquoted
  1.1080 +                    (?:(?<!,))                      # Empty value
  1.1081 +                )?          # last item in a list - or string value
  1.1082 +            )|
  1.1083 +            (,)             # alternatively a single comma - empty list
  1.1084 +        )
  1.1085 +        \s*(\#.*)?          # optional comment
  1.1086 +        $''',
  1.1087 +        re.VERBOSE)
  1.1088 +
  1.1089 +    # use findall to get the members of a list value
  1.1090 +    _listvalueexp = re.compile(r'''
  1.1091 +        (
  1.1092 +            (?:".*?")|          # double quotes
  1.1093 +            (?:'.*?')|          # single quotes
  1.1094 +            (?:[^'",\#].*?)       # unquoted
  1.1095 +        )
  1.1096 +        \s*,\s*                 # comma
  1.1097 +        ''',
  1.1098 +        re.VERBOSE)
  1.1099 +
  1.1100 +    # this regexp is used for the value
  1.1101 +    # when lists are switched off
  1.1102 +    _nolistvalue = re.compile(r'''^
  1.1103 +        (
  1.1104 +            (?:".*?")|          # double quotes
  1.1105 +            (?:'.*?')|          # single quotes
  1.1106 +            (?:[^'"\#].*?)|     # unquoted
  1.1107 +            (?:)                # Empty value
  1.1108 +        )
  1.1109 +        \s*(\#.*)?              # optional comment
  1.1110 +        $''',
  1.1111 +        re.VERBOSE)
  1.1112 +
  1.1113 +    # regexes for finding triple quoted values on one line
  1.1114 +    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
  1.1115 +    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
  1.1116 +    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
  1.1117 +    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
  1.1118 +
  1.1119 +    _triple_quote = {
  1.1120 +        "'''": (_single_line_single, _multi_line_single),
  1.1121 +        '"""': (_single_line_double, _multi_line_double),
  1.1122 +    }
  1.1123 +
  1.1124 +    # Used by the ``istrue`` Section method
  1.1125 +    _bools = {
  1.1126 +        'yes': True, 'no': False,
  1.1127 +        'on': True, 'off': False,
  1.1128 +        '1': True, '0': False,
  1.1129 +        'true': True, 'false': False,
  1.1130 +        }
  1.1131 +
  1.1132 +    def __init__(self, infile=None, options=None, **kwargs):
  1.1133 +        """
  1.1134 +        Parse or create a config file object.
  1.1135 +        
  1.1136 +        ``ConfigObj(infile=None, options=None, **kwargs)``
  1.1137 +        """
  1.1138 +        if infile is None:
  1.1139 +            infile = []
  1.1140 +        if options is None:
  1.1141 +            options = {}
  1.1142 +        else:
  1.1143 +            options = dict(options)
  1.1144 +        # keyword arguments take precedence over an options dictionary
  1.1145 +        options.update(kwargs)
  1.1146 +        # init the superclass
  1.1147 +        Section.__init__(self, self, 0, self)
  1.1148 +        #
  1.1149 +        defaults = OPTION_DEFAULTS.copy()
  1.1150 +        for entry in options.keys():
  1.1151 +            if entry not in defaults.keys():
  1.1152 +                raise TypeError, 'Unrecognised option "%s".' % entry
  1.1153 +        # TODO: check the values too.
  1.1154 +        #
  1.1155 +        # Add any explicit options to the defaults
  1.1156 +        defaults.update(options)
  1.1157 +        #
  1.1158 +        # initialise a few variables
  1.1159 +        self.filename = None
  1.1160 +        self._errors = []
  1.1161 +        self.raise_errors = defaults['raise_errors']
  1.1162 +        self.interpolation = defaults['interpolation']
  1.1163 +        self.list_values = defaults['list_values']
  1.1164 +        self.create_empty = defaults['create_empty']
  1.1165 +        self.file_error = defaults['file_error']
  1.1166 +        self.stringify = defaults['stringify']
  1.1167 +        self.indent_type = defaults['indent_type']
  1.1168 +        self.encoding = defaults['encoding']
  1.1169 +        self.default_encoding = defaults['default_encoding']
  1.1170 +        self.BOM = False
  1.1171 +        self.newlines = None
  1.1172 +        self.write_empty_values = defaults['write_empty_values']
  1.1173 +        self.unrepr = defaults['unrepr']
  1.1174 +        #
  1.1175 +        self.initial_comment = []
  1.1176 +        self.final_comment = []
  1.1177 +        #
  1.1178 +        self._terminated = False
  1.1179 +        #
  1.1180 +        if isinstance(infile, StringTypes):
  1.1181 +            self.filename = infile
  1.1182 +            if os.path.isfile(infile):
  1.1183 +                infile = open(infile).read() or []
  1.1184 +            elif self.file_error:
  1.1185 +                # raise an error if the file doesn't exist
  1.1186 +                raise IOError, 'Config file not found: "%s".' % self.filename
  1.1187 +            else:
  1.1188 +                # file doesn't already exist
  1.1189 +                if self.create_empty:
  1.1190 +                    # this is a good test that the filename specified
  1.1191 +                    # isn't impossible - like on a non existent device
  1.1192 +                    h = open(infile, 'w')
  1.1193 +                    h.write('')
  1.1194 +                    h.close()
  1.1195 +                infile = []
  1.1196 +        elif isinstance(infile, (list, tuple)):
  1.1197 +            infile = list(infile)
  1.1198 +        elif isinstance(infile, dict):
  1.1199 +            # initialise self
  1.1200 +            # the Section class handles creating subsections
  1.1201 +            if isinstance(infile, ConfigObj):
  1.1202 +                # get a copy of our ConfigObj
  1.1203 +                infile = infile.dict()
  1.1204 +            for entry in infile:
  1.1205 +                self[entry] = infile[entry]
  1.1206 +            del self._errors
  1.1207 +            if defaults['configspec'] is not None:
  1.1208 +                self._handle_configspec(defaults['configspec'])
  1.1209 +            else:
  1.1210 +                self.configspec = None
  1.1211 +            return
  1.1212 +        elif hasattr(infile, 'read'):
  1.1213 +            # This supports file like objects
  1.1214 +            infile = infile.read() or []
  1.1215 +            # needs splitting into lines - but needs doing *after* decoding
  1.1216 +            # in case it's not an 8 bit encoding
  1.1217 +        else:
  1.1218 +            raise TypeError, ('infile must be a filename,'
  1.1219 +                ' file like object, or list of lines.')
  1.1220 +        #
  1.1221 +        if infile:
  1.1222 +            # don't do it for the empty ConfigObj
  1.1223 +            infile = self._handle_bom(infile)
  1.1224 +            # infile is now *always* a list
  1.1225 +            #
  1.1226 +            # Set the newlines attribute (first line ending it finds)
  1.1227 +            # and strip trailing '\n' or '\r' from lines
  1.1228 +            for line in infile:
  1.1229 +                if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
  1.1230 +                    continue
  1.1231 +                for end in ('\r\n', '\n', '\r'):
  1.1232 +                    if line.endswith(end):
  1.1233 +                        self.newlines = end
  1.1234 +                        break
  1.1235 +                break
  1.1236 +            if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'):
  1.1237 +                self._terminated = True
  1.1238 +            infile = [line.rstrip('\r\n') for line in infile]
  1.1239 +        #
  1.1240 +        self._parse(infile)
  1.1241 +        # if we had any errors, now is the time to raise them
  1.1242 +        if self._errors:
  1.1243 +            info = "at line %s." % self._errors[0].line_number
  1.1244 +            if len(self._errors) > 1:
  1.1245 +                msg = ("Parsing failed with several errors.\nFirst error %s" %
  1.1246 +                    info)
  1.1247 +                error = ConfigObjError(msg)
  1.1248 +            else:
  1.1249 +                error = self._errors[0]
  1.1250 +            # set the errors attribute; it's a list of tuples:
  1.1251 +            # (error_type, message, line_number)
  1.1252 +            error.errors = self._errors
  1.1253 +            # set the config attribute
  1.1254 +            error.config = self
  1.1255 +            raise error
  1.1256 +        # delete private attributes
  1.1257 +        del self._errors
  1.1258 +        #
  1.1259 +        if defaults['configspec'] is None:
  1.1260 +            self.configspec = None
  1.1261 +        else:
  1.1262 +            self._handle_configspec(defaults['configspec'])
  1.1263 +    
  1.1264 +    def __repr__(self):
  1.1265 +        return 'ConfigObj({%s})' % ', '.join(
  1.1266 +            [('%s: %s' % (repr(key), repr(self[key]))) for key in
  1.1267 +            (self.scalars + self.sections)])
  1.1268 +    
  1.1269 +    def _handle_bom(self, infile):
  1.1270 +        """
  1.1271 +        Handle any BOM, and decode if necessary.
  1.1272 +        
  1.1273 +        If an encoding is specified, that *must* be used - but the BOM should
  1.1274 +        still be removed (and the BOM attribute set).
  1.1275 +        
  1.1276 +        (If the encoding is wrongly specified, then a BOM for an alternative
  1.1277 +        encoding won't be discovered or removed.)
  1.1278 +        
  1.1279 +        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
  1.1280 +        removed. The BOM attribute will be set. UTF16 will be decoded to
  1.1281 +        unicode.
  1.1282 +        
  1.1283 +        NOTE: This method must not be called with an empty ``infile``.
  1.1284 +        
  1.1285 +        Specifying the *wrong* encoding is likely to cause a
  1.1286 +        ``UnicodeDecodeError``.
  1.1287 +        
  1.1288 +        ``infile`` must always be returned as a list of lines, but may be
  1.1289 +        passed in as a single string.
  1.1290 +        """
  1.1291 +        if ((self.encoding is not None) and
  1.1292 +            (self.encoding.lower() not in BOM_LIST)):
  1.1293 +            # No need to check for a BOM
  1.1294 +            # the encoding specified doesn't have one
  1.1295 +            # just decode
  1.1296 +            return self._decode(infile, self.encoding)
  1.1297 +        #
  1.1298 +        if isinstance(infile, (list, tuple)):
  1.1299 +            line = infile[0]
  1.1300 +        else:
  1.1301 +            line = infile
  1.1302 +        if self.encoding is not None:
  1.1303 +            # encoding explicitly supplied
  1.1304 +            # And it could have an associated BOM
  1.1305 +            # TODO: if encoding is just UTF16 - we ought to check for both
  1.1306 +            # TODO: big endian and little endian versions.
  1.1307 +            enc = BOM_LIST[self.encoding.lower()]
  1.1308 +            if enc == 'utf_16':
  1.1309 +                # For UTF16 we try big endian and little endian
  1.1310 +                for BOM, (encoding, final_encoding) in BOMS.items():
  1.1311 +                    if not final_encoding:
  1.1312 +                        # skip UTF8
  1.1313 +                        continue
  1.1314 +                    if infile.startswith(BOM):
  1.1315 +                        ### BOM discovered
  1.1316 +                        ##self.BOM = True
  1.1317 +                        # Don't need to remove BOM
  1.1318 +                        return self._decode(infile, encoding)
  1.1319 +                #
  1.1320 +                # If we get this far, will *probably* raise a DecodeError
  1.1321 +                # As it doesn't appear to start with a BOM
  1.1322 +                return self._decode(infile, self.encoding)
  1.1323 +            #
  1.1324 +            # Must be UTF8
  1.1325 +            BOM = BOM_SET[enc]
  1.1326 +            if not line.startswith(BOM):
  1.1327 +                return self._decode(infile, self.encoding)
  1.1328 +            #
  1.1329 +            newline = line[len(BOM):]
  1.1330 +            #
  1.1331 +            # BOM removed
  1.1332 +            if isinstance(infile, (list, tuple)):
  1.1333 +                infile[0] = newline
  1.1334 +            else:
  1.1335 +                infile = newline
  1.1336 +            self.BOM = True
  1.1337 +            return self._decode(infile, self.encoding)
  1.1338 +        #
  1.1339 +        # No encoding specified - so we need to check for UTF8/UTF16
  1.1340 +        for BOM, (encoding, final_encoding) in BOMS.items():
  1.1341 +            if not line.startswith(BOM):
  1.1342 +                continue
  1.1343 +            else:
  1.1344 +                # BOM discovered
  1.1345 +                self.encoding = final_encoding
  1.1346 +                if not final_encoding:
  1.1347 +                    self.BOM = True
  1.1348 +                    # UTF8
  1.1349 +                    # remove BOM
  1.1350 +                    newline = line[len(BOM):]
  1.1351 +                    if isinstance(infile, (list, tuple)):
  1.1352 +                        infile[0] = newline
  1.1353 +                    else:
  1.1354 +                        infile = newline
  1.1355 +                    # UTF8 - don't decode
  1.1356 +                    if isinstance(infile, StringTypes):
  1.1357 +                        return infile.splitlines(True)
  1.1358 +                    else:
  1.1359 +                        return infile
  1.1360 +                # UTF16 - have to decode
  1.1361 +                return self._decode(infile, encoding)
  1.1362 +        #
  1.1363 +        # No BOM discovered and no encoding specified, just return
  1.1364 +        if isinstance(infile, StringTypes):
  1.1365 +            # infile read from a file will be a single string
  1.1366 +            return infile.splitlines(True)
  1.1367 +        else:
  1.1368 +            return infile
  1.1369 +
  1.1370 +    def _a_to_u(self, aString):
  1.1371 +        """Decode ASCII strings to unicode if a self.encoding is specified."""
  1.1372 +        if self.encoding:
  1.1373 +            return aString.decode('ascii')
  1.1374 +        else:
  1.1375 +            return aString
  1.1376 +
  1.1377 +    def _decode(self, infile, encoding):
  1.1378 +        """
  1.1379 +        Decode infile to unicode. Using the specified encoding.
  1.1380 +        
  1.1381 +        if is a string, it also needs converting to a list.
  1.1382 +        """
  1.1383 +        if isinstance(infile, StringTypes):
  1.1384 +            # can't be unicode
  1.1385 +            # NOTE: Could raise a ``UnicodeDecodeError``
  1.1386 +            return infile.decode(encoding).splitlines(True)
  1.1387 +        for i, line in enumerate(infile):
  1.1388 +            if not isinstance(line, unicode):
  1.1389 +                # NOTE: The isinstance test here handles mixed lists of unicode/string
  1.1390 +                # NOTE: But the decode will break on any non-string values
  1.1391 +                # NOTE: Or could raise a ``UnicodeDecodeError``
  1.1392 +                infile[i] = line.decode(encoding)
  1.1393 +        return infile
  1.1394 +
  1.1395 +    def _decode_element(self, line):
  1.1396 +        """Decode element to unicode if necessary."""
  1.1397 +        if not self.encoding:
  1.1398 +            return line
  1.1399 +        if isinstance(line, str) and self.default_encoding:
  1.1400 +            return line.decode(self.default_encoding)
  1.1401 +        return line
  1.1402 +
  1.1403 +    def _str(self, value):
  1.1404 +        """
  1.1405 +        Used by ``stringify`` within validate, to turn non-string values
  1.1406 +        into strings.
  1.1407 +        """
  1.1408 +        if not isinstance(value, StringTypes):
  1.1409 +            return str(value)
  1.1410 +        else:
  1.1411 +            return value
  1.1412 +
  1.1413 +    def _parse(self, infile):
  1.1414 +        """Actually parse the config file."""
  1.1415 +        temp_list_values = self.list_values
  1.1416 +        if self.unrepr:
  1.1417 +            self.list_values = False
  1.1418 +        comment_list = []
  1.1419 +        done_start = False
  1.1420 +        this_section = self
  1.1421 +        maxline = len(infile) - 1
  1.1422 +        cur_index = -1
  1.1423 +        reset_comment = False
  1.1424 +        while cur_index < maxline:
  1.1425 +            if reset_comment:
  1.1426 +                comment_list = []
  1.1427 +            cur_index += 1
  1.1428 +            line = infile[cur_index]
  1.1429 +            sline = line.strip()
  1.1430 +            # do we have anything on the line ?
  1.1431 +            if not sline or sline.startswith('#') or sline.startswith(';'):
  1.1432 +                reset_comment = False
  1.1433 +                comment_list.append(line)
  1.1434 +                continue
  1.1435 +            if not done_start:
  1.1436 +                # preserve initial comment
  1.1437 +                self.initial_comment = comment_list
  1.1438 +                comment_list = []
  1.1439 +                done_start = True
  1.1440 +            reset_comment = True
  1.1441 +            # first we check if it's a section marker
  1.1442 +            mat = self._sectionmarker.match(line)
  1.1443 +            if mat is not None:
  1.1444 +                # is a section line
  1.1445 +                (indent, sect_open, sect_name, sect_close, comment) = (
  1.1446 +                    mat.groups())
  1.1447 +                if indent and (self.indent_type is None):
  1.1448 +                    self.indent_type = indent
  1.1449 +                cur_depth = sect_open.count('[')
  1.1450 +                if cur_depth != sect_close.count(']'):
  1.1451 +                    self._handle_error(
  1.1452 +                        "Cannot compute the section depth at line %s.",
  1.1453 +                        NestingError, infile, cur_index)
  1.1454 +                    continue
  1.1455 +                #
  1.1456 +                if cur_depth < this_section.depth:
  1.1457 +                    # the new section is dropping back to a previous level
  1.1458 +                    try:
  1.1459 +                        parent = self._match_depth(
  1.1460 +                            this_section,
  1.1461 +                            cur_depth).parent
  1.1462 +                    except SyntaxError:
  1.1463 +                        self._handle_error(
  1.1464 +                            "Cannot compute nesting level at line %s.",
  1.1465 +                            NestingError, infile, cur_index)
  1.1466 +                        continue
  1.1467 +                elif cur_depth == this_section.depth:
  1.1468 +                    # the new section is a sibling of the current section
  1.1469 +                    parent = this_section.parent
  1.1470 +                elif cur_depth == this_section.depth + 1:
  1.1471 +                    # the new section is a child the current section
  1.1472 +                    parent = this_section
  1.1473 +                else:
  1.1474 +                    self._handle_error(
  1.1475 +                        "Section too nested at line %s.",
  1.1476 +                        NestingError, infile, cur_index)
  1.1477 +                #
  1.1478 +                sect_name = self._unquote(sect_name)
  1.1479 +                if parent.has_key(sect_name):
  1.1480 +                    self._handle_error(
  1.1481 +                        'Duplicate section name at line %s.',
  1.1482 +                        DuplicateError, infile, cur_index)
  1.1483 +                    continue
  1.1484 +                # create the new section
  1.1485 +                this_section = Section(
  1.1486 +                    parent,
  1.1487 +                    cur_depth,
  1.1488 +                    self,
  1.1489 +                    name=sect_name)
  1.1490 +                parent[sect_name] = this_section
  1.1491 +                parent.inline_comments[sect_name] = comment
  1.1492 +                parent.comments[sect_name] = comment_list
  1.1493 +                continue
  1.1494 +            #
  1.1495 +            # it's not a section marker,
  1.1496 +            # so it should be a valid ``key = value`` line
  1.1497 +            mat = self._keyword.match(line)
  1.1498 +            if mat is None:
  1.1499 +                # it neither matched as a keyword
  1.1500 +                # or a section marker
  1.1501 +                self._handle_error(
  1.1502 +                    'Invalid line at line "%s".',
  1.1503 +                    ParseError, infile, cur_index)
  1.1504 +            else:
  1.1505 +                # is a keyword value
  1.1506 +                # value will include any inline comment
  1.1507 +                (indent, key, value) = mat.groups()
  1.1508 +                if indent and (self.indent_type is None):
  1.1509 +                    self.indent_type = indent
  1.1510 +                # check for a multiline value
  1.1511 +                if value[:3] in ['"""', "'''"]:
  1.1512 +                    try:
  1.1513 +                        (value, comment, cur_index) = self._multiline(
  1.1514 +                            value, infile, cur_index, maxline)
  1.1515 +                    except SyntaxError:
  1.1516 +                        self._handle_error(
  1.1517 +                            'Parse error in value at line %s.',
  1.1518 +                            ParseError, infile, cur_index)
  1.1519 +                        continue
  1.1520 +                    else:
  1.1521 +                        if self.unrepr:
  1.1522 +                            comment = ''
  1.1523 +                            try:
  1.1524 +                                value = unrepr(value)
  1.1525 +                            except Exception, e:
  1.1526 +                                if type(e) == UnknownType:
  1.1527 +                                    msg = 'Unknown name or type in value at line %s.'
  1.1528 +                                else:
  1.1529 +                                    msg = 'Parse error in value at line %s.'
  1.1530 +                                self._handle_error(msg, UnreprError, infile,
  1.1531 +                                    cur_index)
  1.1532 +                                continue
  1.1533 +                else:
  1.1534 +                    if self.unrepr:
  1.1535 +                        comment = ''
  1.1536 +                        try:
  1.1537 +                            value = unrepr(value)
  1.1538 +                        except Exception, e:
  1.1539 +                            if isinstance(e, UnknownType):
  1.1540 +                                msg = 'Unknown name or type in value at line %s.'
  1.1541 +                            else:
  1.1542 +                                msg = 'Parse error in value at line %s.'
  1.1543 +                            self._handle_error(msg, UnreprError, infile,
  1.1544 +                                cur_index)
  1.1545 +                            continue
  1.1546 +                    else:
  1.1547 +                        # extract comment and lists
  1.1548 +                        try:
  1.1549 +                            (value, comment) = self._handle_value(value)
  1.1550 +                        except SyntaxError:
  1.1551 +                            self._handle_error(
  1.1552 +                                'Parse error in value at line %s.',
  1.1553 +                                ParseError, infile, cur_index)
  1.1554 +                            continue
  1.1555 +                #
  1.1556 +                key = self._unquote(key)
  1.1557 +                if this_section.has_key(key):
  1.1558 +                    self._handle_error(
  1.1559 +                        'Duplicate keyword name at line %s.',
  1.1560 +                        DuplicateError, infile, cur_index)
  1.1561 +                    continue
  1.1562 +                # add the key.
  1.1563 +                # we set unrepr because if we have got this far we will never
  1.1564 +                # be creating a new section
  1.1565 +                this_section.__setitem__(key, value, unrepr=True)
  1.1566 +                this_section.inline_comments[key] = comment
  1.1567 +                this_section.comments[key] = comment_list
  1.1568 +                continue
  1.1569 +        #
  1.1570 +        if self.indent_type is None:
  1.1571 +            # no indentation used, set the type accordingly
  1.1572 +            self.indent_type = ''
  1.1573 +        #
  1.1574 +        if self._terminated:
  1.1575 +            comment_list.append('')
  1.1576 +        # preserve the final comment
  1.1577 +        if not self and not self.initial_comment:
  1.1578 +            self.initial_comment = comment_list
  1.1579 +        elif not reset_comment:
  1.1580 +            self.final_comment = comment_list
  1.1581 +        self.list_values = temp_list_values
  1.1582 +
  1.1583 +    def _match_depth(self, sect, depth):
  1.1584 +        """
  1.1585 +        Given a section and a depth level, walk back through the sections
  1.1586 +        parents to see if the depth level matches a previous section.
  1.1587 +        
  1.1588 +        Return a reference to the right section,
  1.1589 +        or raise a SyntaxError.
  1.1590 +        """
  1.1591 +        while depth < sect.depth:
  1.1592 +            if sect is sect.parent:
  1.1593 +                # we've reached the top level already
  1.1594 +                raise SyntaxError
  1.1595 +            sect = sect.parent
  1.1596 +        if sect.depth == depth:
  1.1597 +            return sect
  1.1598 +        # shouldn't get here
  1.1599 +        raise SyntaxError
  1.1600 +
  1.1601 +    def _handle_error(self, text, ErrorClass, infile, cur_index):
  1.1602 +        """
  1.1603 +        Handle an error according to the error settings.
  1.1604 +        
  1.1605 +        Either raise the error or store it.
  1.1606 +        The error will have occurred at ``cur_index``
  1.1607 +        """
  1.1608 +        line = infile[cur_index]
  1.1609 +        cur_index += 1
  1.1610 +        message = text % cur_index
  1.1611 +        error = ErrorClass(message, cur_index, line)
  1.1612 +        if self.raise_errors:
  1.1613 +            # raise the error - parsing stops here
  1.1614 +            raise error
  1.1615 +        # store the error
  1.1616 +        # reraise when parsing has finished
  1.1617 +        self._errors.append(error)
  1.1618 +
  1.1619 +    def _unquote(self, value):
  1.1620 +        """Return an unquoted version of a value"""
  1.1621 +        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
  1.1622 +            value = value[1:-1]
  1.1623 +        return value
  1.1624 +
  1.1625 +    def _quote(self, value, multiline=True):
  1.1626 +        """
  1.1627 +        Return a safely quoted version of a value.
  1.1628 +        
  1.1629 +        Raise a ConfigObjError if the value cannot be safely quoted.
  1.1630 +        If multiline is ``True`` (default) then use triple quotes
  1.1631 +        if necessary.
  1.1632 +        
  1.1633 +        Don't quote values that don't need it.
  1.1634 +        Recursively quote members of a list and return a comma joined list.
  1.1635 +        Multiline is ``False`` for lists.
  1.1636 +        Obey list syntax for empty and single member lists.
  1.1637 +        
  1.1638 +        If ``list_values=False`` then the value is only quoted if it contains
  1.1639 +        a ``\n`` (is multiline).
  1.1640 +        
  1.1641 +        If ``write_empty_values`` is set, and the value is an empty string, it
  1.1642 +        won't be quoted.
  1.1643 +        """
  1.1644 +        if multiline and self.write_empty_values and value == '':
  1.1645 +            # Only if multiline is set, so that it is used for values not
  1.1646 +            # keys, and not values that are part of a list
  1.1647 +            return ''
  1.1648 +        if multiline and isinstance(value, (list, tuple)):
  1.1649 +            if not value:
  1.1650 +                return ','
  1.1651 +            elif len(value) == 1:
  1.1652 +                return self._quote(value[0], multiline=False) + ','
  1.1653 +            return ', '.join([self._quote(val, multiline=False)
  1.1654 +                for val in value])
  1.1655 +        if not isinstance(value, StringTypes):
  1.1656 +            if self.stringify:
  1.1657 +                value = str(value)
  1.1658 +            else:
  1.1659 +                raise TypeError, 'Value "%s" is not a string.' % value
  1.1660 +        squot = "'%s'"
  1.1661 +        dquot = '"%s"'
  1.1662 +        noquot = "%s"
  1.1663 +        wspace_plus = ' \r\t\n\v\t\'"'
  1.1664 +        tsquot = '"""%s"""'
  1.1665 +        tdquot = "'''%s'''"
  1.1666 +        if not value:
  1.1667 +            return '""'
  1.1668 +        if (not self.list_values and '\n' not in value) or not (multiline and
  1.1669 +                ((("'" in value) and ('"' in value)) or ('\n' in value))):
  1.1670 +            if not self.list_values:
  1.1671 +                # we don't quote if ``list_values=False``
  1.1672 +                quot = noquot
  1.1673 +            # for normal values either single or double quotes will do
  1.1674 +            elif '\n' in value:
  1.1675 +                # will only happen if multiline is off - e.g. '\n' in key
  1.1676 +                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
  1.1677 +                    value)
  1.1678 +            elif ((value[0] not in wspace_plus) and
  1.1679 +                    (value[-1] not in wspace_plus) and
  1.1680 +                    (',' not in value)):
  1.1681 +                quot = noquot
  1.1682 +            else:
  1.1683 +                if ("'" in value) and ('"' in value):
  1.1684 +                    raise ConfigObjError, (
  1.1685 +                        'Value "%s" cannot be safely quoted.' % value)
  1.1686 +                elif '"' in value:
  1.1687 +                    quot = squot
  1.1688 +                else:
  1.1689 +                    quot = dquot
  1.1690 +        else:
  1.1691 +            # if value has '\n' or "'" *and* '"', it will need triple quotes
  1.1692 +            if (value.find('"""') != -1) and (value.find("'''") != -1):
  1.1693 +                raise ConfigObjError, (
  1.1694 +                    'Value "%s" cannot be safely quoted.' % value)
  1.1695 +            if value.find('"""') == -1:
  1.1696 +                quot = tdquot
  1.1697 +            else:
  1.1698 +                quot = tsquot
  1.1699 +        return quot % value
  1.1700 +
  1.1701 +    def _handle_value(self, value):
  1.1702 +        """
  1.1703 +        Given a value string, unquote, remove comment,
  1.1704 +        handle lists. (including empty and single member lists)
  1.1705 +        """
  1.1706 +        # do we look for lists in values ?
  1.1707 +        if not self.list_values:
  1.1708 +            mat = self._nolistvalue.match(value)
  1.1709 +            if mat is None:
  1.1710 +                raise SyntaxError
  1.1711 +            # NOTE: we don't unquote here
  1.1712 +            return mat.groups()
  1.1713 +        #
  1.1714 +        mat = self._valueexp.match(value)
  1.1715 +        if mat is None:
  1.1716 +            # the value is badly constructed, probably badly quoted,
  1.1717 +            # or an invalid list
  1.1718 +            raise SyntaxError
  1.1719 +        (list_values, single, empty_list, comment) = mat.groups()
  1.1720 +        if (list_values == '') and (single is None):
  1.1721 +            # change this if you want to accept empty values
  1.1722 +            raise SyntaxError
  1.1723 +        # NOTE: note there is no error handling from here if the regex
  1.1724 +        # is wrong: then incorrect values will slip through
  1.1725 +        if empty_list is not None:
  1.1726 +            # the single comma - meaning an empty list
  1.1727 +            return ([], comment)
  1.1728 +        if single is not None:
  1.1729 +            # handle empty values
  1.1730 +            if list_values and not single:
  1.1731 +                # FIXME: the '' is a workaround because our regex now matches
  1.1732 +                #   '' at the end of a list if it has a trailing comma
  1.1733 +                single = None
  1.1734 +            else:
  1.1735 +                single = single or '""'
  1.1736 +                single = self._unquote(single)
  1.1737 +        if list_values == '':
  1.1738 +            # not a list value
  1.1739 +            return (single, comment)
  1.1740 +        the_list = self._listvalueexp.findall(list_values)
  1.1741 +        the_list = [self._unquote(val) for val in the_list]
  1.1742 +        if single is not None:
  1.1743 +            the_list += [single]
  1.1744 +        return (the_list, comment)
  1.1745 +
  1.1746 +    def _multiline(self, value, infile, cur_index, maxline):
  1.1747 +        """Extract the value, where we are in a multiline situation."""
  1.1748 +        quot = value[:3]
  1.1749 +        newvalue = value[3:]
  1.1750 +        single_line = self._triple_quote[quot][0]
  1.1751 +        multi_line = self._triple_quote[quot][1]
  1.1752 +        mat = single_line.match(value)
  1.1753 +        if mat is not None:
  1.1754 +            retval = list(mat.groups())
  1.1755 +            retval.append(cur_index)
  1.1756 +            return retval
  1.1757 +        elif newvalue.find(quot) != -1:
  1.1758 +            # somehow the triple quote is missing
  1.1759 +            raise SyntaxError
  1.1760 +        #
  1.1761 +        while cur_index < maxline:
  1.1762 +            cur_index += 1
  1.1763 +            newvalue += '\n'
  1.1764 +            line = infile[cur_index]
  1.1765 +            if line.find(quot) == -1:
  1.1766 +                newvalue += line
  1.1767 +            else:
  1.1768 +                # end of multiline, process it
  1.1769 +                break
  1.1770 +        else:
  1.1771 +            # we've got to the end of the config, oops...
  1.1772 +            raise SyntaxError
  1.1773 +        mat = multi_line.match(line)
  1.1774 +        if mat is None:
  1.1775 +            # a badly formed line
  1.1776 +            raise SyntaxError
  1.1777 +        (value, comment) = mat.groups()
  1.1778 +        return (newvalue + value, comment, cur_index)
  1.1779 +
  1.1780 +    def _handle_configspec(self, configspec):
  1.1781 +        """Parse the configspec."""
  1.1782 +        # FIXME: Should we check that the configspec was created with the 
  1.1783 +        #   correct settings ? (i.e. ``list_values=False``)
  1.1784 +        if not isinstance(configspec, ConfigObj):
  1.1785 +            try:
  1.1786 +                configspec = ConfigObj(
  1.1787 +                    configspec,
  1.1788 +                    raise_errors=True,
  1.1789 +                    file_error=True,
  1.1790 +                    list_values=False)
  1.1791 +            except ConfigObjError, e:
  1.1792 +                # FIXME: Should these errors have a reference
  1.1793 +                # to the already parsed ConfigObj ?
  1.1794 +                raise ConfigspecError('Parsing configspec failed: %s' % e)
  1.1795 +            except IOError, e:
  1.1796 +                raise IOError('Reading configspec failed: %s' % e)
  1.1797 +        self._set_configspec_value(configspec, self)
  1.1798 +
  1.1799 +    def _set_configspec_value(self, configspec, section):
  1.1800 +        """Used to recursively set configspec values."""
  1.1801 +        if '__many__' in configspec.sections:
  1.1802 +            section.configspec['__many__'] = configspec['__many__']
  1.1803 +            if len(configspec.sections) > 1:
  1.1804 +                # FIXME: can we supply any useful information here ?
  1.1805 +                raise RepeatSectionError
  1.1806 +        if hasattr(configspec, 'initial_comment'):
  1.1807 +            section._configspec_initial_comment = configspec.initial_comment
  1.1808 +            section._configspec_final_comment = configspec.final_comment
  1.1809 +            section._configspec_encoding = configspec.encoding
  1.1810 +            section._configspec_BOM = configspec.BOM
  1.1811 +            section._configspec_newlines = configspec.newlines
  1.1812 +            section._configspec_indent_type = configspec.indent_type
  1.1813 +        for entry in configspec.scalars:
  1.1814 +            section._configspec_comments[entry] = configspec.comments[entry]
  1.1815 +            section._configspec_inline_comments[entry] = (
  1.1816 +                configspec.inline_comments[entry])
  1.1817 +            section.configspec[entry] = configspec[entry]
  1.1818 +            section._order.append(entry)
  1.1819 +        for entry in configspec.sections:
  1.1820 +            if entry == '__many__':
  1.1821 +                continue
  1.1822 +            section._cs_section_comments[entry] = configspec.comments[entry]
  1.1823 +            section._cs_section_inline_comments[entry] = (
  1.1824 +                configspec.inline_comments[entry])
  1.1825 +            if not section.has_key(entry):
  1.1826 +                section[entry] = {}
  1.1827 +            self._set_configspec_value(configspec[entry], section[entry])
  1.1828 +
  1.1829 +    def _handle_repeat(self, section, configspec):
  1.1830 +        """Dynamically assign configspec for repeated section."""
  1.1831 +        try:
  1.1832 +            section_keys = configspec.sections
  1.1833 +            scalar_keys = configspec.scalars
  1.1834 +        except AttributeError:
  1.1835 +            section_keys = [entry for entry in configspec 
  1.1836 +                                if isinstance(configspec[entry], dict)]
  1.1837 +            scalar_keys = [entry for entry in configspec 
  1.1838 +                                if not isinstance(configspec[entry], dict)]
  1.1839 +        if '__many__' in section_keys and len(section_keys) > 1:
  1.1840 +            # FIXME: can we supply any useful information here ?
  1.1841 +            raise RepeatSectionError
  1.1842 +        scalars = {}
  1.1843 +        sections = {}
  1.1844 +        for entry in scalar_keys:
  1.1845 +            val = configspec[entry]
  1.1846 +            scalars[entry] = val
  1.1847 +        for entry in section_keys:
  1.1848 +            val = configspec[entry]
  1.1849 +            if entry == '__many__':
  1.1850 +                scalars[entry] = val
  1.1851 +                continue
  1.1852 +            sections[entry] = val
  1.1853 +        #
  1.1854 +        section.configspec = scalars
  1.1855 +        for entry in sections:
  1.1856 +            if not section.has_key(entry):
  1.1857 +                section[entry] = {}
  1.1858 +            self._handle_repeat(section[entry], sections[entry])
  1.1859 +
  1.1860 +    def _write_line(self, indent_string, entry, this_entry, comment):
  1.1861 +        """Write an individual line, for the write method"""
  1.1862 +        # NOTE: the calls to self._quote here handles non-StringType values.
  1.1863 +        if not self.unrepr:
  1.1864 +            val = self._decode_element(self._quote(this_entry))
  1.1865 +        else:
  1.1866 +            val = repr(this_entry)
  1.1867 +        return '%s%s%s%s%s' % (
  1.1868 +            indent_string,
  1.1869 +            self._decode_element(self._quote(entry, multiline=False)),
  1.1870 +            self._a_to_u(' = '),
  1.1871 +            val,
  1.1872 +            self._decode_element(comment))
  1.1873 +
  1.1874 +    def _write_marker(self, indent_string, depth, entry, comment):
  1.1875 +        """Write a section marker line"""
  1.1876 +        return '%s%s%s%s%s' % (
  1.1877 +            indent_string,
  1.1878 +            self._a_to_u('[' * depth),
  1.1879 +            self._quote(self._decode_element(entry), multiline=False),
  1.1880 +            self._a_to_u(']' * depth),
  1.1881 +            self._decode_element(comment))
  1.1882 +
  1.1883 +    def _handle_comment(self, comment):
  1.1884 +        """Deal with a comment."""
  1.1885 +        if not comment:
  1.1886 +            return ''
  1.1887 +        start = self.indent_type
  1.1888 +        if not comment.startswith('#'):
  1.1889 +            start += self._a_to_u(' # ')
  1.1890 +        return (start + comment)
  1.1891 +
  1.1892 +    # Public methods
  1.1893 +
  1.1894 +    def write(self, outfile=None, section=None):
  1.1895 +        """
  1.1896 +        Write the current ConfigObj as a file
  1.1897 +        
  1.1898 +        tekNico: FIXME: use StringIO instead of real files
  1.1899 +        
  1.1900 +        >>> filename = a.filename
  1.1901 +        >>> a.filename = 'test.ini'
  1.1902 +        >>> a.write()
  1.1903 +        >>> a.filename = filename
  1.1904 +        >>> a == ConfigObj('test.ini', raise_errors=True)
  1.1905 +        1
  1.1906 +        """
  1.1907 +        if self.indent_type is None:
  1.1908 +            # this can be true if initialised from a dictionary
  1.1909 +            self.indent_type = DEFAULT_INDENT_TYPE
  1.1910 +        #
  1.1911 +        out = []
  1.1912 +        cs = self._a_to_u('#')
  1.1913 +        csp = self._a_to_u('# ')
  1.1914 +        if section is None:
  1.1915 +            int_val = self.interpolation
  1.1916 +            self.interpolation = False
  1.1917 +            section = self
  1.1918 +            for line in self.initial_comment:
  1.1919 +                line = self._decode_element(line)
  1.1920 +                stripped_line = line.strip()
  1.1921 +                if stripped_line and not stripped_line.startswith(cs):
  1.1922 +                    line = csp + line
  1.1923 +                out.append(line)
  1.1924 +        #
  1.1925 +        indent_string = self.indent_type * section.depth
  1.1926 +        for entry in (section.scalars + section.sections):
  1.1927 +            if entry in section.defaults:
  1.1928 +                # don't write out default values
  1.1929 +                continue
  1.1930 +            for comment_line in section.comments[entry]:
  1.1931 +                comment_line = self._decode_element(comment_line.lstrip())
  1.1932 +                if comment_line and not comment_line.startswith(cs):
  1.1933 +                    comment_line = csp + comment_line
  1.1934 +                out.append(indent_string + comment_line)
  1.1935 +            this_entry = section[entry]
  1.1936 +            comment = self._handle_comment(section.inline_comments[entry])
  1.1937 +            #
  1.1938 +            if isinstance(this_entry, dict):
  1.1939 +                # a section
  1.1940 +                out.append(self._write_marker(
  1.1941 +                    indent_string,
  1.1942 +                    this_entry.depth,
  1.1943 +                    entry,
  1.1944 +                    comment))
  1.1945 +                out.extend(self.write(section=this_entry))
  1.1946 +            else:
  1.1947 +                out.append(self._write_line(
  1.1948 +                    indent_string,
  1.1949 +                    entry,
  1.1950 +                    this_entry,
  1.1951 +                    comment))
  1.1952 +        #
  1.1953 +        if section is self:
  1.1954 +            for line in self.final_comment:
  1.1955 +                line = self._decode_element(line)
  1.1956 +                stripped_line = line.strip()
  1.1957 +                if stripped_line and not stripped_line.startswith(cs):
  1.1958 +                    line = csp + line
  1.1959 +                out.append(line)
  1.1960 +            self.interpolation = int_val
  1.1961 +        #
  1.1962 +        if section is not self:
  1.1963 +            return out
  1.1964 +        #
  1.1965 +        if (self.filename is None) and (outfile is None):
  1.1966 +            # output a list of lines
  1.1967 +            # might need to encode
  1.1968 +            # NOTE: This will *screw* UTF16, each line will start with the BOM
  1.1969 +            if self.encoding:
  1.1970 +                out = [l.encode(self.encoding) for l in out]
  1.1971 +            if (self.BOM and ((self.encoding is None) or
  1.1972 +                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
  1.1973 +                # Add the UTF8 BOM
  1.1974 +                if not out:
  1.1975 +                    out.append('')
  1.1976 +                out[0] = BOM_UTF8 + out[0]
  1.1977 +            return out
  1.1978 +        #
  1.1979 +        # Turn the list to a string, joined with correct newlines
  1.1980 +        output = (self._a_to_u(self.newlines or os.linesep)
  1.1981 +            ).join(out)
  1.1982 +        if self.encoding:
  1.1983 +            output = output.encode(self.encoding)
  1.1984 +        if (self.BOM and ((self.encoding is None) or
  1.1985 +            (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
  1.1986 +            # Add the UTF8 BOM
  1.1987 +            output = BOM_UTF8 + output
  1.1988 +        if outfile is not None:
  1.1989 +            outfile.write(output)
  1.1990 +        else:
  1.1991 +            h = open(self.filename, 'wb')
  1.1992 +            h.write(output)
  1.1993 +            h.close()
  1.1994 +
  1.1995 +    def validate(self, validator, preserve_errors=False, copy=False,
  1.1996 +        section=None):
  1.1997 +        """
  1.1998 +        Test the ConfigObj against a configspec.
  1.1999 +        
  1.2000 +        It uses the ``validator`` object from *validate.py*.
  1.2001 +        
  1.2002 +        To run ``validate`` on the current ConfigObj, call: ::
  1.2003 +        
  1.2004 +            test = config.validate(validator)
  1.2005 +        
  1.2006 +        (Normally having previously passed in the configspec when the ConfigObj
  1.2007 +        was created - you can dynamically assign a dictionary of checks to the
  1.2008 +        ``configspec`` attribute of a section though).
  1.2009 +        
  1.2010 +        It returns ``True`` if everything passes, or a dictionary of
  1.2011 +        pass/fails (True/False). If every member of a subsection passes, it
  1.2012 +        will just have the value ``True``. (It also returns ``False`` if all
  1.2013 +        members fail).
  1.2014 +        
  1.2015 +        In addition, it converts the values from strings to their native
  1.2016 +        types if their checks pass (and ``stringify`` is set).
  1.2017 +        
  1.2018 +        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
  1.2019 +        of a marking a fail with a ``False``, it will preserve the actual
  1.2020 +        exception object. This can contain info about the reason for failure.
  1.2021 +        For example the ``VdtValueTooSmallError`` indeicates that the value
  1.2022 +        supplied was too small. If a value (or section) is missing it will
  1.2023 +        still be marked as ``False``.
  1.2024 +        
  1.2025 +        You must have the validate module to use ``preserve_errors=True``.
  1.2026 +        
  1.2027 +        You can then use the ``flatten_errors`` function to turn your nested
  1.2028 +        results dictionary into a flattened list of failures - useful for
  1.2029 +        displaying meaningful error messages.
  1.2030 +        """
  1.2031 +        if section is None:
  1.2032 +            if self.configspec is None:
  1.2033 +                raise ValueError, 'No configspec supplied.'
  1.2034 +            if preserve_errors:
  1.2035 +                if VdtMissingValue is None:
  1.2036 +                    raise ImportError('Missing validate module.')
  1.2037 +            section = self
  1.2038 +        #
  1.2039 +        spec_section = section.configspec
  1.2040 +        if copy and hasattr(section, '_configspec_initial_comment'):
  1.2041 +            section.initial_comment = section._configspec_initial_comment
  1.2042 +            section.final_comment = section._configspec_final_comment
  1.2043 +            section.encoding = section._configspec_encoding
  1.2044 +            section.BOM = section._configspec_BOM
  1.2045 +            section.newlines = section._configspec_newlines
  1.2046 +            section.indent_type = section._configspec_indent_type
  1.2047 +        if '__many__' in section.configspec:
  1.2048 +            many = spec_section['__many__']
  1.2049 +            # dynamically assign the configspecs
  1.2050 +            # for the sections below
  1.2051 +            for entry in section.sections:
  1.2052 +                self._handle_repeat(section[entry], many)
  1.2053 +        #
  1.2054 +        out = {}
  1.2055 +        ret_true = True
  1.2056 +        ret_false = True
  1.2057 +        order = [k for k in section._order if k in spec_section]
  1.2058 +        order += [k for k in spec_section if k not in order]
  1.2059 +        for entry in order:
  1.2060 +            if entry == '__many__':
  1.2061 +                continue
  1.2062 +            if (not entry in section.scalars) or (entry in section.defaults):
  1.2063 +                # missing entries
  1.2064 +                # or entries from defaults
  1.2065 +                missing = True
  1.2066 +                val = None
  1.2067 +                if copy and not entry in section.scalars:
  1.2068 +                    # copy comments
  1.2069 +                    section.comments[entry] = (
  1.2070 +                        section._configspec_comments.get(entry, []))
  1.2071 +                    section.inline_comments[entry] = (
  1.2072 +                        section._configspec_inline_comments.get(entry, ''))
  1.2073 +                #
  1.2074 +            else:
  1.2075 +                missing = False
  1.2076 +                val = section[entry]
  1.2077 +            try:
  1.2078 +                check = validator.check(spec_section[entry],
  1.2079 +                                        val,
  1.2080 +                                        missing=missing
  1.2081 +                                        )
  1.2082 +            except validator.baseErrorClass, e:
  1.2083 +                if not preserve_errors or isinstance(e, VdtMissingValue):
  1.2084 +                    out[entry] = False
  1.2085 +                else:
  1.2086 +                    # preserve the error
  1.2087 +                    out[entry] = e
  1.2088 +                    ret_false = False
  1.2089 +                ret_true = False
  1.2090 +            else:
  1.2091 +                ret_false = False
  1.2092 +                out[entry] = True
  1.2093 +                if self.stringify or missing:
  1.2094 +                    # if we are doing type conversion
  1.2095 +                    # or the value is a supplied default
  1.2096 +                    if not self.stringify:
  1.2097 +                        if isinstance(check, (list, tuple)):
  1.2098 +                            # preserve lists
  1.2099 +                            check = [self._str(item) for item in check]
  1.2100 +                        elif missing and check is None:
  1.2101 +                            # convert the None from a default to a ''
  1.2102 +                            check = ''
  1.2103 +                        else:
  1.2104 +                            check = self._str(check)
  1.2105 +                    if (check != val) or missing:
  1.2106 +                        section[entry] = check
  1.2107 +                if not copy and missing and entry not in section.defaults:
  1.2108 +                    section.defaults.append(entry)
  1.2109 +        #
  1.2110 +        # Missing sections will have been created as empty ones when the
  1.2111 +        # configspec was read.
  1.2112 +        for entry in section.sections:
  1.2113 +            # FIXME: this means DEFAULT is not copied in copy mode
  1.2114 +            if section is self and entry == 'DEFAULT':
  1.2115 +                continue
  1.2116 +            if copy:
  1.2117 +                section.comments[entry] = section._cs_section_comments[entry]
  1.2118 +                section.inline_comments[entry] = (
  1.2119 +                    section._cs_section_inline_comments[entry])
  1.2120 +            check = self.validate(validator, preserve_errors=preserve_errors,
  1.2121 +                copy=copy, section=section[entry])
  1.2122 +            out[entry] = check
  1.2123 +            if check == False:
  1.2124 +                ret_true = False
  1.2125 +            elif check == True:
  1.2126 +                ret_false = False
  1.2127 +            else:
  1.2128 +                ret_true = False
  1.2129 +                ret_false = False
  1.2130 +        #
  1.2131 +        if ret_true:
  1.2132 +            return True
  1.2133 +        elif ret_false:
  1.2134 +            return False
  1.2135 +        else:
  1.2136 +            return out
  1.2137 +
  1.2138 +class SimpleVal(object):
  1.2139 +    """
  1.2140 +    A simple validator.
  1.2141 +    Can be used to check that all members expected are present.
  1.2142 +    
  1.2143 +    To use it, provide a configspec with all your members in (the value given
  1.2144 +    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
  1.2145 +    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
  1.2146 +    members are present, or a dictionary with True/False meaning
  1.2147 +    present/missing. (Whole missing sections will be replaced with ``False``)
  1.2148 +    """
  1.2149 +    
  1.2150 +    def __init__(self):
  1.2151 +        self.baseErrorClass = ConfigObjError
  1.2152 +    
  1.2153 +    def check(self, check, member, missing=False):
  1.2154 +        """A dummy check method, always returns the value unchanged."""
  1.2155 +        if missing:
  1.2156 +            raise self.baseErrorClass
  1.2157 +        return member
  1.2158 +
  1.2159 +# Check / processing functions for options
  1.2160 +def flatten_errors(cfg, res, levels=None, results=None):
  1.2161 +    """
  1.2162 +    An example function that will turn a nested dictionary of results
  1.2163 +    (as returned by ``ConfigObj.validate``) into a flat list.
  1.2164 +    
  1.2165 +    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
  1.2166 +    dictionary returned by ``validate``.
  1.2167 +    
  1.2168 +    (This is a recursive function, so you shouldn't use the ``levels`` or
  1.2169 +    ``results`` arguments - they are used by the function.
  1.2170 +    
  1.2171 +    Returns a list of keys that failed. Each member of the list is a tuple :
  1.2172 +    ::
  1.2173 +    
  1.2174 +        ([list of sections...], key, result)
  1.2175 +    
  1.2176 +    If ``validate`` was called with ``preserve_errors=False`` (the default)
  1.2177 +    then ``result`` will always be ``False``.
  1.2178 +
  1.2179 +    *list of sections* is a flattened list of sections that the key was found
  1.2180 +    in.
  1.2181 +    
  1.2182 +    If the section was missing then key will be ``None``.
  1.2183 +    
  1.2184 +    If the value (or section) was missing then ``result`` will be ``False``.
  1.2185 +    
  1.2186 +    If ``validate`` was called with ``preserve_errors=True`` and a value
  1.2187 +    was present, but failed the check, then ``result`` will be the exception
  1.2188 +    object returned. You can use this as a string that describes the failure.
  1.2189 +    
  1.2190 +    For example *The value "3" is of the wrong type*.
  1.2191 +    
  1.2192 +    >>> import validate
  1.2193 +    >>> vtor = validate.Validator()
  1.2194 +    >>> my_ini = '''
  1.2195 +    ...     option1 = True
  1.2196 +    ...     [section1]
  1.2197 +    ...     option1 = True
  1.2198 +    ...     [section2]
  1.2199 +    ...     another_option = Probably
  1.2200 +    ...     [section3]
  1.2201 +    ...     another_option = True
  1.2202 +    ...     [[section3b]]
  1.2203 +    ...     value = 3
  1.2204 +    ...     value2 = a
  1.2205 +    ...     value3 = 11
  1.2206 +    ...     '''
  1.2207 +    >>> my_cfg = '''
  1.2208 +    ...     option1 = boolean()
  1.2209 +    ...     option2 = boolean()
  1.2210 +    ...     option3 = boolean(default=Bad_value)
  1.2211 +    ...     [section1]
  1.2212 +    ...     option1 = boolean()
  1.2213 +    ...     option2 = boolean()
  1.2214 +    ...     option3 = boolean(default=Bad_value)
  1.2215 +    ...     [section2]
  1.2216 +    ...     another_option = boolean()
  1.2217 +    ...     [section3]
  1.2218 +    ...     another_option = boolean()
  1.2219 +    ...     [[section3b]]
  1.2220 +    ...     value = integer
  1.2221 +    ...     value2 = integer
  1.2222 +    ...     value3 = integer(0, 10)
  1.2223 +    ...         [[[section3b-sub]]]
  1.2224 +    ...         value = string
  1.2225 +    ...     [section4]
  1.2226 +    ...     another_option = boolean()
  1.2227 +    ...     '''
  1.2228 +    >>> cs = my_cfg.split('\\n')
  1.2229 +    >>> ini = my_ini.split('\\n')
  1.2230 +    >>> cfg = ConfigObj(ini, configspec=cs)
  1.2231 +    >>> res = cfg.validate(vtor, preserve_errors=True)
  1.2232 +    >>> errors = []
  1.2233 +    >>> for entry in flatten_errors(cfg, res):
  1.2234 +    ...     section_list, key, error = entry
  1.2235 +    ...     section_list.insert(0, '[root]')
  1.2236 +    ...     if key is not None:
  1.2237 +    ...        section_list.append(key)
  1.2238 +    ...     else:
  1.2239 +    ...         section_list.append('[missing]')
  1.2240 +    ...     section_string = ', '.join(section_list)
  1.2241 +    ...     errors.append((section_string, ' = ', error))
  1.2242 +    >>> errors.sort()
  1.2243 +    >>> for entry in errors:
  1.2244 +    ...     print entry[0], entry[1], (entry[2] or 0)
  1.2245 +    [root], option2  =  0
  1.2246 +    [root], option3  =  the value "Bad_value" is of the wrong type.
  1.2247 +    [root], section1, option2  =  0
  1.2248 +    [root], section1, option3  =  the value "Bad_value" is of the wrong type.
  1.2249 +    [root], section2, another_option  =  the value "Probably" is of the wrong type.
  1.2250 +    [root], section3, section3b, section3b-sub, [missing]  =  0
  1.2251 +    [root], section3, section3b, value2  =  the value "a" is of the wrong type.
  1.2252 +    [root], section3, section3b, value3  =  the value "11" is too big.
  1.2253 +    [root], section4, [missing]  =  0
  1.2254 +    """
  1.2255 +    if levels is None:
  1.2256 +        # first time called
  1.2257 +        levels = []
  1.2258 +        results = []
  1.2259 +    if res is True:
  1.2260 +        return results
  1.2261 +    if res is False:
  1.2262 +        results.append((levels[:], None, False))
  1.2263 +        if levels:
  1.2264 +            levels.pop()
  1.2265 +        return results
  1.2266 +    for (key, val) in res.items():
  1.2267 +        if val == True:
  1.2268 +            continue
  1.2269 +        if isinstance(cfg.get(key), dict):
  1.2270 +            # Go down one level
  1.2271 +            levels.append(key)
  1.2272 +            flatten_errors(cfg[key], val, levels, results)
  1.2273 +            continue
  1.2274 +        results.append((levels[:], key, val))
  1.2275 +    #
  1.2276 +    # Go up one level
  1.2277 +    if levels:
  1.2278 +        levels.pop()
  1.2279 +    #
  1.2280 +    return results
  1.2281 +
  1.2282 +"""*A programming language is a medium of expression.* - Paul Graham"""

mercurial