Back to index

enigmail  1.4.3
configobj.py
Go to the documentation of this file.
00001 # configobj.py
00002 # A config file reader/writer that supports nested sections in config files.
00003 # Copyright (C) 2005-2006 Michael Foord, Nicola Larosa
00004 # E-mail: fuzzyman AT voidspace DOT org DOT uk
00005 #         nico AT tekNico DOT net
00006 
00007 # ConfigObj 4
00008 # http://www.voidspace.org.uk/python/configobj.html
00009 
00010 # Released subject to the BSD License
00011 # Please see http://www.voidspace.org.uk/python/license.shtml
00012 
00013 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
00014 # For information about bugfixes, updates and support, please join the
00015 # ConfigObj mailing list:
00016 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
00017 # Comments, suggestions and bug reports welcome.
00018 
00019 from __future__ import generators
00020 
00021 import sys
00022 INTP_VER = sys.version_info[:2]
00023 if INTP_VER < (2, 2):
00024     raise RuntimeError("Python v.2.2 or later needed")
00025 
00026 import os, re
00027 compiler = None
00028 try:
00029     import compiler
00030 except ImportError:
00031     # for IronPython
00032     pass
00033 from types import StringTypes
00034 from warnings import warn
00035 try:
00036     from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
00037 except ImportError:
00038     # Python 2.2 does not have these
00039     # UTF-8
00040     BOM_UTF8 = '\xef\xbb\xbf'
00041     # UTF-16, little endian
00042     BOM_UTF16_LE = '\xff\xfe'
00043     # UTF-16, big endian
00044     BOM_UTF16_BE = '\xfe\xff'
00045     if sys.byteorder == 'little':
00046         # UTF-16, native endianness
00047         BOM_UTF16 = BOM_UTF16_LE
00048     else:
00049         # UTF-16, native endianness
00050         BOM_UTF16 = BOM_UTF16_BE
00051 
00052 # A dictionary mapping BOM to
00053 # the encoding to decode with, and what to set the
00054 # encoding attribute to.
00055 BOMS = {
00056     BOM_UTF8: ('utf_8', None),
00057     BOM_UTF16_BE: ('utf16_be', 'utf_16'),
00058     BOM_UTF16_LE: ('utf16_le', 'utf_16'),
00059     BOM_UTF16: ('utf_16', 'utf_16'),
00060     }
00061 # All legal variants of the BOM codecs.
00062 # TODO: the list of aliases is not meant to be exhaustive, is there a
00063 #   better way ?
00064 BOM_LIST = {
00065     'utf_16': 'utf_16',
00066     'u16': 'utf_16',
00067     'utf16': 'utf_16',
00068     'utf-16': 'utf_16',
00069     'utf16_be': 'utf16_be',
00070     'utf_16_be': 'utf16_be',
00071     'utf-16be': 'utf16_be',
00072     'utf16_le': 'utf16_le',
00073     'utf_16_le': 'utf16_le',
00074     'utf-16le': 'utf16_le',
00075     'utf_8': 'utf_8',
00076     'u8': 'utf_8',
00077     'utf': 'utf_8',
00078     'utf8': 'utf_8',
00079     'utf-8': 'utf_8',
00080     }
00081 
00082 # Map of encodings to the BOM to write.
00083 BOM_SET = {
00084     'utf_8': BOM_UTF8,
00085     'utf_16': BOM_UTF16,
00086     'utf16_be': BOM_UTF16_BE,
00087     'utf16_le': BOM_UTF16_LE,
00088     None: BOM_UTF8
00089     }
00090 
00091 try:
00092     from validate import VdtMissingValue
00093 except ImportError:
00094     VdtMissingValue = None
00095 
00096 try:
00097     enumerate
00098 except NameError:
00099     def enumerate(obj):
00100         """enumerate for Python 2.2."""
00101         i = -1
00102         for item in obj:
00103             i += 1
00104             yield i, item
00105 
00106 try:
00107     True, False
00108 except NameError:
00109     True, False = 1, 0
00110 
00111 
00112 __version__ = '4.4.0'
00113 
00114 __revision__ = '$Id: configobj.py,v 3.5 2007/07/02 18:20:24 benjamin%smedbergs.us Exp $'
00115 
00116 __docformat__ = "restructuredtext en"
00117 
00118 __all__ = (
00119     '__version__',
00120     'DEFAULT_INDENT_TYPE',
00121     'DEFAULT_INTERPOLATION',
00122     'ConfigObjError',
00123     'NestingError',
00124     'ParseError',
00125     'DuplicateError',
00126     'ConfigspecError',
00127     'ConfigObj',
00128     'SimpleVal',
00129     'InterpolationError',
00130     'InterpolationLoopError',
00131     'MissingInterpolationOption',
00132     'RepeatSectionError',
00133     'UnreprError',
00134     'UnknownType',
00135     '__docformat__',
00136     'flatten_errors',
00137 )
00138 
00139 DEFAULT_INTERPOLATION = 'configparser'
00140 DEFAULT_INDENT_TYPE = '    '
00141 MAX_INTERPOL_DEPTH = 10
00142 
00143 OPTION_DEFAULTS = {
00144     'interpolation': True,
00145     'raise_errors': False,
00146     'list_values': True,
00147     'create_empty': False,
00148     'file_error': False,
00149     'configspec': None,
00150     'stringify': True,
00151     # option may be set to one of ('', ' ', '\t')
00152     'indent_type': None,
00153     'encoding': None,
00154     'default_encoding': None,
00155     'unrepr': False,
00156     'write_empty_values': False,
00157 }
00158 
00159 
00160 def getObj(s):
00161     s = "a=" + s
00162     if compiler is None:
00163         raise ImportError('compiler module not available')
00164     p = compiler.parse(s)
00165     return p.getChildren()[1].getChildren()[0].getChildren()[1]
00166 
00167 class UnknownType(Exception):
00168     pass
00169 
00170 class Builder:
00171     
00172     def build(self, o):
00173         m = getattr(self, 'build_' + o.__class__.__name__, None)
00174         if m is None:
00175             raise UnknownType(o.__class__.__name__)
00176         return m(o)
00177     
00178     def build_List(self, o):
00179         return map(self.build, o.getChildren())
00180     
00181     def build_Const(self, o):
00182         return o.value
00183     
00184     def build_Dict(self, o):
00185         d = {}
00186         i = iter(map(self.build, o.getChildren()))
00187         for el in i:
00188             d[el] = i.next()
00189         return d
00190     
00191     def build_Tuple(self, o):
00192         return tuple(self.build_List(o))
00193     
00194     def build_Name(self, o):
00195         if o.name == 'None':
00196             return None
00197         if o.name == 'True':
00198             return True
00199         if o.name == 'False':
00200             return False
00201         
00202         # An undefinted Name
00203         raise UnknownType('Undefined Name')
00204     
00205     def build_Add(self, o):
00206         real, imag = map(self.build_Const, o.getChildren())
00207         try:
00208             real = float(real)
00209         except TypeError:
00210             raise UnknownType('Add')
00211         if not isinstance(imag, complex) or imag.real != 0.0:
00212             raise UnknownType('Add')
00213         return real+imag
00214     
00215     def build_Getattr(self, o):
00216         parent = self.build(o.expr)
00217         return getattr(parent, o.attrname)
00218     
00219     def build_UnarySub(self, o):
00220         return -self.build_Const(o.getChildren()[0])
00221     
00222     def build_UnaryAdd(self, o):
00223         return self.build_Const(o.getChildren()[0])
00224 
00225 def unrepr(s):
00226     if not s:
00227         return s
00228     return Builder().build(getObj(s))
00229 
00230 def _splitlines(instring):
00231     """Split a string on lines, without losing line endings or truncating."""
00232     
00233 
00234 class ConfigObjError(SyntaxError):
00235     """
00236     This is the base class for all errors that ConfigObj raises.
00237     It is a subclass of SyntaxError.
00238     """
00239     def __init__(self, message='', line_number=None, line=''):
00240         self.line = line
00241         self.line_number = line_number
00242         self.message = message
00243         SyntaxError.__init__(self, message)
00244 
00245 class NestingError(ConfigObjError):
00246     """
00247     This error indicates a level of nesting that doesn't match.
00248     """
00249 
00250 class ParseError(ConfigObjError):
00251     """
00252     This error indicates that a line is badly written.
00253     It is neither a valid ``key = value`` line,
00254     nor a valid section marker line.
00255     """
00256 
00257 class DuplicateError(ConfigObjError):
00258     """
00259     The keyword or section specified already exists.
00260     """
00261 
00262 class ConfigspecError(ConfigObjError):
00263     """
00264     An error occurred whilst parsing a configspec.
00265     """
00266 
00267 class InterpolationError(ConfigObjError):
00268     """Base class for the two interpolation errors."""
00269 
00270 class InterpolationLoopError(InterpolationError):
00271     """Maximum interpolation depth exceeded in string interpolation."""
00272 
00273     def __init__(self, option):
00274         InterpolationError.__init__(
00275             self,
00276             'interpolation loop detected in value "%s".' % option)
00277 
00278 class RepeatSectionError(ConfigObjError):
00279     """
00280     This error indicates additional sections in a section with a
00281     ``__many__`` (repeated) section.
00282     """
00283 
00284 class MissingInterpolationOption(InterpolationError):
00285     """A value specified for interpolation was missing."""
00286 
00287     def __init__(self, option):
00288         InterpolationError.__init__(
00289             self,
00290             'missing option "%s" in interpolation.' % option)
00291 
00292 class UnreprError(ConfigObjError):
00293     """An error parsing in unrepr mode."""
00294 
00295 
00296 class InterpolationEngine(object):
00297     """
00298     A helper class to help perform string interpolation.
00299 
00300     This class is an abstract base class; its descendants perform
00301     the actual work.
00302     """
00303 
00304     # compiled regexp to use in self.interpolate()
00305     _KEYCRE = re.compile(r"%\(([^)]*)\)s")
00306 
00307     def __init__(self, section):
00308         # the Section instance that "owns" this engine
00309         self.section = section
00310 
00311     def interpolate(self, key, value):
00312         def recursive_interpolate(key, value, section, backtrail):
00313             """The function that does the actual work.
00314 
00315             ``value``: the string we're trying to interpolate.
00316             ``section``: the section in which that string was found
00317             ``backtrail``: a dict to keep track of where we've been,
00318             to detect and prevent infinite recursion loops
00319 
00320             This is similar to a depth-first-search algorithm.
00321             """
00322             # Have we been here already?
00323             if backtrail.has_key((key, section.name)):
00324                 # Yes - infinite loop detected
00325                 raise InterpolationLoopError(key)
00326             # Place a marker on our backtrail so we won't come back here again
00327             backtrail[(key, section.name)] = 1
00328 
00329             # Now start the actual work
00330             match = self._KEYCRE.search(value)
00331             while match:
00332                 # The actual parsing of the match is implementation-dependent,
00333                 # so delegate to our helper function
00334                 k, v, s = self._parse_match(match)
00335                 if k is None:
00336                     # That's the signal that no further interpolation is needed
00337                     replacement = v
00338                 else:
00339                     # Further interpolation may be needed to obtain final value
00340                     replacement = recursive_interpolate(k, v, s, backtrail)
00341                 # Replace the matched string with its final value
00342                 start, end = match.span()
00343                 value = ''.join((value[:start], replacement, value[end:]))
00344                 new_search_start = start + len(replacement)
00345                 # Pick up the next interpolation key, if any, for next time
00346                 # through the while loop
00347                 match = self._KEYCRE.search(value, new_search_start)
00348 
00349             # Now safe to come back here again; remove marker from backtrail
00350             del backtrail[(key, section.name)]
00351 
00352             return value
00353 
00354         # Back in interpolate(), all we have to do is kick off the recursive
00355         # function with appropriate starting values
00356         value = recursive_interpolate(key, value, self.section, {})
00357         return value
00358 
00359     def _fetch(self, key):
00360         """Helper function to fetch values from owning section.
00361 
00362         Returns a 2-tuple: the value, and the section where it was found.
00363         """
00364         # switch off interpolation before we try and fetch anything !
00365         save_interp = self.section.main.interpolation
00366         self.section.main.interpolation = False
00367 
00368         # Start at section that "owns" this InterpolationEngine
00369         current_section = self.section
00370         while True:
00371             # try the current section first
00372             val = current_section.get(key)
00373             if val is not None:
00374                 break
00375             # try "DEFAULT" next
00376             val = current_section.get('DEFAULT', {}).get(key)
00377             if val is not None:
00378                 break
00379             # move up to parent and try again
00380             # top-level's parent is itself
00381             if current_section.parent is current_section:
00382                 # reached top level, time to give up
00383                 break
00384             current_section = current_section.parent
00385 
00386         # restore interpolation to previous value before returning
00387         self.section.main.interpolation = save_interp
00388         if val is None:
00389             raise MissingInterpolationOption(key)
00390         return val, current_section
00391 
00392     def _parse_match(self, match):
00393         """Implementation-dependent helper function.
00394 
00395         Will be passed a match object corresponding to the interpolation
00396         key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
00397         key in the appropriate config file section (using the ``_fetch()``
00398         helper function) and return a 3-tuple: (key, value, section)
00399 
00400         ``key`` is the name of the key we're looking for
00401         ``value`` is the value found for that key
00402         ``section`` is a reference to the section where it was found
00403 
00404         ``key`` and ``section`` should be None if no further
00405         interpolation should be performed on the resulting value
00406         (e.g., if we interpolated "$$" and returned "$").
00407         """
00408         raise NotImplementedError
00409     
00410 
00411 class ConfigParserInterpolation(InterpolationEngine):
00412     """Behaves like ConfigParser."""
00413     _KEYCRE = re.compile(r"%\(([^)]*)\)s")
00414 
00415     def _parse_match(self, match):
00416         key = match.group(1)
00417         value, section = self._fetch(key)
00418         return key, value, section
00419 
00420 
00421 class TemplateInterpolation(InterpolationEngine):
00422     """Behaves like string.Template."""
00423     _delimiter = '$'
00424     _KEYCRE = re.compile(r"""
00425         \$(?:
00426           (?P<escaped>\$)              |   # Two $ signs
00427           (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
00428           {(?P<braced>[^}]*)}              # ${name} format
00429         )
00430         """, re.IGNORECASE | re.VERBOSE)
00431 
00432     def _parse_match(self, match):
00433         # Valid name (in or out of braces): fetch value from section
00434         key = match.group('named') or match.group('braced')
00435         if key is not None:
00436             value, section = self._fetch(key)
00437             return key, value, section
00438         # Escaped delimiter (e.g., $$): return single delimiter
00439         if match.group('escaped') is not None:
00440             # Return None for key and section to indicate it's time to stop
00441             return None, self._delimiter, None
00442         # Anything else: ignore completely, just return it unchanged
00443         return None, match.group(), None
00444 
00445 interpolation_engines = {
00446     'configparser': ConfigParserInterpolation,
00447     'template': TemplateInterpolation,
00448 }
00449 
00450 class Section(dict):
00451     """
00452     A dictionary-like object that represents a section in a config file.
00453     
00454     It does string interpolation if the 'interpolation' attribute
00455     of the 'main' object is set to True.
00456     
00457     Interpolation is tried first from this object, then from the 'DEFAULT'
00458     section of this object, next from the parent and its 'DEFAULT' section,
00459     and so on until the main object is reached.
00460     
00461     A Section will behave like an ordered dictionary - following the
00462     order of the ``scalars`` and ``sections`` attributes.
00463     You can use this to change the order of members.
00464     
00465     Iteration follows the order: scalars, then sections.
00466     """
00467 
00468     def __init__(self, parent, depth, main, indict=None, name=None):
00469         """
00470         * parent is the section above
00471         * depth is the depth level of this section
00472         * main is the main ConfigObj
00473         * indict is a dictionary to initialise the section with
00474         """
00475         if indict is None:
00476             indict = {}
00477         dict.__init__(self)
00478         # used for nesting level *and* interpolation
00479         self.parent = parent
00480         # used for the interpolation attribute
00481         self.main = main
00482         # level of nesting depth of this Section
00483         self.depth = depth
00484         # the sequence of scalar values in this Section
00485         self.scalars = []
00486         # the sequence of sections in this Section
00487         self.sections = []
00488         # purely for information
00489         self.name = name
00490         # for comments :-)
00491         self.comments = {}
00492         self.inline_comments = {}
00493         # for the configspec
00494         self.configspec = {}
00495         self._order = []
00496         self._configspec_comments = {}
00497         self._configspec_inline_comments = {}
00498         self._cs_section_comments = {}
00499         self._cs_section_inline_comments = {}
00500         # for defaults
00501         self.defaults = []
00502         #
00503         # we do this explicitly so that __setitem__ is used properly
00504         # (rather than just passing to ``dict.__init__``)
00505         for entry in indict:
00506             self[entry] = indict[entry]
00507 
00508     def _interpolate(self, key, value):
00509         try:
00510             # do we already have an interpolation engine?
00511             engine = self._interpolation_engine
00512         except AttributeError:
00513             # not yet: first time running _interpolate(), so pick the engine
00514             name = self.main.interpolation
00515             if name == True:  # note that "if name:" would be incorrect here
00516                 # backwards-compatibility: interpolation=True means use default
00517                 name = DEFAULT_INTERPOLATION
00518             name = name.lower()  # so that "Template", "template", etc. all work
00519             class_ = interpolation_engines.get(name, None)
00520             if class_ is None:
00521                 # invalid value for self.main.interpolation
00522                 self.main.interpolation = False
00523                 return value
00524             else:
00525                 # save reference to engine so we don't have to do this again
00526                 engine = self._interpolation_engine = class_(self)
00527         # let the engine do the actual work
00528         return engine.interpolate(key, value)
00529 
00530     def __getitem__(self, key):
00531         """Fetch the item and do string interpolation."""
00532         val = dict.__getitem__(self, key)
00533         if self.main.interpolation and isinstance(val, StringTypes):
00534             return self._interpolate(key, val)
00535         return val
00536 
00537     def __setitem__(self, key, value, unrepr=False):
00538         """
00539         Correctly set a value.
00540         
00541         Making dictionary values Section instances.
00542         (We have to special case 'Section' instances - which are also dicts)
00543         
00544         Keys must be strings.
00545         Values need only be strings (or lists of strings) if
00546         ``main.stringify`` is set.
00547         
00548         `unrepr`` must be set when setting a value to a dictionary, without
00549         creating a new sub-section.
00550         """
00551         if not isinstance(key, StringTypes):
00552             raise ValueError, 'The key "%s" is not a string.' % key
00553         # add the comment
00554         if not self.comments.has_key(key):
00555             self.comments[key] = []
00556             self.inline_comments[key] = ''
00557         # remove the entry from defaults
00558         if key in self.defaults:
00559             self.defaults.remove(key)
00560         #
00561         if isinstance(value, Section):
00562             if not self.has_key(key):
00563                 self.sections.append(key)
00564             dict.__setitem__(self, key, value)
00565         elif isinstance(value, dict) and not unrepr:
00566             # First create the new depth level,
00567             # then create the section
00568             if not self.has_key(key):
00569                 self.sections.append(key)
00570             new_depth = self.depth + 1
00571             dict.__setitem__(
00572                 self,
00573                 key,
00574                 Section(
00575                     self,
00576                     new_depth,
00577                     self.main,
00578                     indict=value,
00579                     name=key))
00580         else:
00581             if not self.has_key(key):
00582                 self.scalars.append(key)
00583             if not self.main.stringify:
00584                 if isinstance(value, StringTypes):
00585                     pass
00586                 elif isinstance(value, (list, tuple)):
00587                     for entry in value:
00588                         if not isinstance(entry, StringTypes):
00589                             raise TypeError, (
00590                                 'Value is not a string "%s".' % entry)
00591                 else:
00592                     raise TypeError, 'Value is not a string "%s".' % value
00593             dict.__setitem__(self, key, value)
00594 
00595     def __delitem__(self, key):
00596         """Remove items from the sequence when deleting."""
00597         dict. __delitem__(self, key)
00598         if key in self.scalars:
00599             self.scalars.remove(key)
00600         else:
00601             self.sections.remove(key)
00602         del self.comments[key]
00603         del self.inline_comments[key]
00604 
00605     def get(self, key, default=None):
00606         """A version of ``get`` that doesn't bypass string interpolation."""
00607         try:
00608             return self[key]
00609         except KeyError:
00610             return default
00611 
00612     def update(self, indict):
00613         """
00614         A version of update that uses our ``__setitem__``.
00615         """
00616         for entry in indict:
00617             self[entry] = indict[entry]
00618 
00619     def pop(self, key, *args):
00620         """ """
00621         val = dict.pop(self, key, *args)
00622         if key in self.scalars:
00623             del self.comments[key]
00624             del self.inline_comments[key]
00625             self.scalars.remove(key)
00626         elif key in self.sections:
00627             del self.comments[key]
00628             del self.inline_comments[key]
00629             self.sections.remove(key)
00630         if self.main.interpolation and isinstance(val, StringTypes):
00631             return self._interpolate(key, val)
00632         return val
00633 
00634     def popitem(self):
00635         """Pops the first (key,val)"""
00636         sequence = (self.scalars + self.sections)
00637         if not sequence:
00638             raise KeyError, ": 'popitem(): dictionary is empty'"
00639         key = sequence[0]
00640         val =  self[key]
00641         del self[key]
00642         return key, val
00643 
00644     def clear(self):
00645         """
00646         A version of clear that also affects scalars/sections
00647         Also clears comments and configspec.
00648         
00649         Leaves other attributes alone :
00650             depth/main/parent are not affected
00651         """
00652         dict.clear(self)
00653         self.scalars = []
00654         self.sections = []
00655         self.comments = {}
00656         self.inline_comments = {}
00657         self.configspec = {}
00658 
00659     def setdefault(self, key, default=None):
00660         """A version of setdefault that sets sequence if appropriate."""
00661         try:
00662             return self[key]
00663         except KeyError:
00664             self[key] = default
00665             return self[key]
00666 
00667     def items(self):
00668         """ """
00669         return zip((self.scalars + self.sections), self.values())
00670 
00671     def keys(self):
00672         """ """
00673         return (self.scalars + self.sections)
00674 
00675     def values(self):
00676         """ """
00677         return [self[key] for key in (self.scalars + self.sections)]
00678 
00679     def iteritems(self):
00680         """ """
00681         return iter(self.items())
00682 
00683     def iterkeys(self):
00684         """ """
00685         return iter((self.scalars + self.sections))
00686 
00687     __iter__ = iterkeys
00688 
00689     def itervalues(self):
00690         """ """
00691         return iter(self.values())
00692 
00693     def __repr__(self):
00694         return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
00695             for key in (self.scalars + self.sections)])
00696 
00697     __str__ = __repr__
00698 
00699     # Extra methods - not in a normal dictionary
00700 
00701     def dict(self):
00702         """
00703         Return a deepcopy of self as a dictionary.
00704         
00705         All members that are ``Section`` instances are recursively turned to
00706         ordinary dictionaries - by calling their ``dict`` method.
00707         
00708         >>> n = a.dict()
00709         >>> n == a
00710         1
00711         >>> n is a
00712         0
00713         """
00714         newdict = {}
00715         for entry in self:
00716             this_entry = self[entry]
00717             if isinstance(this_entry, Section):
00718                 this_entry = this_entry.dict()
00719             elif isinstance(this_entry, list):
00720                 # create a copy rather than a reference
00721                 this_entry = list(this_entry)
00722             elif isinstance(this_entry, tuple):
00723                 # create a copy rather than a reference
00724                 this_entry = tuple(this_entry)
00725             newdict[entry] = this_entry
00726         return newdict
00727 
00728     def merge(self, indict):
00729         """
00730         A recursive update - useful for merging config files.
00731         
00732         >>> a = '''[section1]
00733         ...     option1 = True
00734         ...     [[subsection]]
00735         ...     more_options = False
00736         ...     # end of file'''.splitlines()
00737         >>> b = '''# File is user.ini
00738         ...     [section1]
00739         ...     option1 = False
00740         ...     # end of file'''.splitlines()
00741         >>> c1 = ConfigObj(b)
00742         >>> c2 = ConfigObj(a)
00743         >>> c2.merge(c1)
00744         >>> c2
00745         {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
00746         """
00747         for key, val in indict.items():
00748             if (key in self and isinstance(self[key], dict) and
00749                                 isinstance(val, dict)):
00750                 self[key].merge(val)
00751             else:   
00752                 self[key] = val
00753 
00754     def rename(self, oldkey, newkey):
00755         """
00756         Change a keyname to another, without changing position in sequence.
00757         
00758         Implemented so that transformations can be made on keys,
00759         as well as on values. (used by encode and decode)
00760         
00761         Also renames comments.
00762         """
00763         if oldkey in self.scalars:
00764             the_list = self.scalars
00765         elif oldkey in self.sections:
00766             the_list = self.sections
00767         else:
00768             raise KeyError, 'Key "%s" not found.' % oldkey
00769         pos = the_list.index(oldkey)
00770         #
00771         val = self[oldkey]
00772         dict.__delitem__(self, oldkey)
00773         dict.__setitem__(self, newkey, val)
00774         the_list.remove(oldkey)
00775         the_list.insert(pos, newkey)
00776         comm = self.comments[oldkey]
00777         inline_comment = self.inline_comments[oldkey]
00778         del self.comments[oldkey]
00779         del self.inline_comments[oldkey]
00780         self.comments[newkey] = comm
00781         self.inline_comments[newkey] = inline_comment
00782 
00783     def walk(self, function, raise_errors=True,
00784             call_on_sections=False, **keywargs):
00785         """
00786         Walk every member and call a function on the keyword and value.
00787         
00788         Return a dictionary of the return values
00789         
00790         If the function raises an exception, raise the errror
00791         unless ``raise_errors=False``, in which case set the return value to
00792         ``False``.
00793         
00794         Any unrecognised keyword arguments you pass to walk, will be pased on
00795         to the function you pass in.
00796         
00797         Note: if ``call_on_sections`` is ``True`` then - on encountering a
00798         subsection, *first* the function is called for the *whole* subsection,
00799         and then recurses into its members. This means your function must be
00800         able to handle strings, dictionaries and lists. This allows you
00801         to change the key of subsections as well as for ordinary members. The
00802         return value when called on the whole subsection has to be discarded.
00803         
00804         See  the encode and decode methods for examples, including functions.
00805         
00806         .. caution::
00807         
00808             You can use ``walk`` to transform the names of members of a section
00809             but you mustn't add or delete members.
00810         
00811         >>> config = '''[XXXXsection]
00812         ... XXXXkey = XXXXvalue'''.splitlines()
00813         >>> cfg = ConfigObj(config)
00814         >>> cfg
00815         {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
00816         >>> def transform(section, key):
00817         ...     val = section[key]
00818         ...     newkey = key.replace('XXXX', 'CLIENT1')
00819         ...     section.rename(key, newkey)
00820         ...     if isinstance(val, (tuple, list, dict)):
00821         ...         pass
00822         ...     else:
00823         ...         val = val.replace('XXXX', 'CLIENT1')
00824         ...         section[newkey] = val
00825         >>> cfg.walk(transform, call_on_sections=True)
00826         {'CLIENT1section': {'CLIENT1key': None}}
00827         >>> cfg
00828         {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
00829         """
00830         out = {}
00831         # scalars first
00832         for i in range(len(self.scalars)):
00833             entry = self.scalars[i]
00834             try:
00835                 val = function(self, entry, **keywargs)
00836                 # bound again in case name has changed
00837                 entry = self.scalars[i]
00838                 out[entry] = val
00839             except Exception:
00840                 if raise_errors:
00841                     raise
00842                 else:
00843                     entry = self.scalars[i]
00844                     out[entry] = False
00845         # then sections
00846         for i in range(len(self.sections)):
00847             entry = self.sections[i]
00848             if call_on_sections:
00849                 try:
00850                     function(self, entry, **keywargs)
00851                 except Exception:
00852                     if raise_errors:
00853                         raise
00854                     else:
00855                         entry = self.sections[i]
00856                         out[entry] = False
00857                 # bound again in case name has changed
00858                 entry = self.sections[i]
00859             # previous result is discarded
00860             out[entry] = self[entry].walk(
00861                 function,
00862                 raise_errors=raise_errors,
00863                 call_on_sections=call_on_sections,
00864                 **keywargs)
00865         return out
00866 
00867     def decode(self, encoding):
00868         """
00869         Decode all strings and values to unicode, using the specified encoding.
00870         
00871         Works with subsections and list values.
00872         
00873         Uses the ``walk`` method.
00874         
00875         Testing ``encode`` and ``decode``.
00876         >>> m = ConfigObj(a)
00877         >>> m.decode('ascii')
00878         >>> def testuni(val):
00879         ...     for entry in val:
00880         ...         if not isinstance(entry, unicode):
00881         ...             print >> sys.stderr, type(entry)
00882         ...             raise AssertionError, 'decode failed.'
00883         ...         if isinstance(val[entry], dict):
00884         ...             testuni(val[entry])
00885         ...         elif not isinstance(val[entry], unicode):
00886         ...             raise AssertionError, 'decode failed.'
00887         >>> testuni(m)
00888         >>> m.encode('ascii')
00889         >>> a == m
00890         1
00891         """
00892         warn('use of ``decode`` is deprecated.', DeprecationWarning)
00893         def decode(section, key, encoding=encoding, warn=True):
00894             """ """
00895             val = section[key]
00896             if isinstance(val, (list, tuple)):
00897                 newval = []
00898                 for entry in val:
00899                     newval.append(entry.decode(encoding))
00900             elif isinstance(val, dict):
00901                 newval = val
00902             else:
00903                 newval = val.decode(encoding)
00904             newkey = key.decode(encoding)
00905             section.rename(key, newkey)
00906             section[newkey] = newval
00907         # using ``call_on_sections`` allows us to modify section names
00908         self.walk(decode, call_on_sections=True)
00909 
00910     def encode(self, encoding):
00911         """
00912         Encode all strings and values from unicode,
00913         using the specified encoding.
00914         
00915         Works with subsections and list values.
00916         Uses the ``walk`` method.
00917         """
00918         warn('use of ``encode`` is deprecated.', DeprecationWarning)
00919         def encode(section, key, encoding=encoding):
00920             """ """
00921             val = section[key]
00922             if isinstance(val, (list, tuple)):
00923                 newval = []
00924                 for entry in val:
00925                     newval.append(entry.encode(encoding))
00926             elif isinstance(val, dict):
00927                 newval = val
00928             else:
00929                 newval = val.encode(encoding)
00930             newkey = key.encode(encoding)
00931             section.rename(key, newkey)
00932             section[newkey] = newval
00933         self.walk(encode, call_on_sections=True)
00934 
00935     def istrue(self, key):
00936         """A deprecated version of ``as_bool``."""
00937         warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
00938                 'instead.', DeprecationWarning)
00939         return self.as_bool(key)
00940 
00941     def as_bool(self, key):
00942         """
00943         Accepts a key as input. The corresponding value must be a string or
00944         the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
00945         retain compatibility with Python 2.2.
00946         
00947         If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
00948         ``True``.
00949         
00950         If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
00951         ``False``.
00952         
00953         ``as_bool`` is not case sensitive.
00954         
00955         Any other input will raise a ``ValueError``.
00956         
00957         >>> a = ConfigObj()
00958         >>> a['a'] = 'fish'
00959         >>> a.as_bool('a')
00960         Traceback (most recent call last):
00961         ValueError: Value "fish" is neither True nor False
00962         >>> a['b'] = 'True'
00963         >>> a.as_bool('b')
00964         1
00965         >>> a['b'] = 'off'
00966         >>> a.as_bool('b')
00967         0
00968         """
00969         val = self[key]
00970         if val == True:
00971             return True
00972         elif val == False:
00973             return False
00974         else:
00975             try:
00976                 if not isinstance(val, StringTypes):
00977                     raise KeyError
00978                 else:
00979                     return self.main._bools[val.lower()]
00980             except KeyError:
00981                 raise ValueError('Value "%s" is neither True nor False' % val)
00982 
00983     def as_int(self, key):
00984         """
00985         A convenience method which coerces the specified value to an integer.
00986         
00987         If the value is an invalid literal for ``int``, a ``ValueError`` will
00988         be raised.
00989         
00990         >>> a = ConfigObj()
00991         >>> a['a'] = 'fish'
00992         >>> a.as_int('a')
00993         Traceback (most recent call last):
00994         ValueError: invalid literal for int(): fish
00995         >>> a['b'] = '1'
00996         >>> a.as_int('b')
00997         1
00998         >>> a['b'] = '3.2'
00999         >>> a.as_int('b')
01000         Traceback (most recent call last):
01001         ValueError: invalid literal for int(): 3.2
01002         """
01003         return int(self[key])
01004 
01005     def as_float(self, key):
01006         """
01007         A convenience method which coerces the specified value to a float.
01008         
01009         If the value is an invalid literal for ``float``, a ``ValueError`` will
01010         be raised.
01011         
01012         >>> a = ConfigObj()
01013         >>> a['a'] = 'fish'
01014         >>> a.as_float('a')
01015         Traceback (most recent call last):
01016         ValueError: invalid literal for float(): fish
01017         >>> a['b'] = '1'
01018         >>> a.as_float('b')
01019         1.0
01020         >>> a['b'] = '3.2'
01021         >>> a.as_float('b')
01022         3.2000000000000002
01023         """
01024         return float(self[key])
01025     
01026 
01027 class ConfigObj(Section):
01028     """An object to read, create, and write config files."""
01029 
01030     _keyword = re.compile(r'''^ # line start
01031         (\s*)                   # indentation
01032         (                       # keyword
01033             (?:".*?")|          # double quotes
01034             (?:'.*?')|          # single quotes
01035             (?:[^'"=].*?)       # no quotes
01036         )
01037         \s*=\s*                 # divider
01038         (.*)                    # value (including list values and comments)
01039         $   # line end
01040         ''',
01041         re.VERBOSE)
01042 
01043     _sectionmarker = re.compile(r'''^
01044         (\s*)                     # 1: indentation
01045         ((?:\[\s*)+)              # 2: section marker open
01046         (                         # 3: section name open
01047             (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
01048             (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
01049             (?:[^'"\s].*?)        # at least one non-space unquoted
01050         )                         # section name close
01051         ((?:\s*\])+)              # 4: section marker close
01052         \s*(\#.*)?                # 5: optional comment
01053         $''',
01054         re.VERBOSE)
01055 
01056     # this regexp pulls list values out as a single string
01057     # or single values and comments
01058     # FIXME: this regex adds a '' to the end of comma terminated lists
01059     #   workaround in ``_handle_value``
01060     _valueexp = re.compile(r'''^
01061         (?:
01062             (?:
01063                 (
01064                     (?:
01065                         (?:
01066                             (?:".*?")|              # double quotes
01067                             (?:'.*?')|              # single quotes
01068                             (?:[^'",\#][^,\#]*?)    # unquoted
01069                         )
01070                         \s*,\s*                     # comma
01071                     )*      # match all list items ending in a comma (if any)
01072                 )
01073                 (
01074                     (?:".*?")|                      # double quotes
01075                     (?:'.*?')|                      # single quotes
01076                     (?:[^'",\#\s][^,]*?)|           # unquoted
01077                     (?:(?<!,))                      # Empty value
01078                 )?          # last item in a list - or string value
01079             )|
01080             (,)             # alternatively a single comma - empty list
01081         )
01082         \s*(\#.*)?          # optional comment
01083         $''',
01084         re.VERBOSE)
01085 
01086     # use findall to get the members of a list value
01087     _listvalueexp = re.compile(r'''
01088         (
01089             (?:".*?")|          # double quotes
01090             (?:'.*?')|          # single quotes
01091             (?:[^'",\#].*?)       # unquoted
01092         )
01093         \s*,\s*                 # comma
01094         ''',
01095         re.VERBOSE)
01096 
01097     # this regexp is used for the value
01098     # when lists are switched off
01099     _nolistvalue = re.compile(r'''^
01100         (
01101             (?:".*?")|          # double quotes
01102             (?:'.*?')|          # single quotes
01103             (?:[^'"\#].*?)|     # unquoted
01104             (?:)                # Empty value
01105         )
01106         \s*(\#.*)?              # optional comment
01107         $''',
01108         re.VERBOSE)
01109 
01110     # regexes for finding triple quoted values on one line
01111     _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
01112     _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
01113     _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
01114     _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
01115 
01116     _triple_quote = {
01117         "'''": (_single_line_single, _multi_line_single),
01118         '"""': (_single_line_double, _multi_line_double),
01119     }
01120 
01121     # Used by the ``istrue`` Section method
01122     _bools = {
01123         'yes': True, 'no': False,
01124         'on': True, 'off': False,
01125         '1': True, '0': False,
01126         'true': True, 'false': False,
01127         }
01128 
01129     def __init__(self, infile=None, options=None, **kwargs):
01130         """
01131         Parse or create a config file object.
01132         
01133         ``ConfigObj(infile=None, options=None, **kwargs)``
01134         """
01135         if infile is None:
01136             infile = []
01137         if options is None:
01138             options = {}
01139         else:
01140             options = dict(options)
01141         # keyword arguments take precedence over an options dictionary
01142         options.update(kwargs)
01143         # init the superclass
01144         Section.__init__(self, self, 0, self)
01145         #
01146         defaults = OPTION_DEFAULTS.copy()
01147         for entry in options.keys():
01148             if entry not in defaults.keys():
01149                 raise TypeError, 'Unrecognised option "%s".' % entry
01150         # TODO: check the values too.
01151         #
01152         # Add any explicit options to the defaults
01153         defaults.update(options)
01154         #
01155         # initialise a few variables
01156         self.filename = None
01157         self._errors = []
01158         self.raise_errors = defaults['raise_errors']
01159         self.interpolation = defaults['interpolation']
01160         self.list_values = defaults['list_values']
01161         self.create_empty = defaults['create_empty']
01162         self.file_error = defaults['file_error']
01163         self.stringify = defaults['stringify']
01164         self.indent_type = defaults['indent_type']
01165         self.encoding = defaults['encoding']
01166         self.default_encoding = defaults['default_encoding']
01167         self.BOM = False
01168         self.newlines = None
01169         self.write_empty_values = defaults['write_empty_values']
01170         self.unrepr = defaults['unrepr']
01171         #
01172         self.initial_comment = []
01173         self.final_comment = []
01174         #
01175         self._terminated = False
01176         #
01177         if isinstance(infile, StringTypes):
01178             self.filename = infile
01179             if os.path.isfile(infile):
01180                 infile = open(infile).read() or []
01181             elif self.file_error:
01182                 # raise an error if the file doesn't exist
01183                 raise IOError, 'Config file not found: "%s".' % self.filename
01184             else:
01185                 # file doesn't already exist
01186                 if self.create_empty:
01187                     # this is a good test that the filename specified
01188                     # isn't impossible - like on a non existent device
01189                     h = open(infile, 'w')
01190                     h.write('')
01191                     h.close()
01192                 infile = []
01193         elif isinstance(infile, (list, tuple)):
01194             infile = list(infile)
01195         elif isinstance(infile, dict):
01196             # initialise self
01197             # the Section class handles creating subsections
01198             if isinstance(infile, ConfigObj):
01199                 # get a copy of our ConfigObj
01200                 infile = infile.dict()
01201             for entry in infile:
01202                 self[entry] = infile[entry]
01203             del self._errors
01204             if defaults['configspec'] is not None:
01205                 self._handle_configspec(defaults['configspec'])
01206             else:
01207                 self.configspec = None
01208             return
01209         elif hasattr(infile, 'read'):
01210             # This supports file like objects
01211             infile = infile.read() or []
01212             # needs splitting into lines - but needs doing *after* decoding
01213             # in case it's not an 8 bit encoding
01214         else:
01215             raise TypeError, ('infile must be a filename,'
01216                 ' file like object, or list of lines.')
01217         #
01218         if infile:
01219             # don't do it for the empty ConfigObj
01220             infile = self._handle_bom(infile)
01221             # infile is now *always* a list
01222             #
01223             # Set the newlines attribute (first line ending it finds)
01224             # and strip trailing '\n' or '\r' from lines
01225             for line in infile:
01226                 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
01227                     continue
01228                 for end in ('\r\n', '\n', '\r'):
01229                     if line.endswith(end):
01230                         self.newlines = end
01231                         break
01232                 break
01233             if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'):
01234                 self._terminated = True
01235             infile = [line.rstrip('\r\n') for line in infile]
01236         #
01237         self._parse(infile)
01238         # if we had any errors, now is the time to raise them
01239         if self._errors:
01240             info = "at line %s." % self._errors[0].line_number
01241             if len(self._errors) > 1:
01242                 msg = ("Parsing failed with several errors.\nFirst error %s" %
01243                     info)
01244                 error = ConfigObjError(msg)
01245             else:
01246                 error = self._errors[0]
01247             # set the errors attribute; it's a list of tuples:
01248             # (error_type, message, line_number)
01249             error.errors = self._errors
01250             # set the config attribute
01251             error.config = self
01252             raise error
01253         # delete private attributes
01254         del self._errors
01255         #
01256         if defaults['configspec'] is None:
01257             self.configspec = None
01258         else:
01259             self._handle_configspec(defaults['configspec'])
01260     
01261     def __repr__(self):
01262         return 'ConfigObj({%s})' % ', '.join(
01263             [('%s: %s' % (repr(key), repr(self[key]))) for key in
01264             (self.scalars + self.sections)])
01265     
01266     def _handle_bom(self, infile):
01267         """
01268         Handle any BOM, and decode if necessary.
01269         
01270         If an encoding is specified, that *must* be used - but the BOM should
01271         still be removed (and the BOM attribute set).
01272         
01273         (If the encoding is wrongly specified, then a BOM for an alternative
01274         encoding won't be discovered or removed.)
01275         
01276         If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
01277         removed. The BOM attribute will be set. UTF16 will be decoded to
01278         unicode.
01279         
01280         NOTE: This method must not be called with an empty ``infile``.
01281         
01282         Specifying the *wrong* encoding is likely to cause a
01283         ``UnicodeDecodeError``.
01284         
01285         ``infile`` must always be returned as a list of lines, but may be
01286         passed in as a single string.
01287         """
01288         if ((self.encoding is not None) and
01289             (self.encoding.lower() not in BOM_LIST)):
01290             # No need to check for a BOM
01291             # the encoding specified doesn't have one
01292             # just decode
01293             return self._decode(infile, self.encoding)
01294         #
01295         if isinstance(infile, (list, tuple)):
01296             line = infile[0]
01297         else:
01298             line = infile
01299         if self.encoding is not None:
01300             # encoding explicitly supplied
01301             # And it could have an associated BOM
01302             # TODO: if encoding is just UTF16 - we ought to check for both
01303             # TODO: big endian and little endian versions.
01304             enc = BOM_LIST[self.encoding.lower()]
01305             if enc == 'utf_16':
01306                 # For UTF16 we try big endian and little endian
01307                 for BOM, (encoding, final_encoding) in BOMS.items():
01308                     if not final_encoding:
01309                         # skip UTF8
01310                         continue
01311                     if infile.startswith(BOM):
01312                         ### BOM discovered
01313                         ##self.BOM = True
01314                         # Don't need to remove BOM
01315                         return self._decode(infile, encoding)
01316                 #
01317                 # If we get this far, will *probably* raise a DecodeError
01318                 # As it doesn't appear to start with a BOM
01319                 return self._decode(infile, self.encoding)
01320             #
01321             # Must be UTF8
01322             BOM = BOM_SET[enc]
01323             if not line.startswith(BOM):
01324                 return self._decode(infile, self.encoding)
01325             #
01326             newline = line[len(BOM):]
01327             #
01328             # BOM removed
01329             if isinstance(infile, (list, tuple)):
01330                 infile[0] = newline
01331             else:
01332                 infile = newline
01333             self.BOM = True
01334             return self._decode(infile, self.encoding)
01335         #
01336         # No encoding specified - so we need to check for UTF8/UTF16
01337         for BOM, (encoding, final_encoding) in BOMS.items():
01338             if not line.startswith(BOM):
01339                 continue
01340             else:
01341                 # BOM discovered
01342                 self.encoding = final_encoding
01343                 if not final_encoding:
01344                     self.BOM = True
01345                     # UTF8
01346                     # remove BOM
01347                     newline = line[len(BOM):]
01348                     if isinstance(infile, (list, tuple)):
01349                         infile[0] = newline
01350                     else:
01351                         infile = newline
01352                     # UTF8 - don't decode
01353                     if isinstance(infile, StringTypes):
01354                         return infile.splitlines(True)
01355                     else:
01356                         return infile
01357                 # UTF16 - have to decode
01358                 return self._decode(infile, encoding)
01359         #
01360         # No BOM discovered and no encoding specified, just return
01361         if isinstance(infile, StringTypes):
01362             # infile read from a file will be a single string
01363             return infile.splitlines(True)
01364         else:
01365             return infile
01366 
01367     def _a_to_u(self, aString):
01368         """Decode ASCII strings to unicode if a self.encoding is specified."""
01369         if self.encoding:
01370             return aString.decode('ascii')
01371         else:
01372             return aString
01373 
01374     def _decode(self, infile, encoding):
01375         """
01376         Decode infile to unicode. Using the specified encoding.
01377         
01378         if is a string, it also needs converting to a list.
01379         """
01380         if isinstance(infile, StringTypes):
01381             # can't be unicode
01382             # NOTE: Could raise a ``UnicodeDecodeError``
01383             return infile.decode(encoding).splitlines(True)
01384         for i, line in enumerate(infile):
01385             if not isinstance(line, unicode):
01386                 # NOTE: The isinstance test here handles mixed lists of unicode/string
01387                 # NOTE: But the decode will break on any non-string values
01388                 # NOTE: Or could raise a ``UnicodeDecodeError``
01389                 infile[i] = line.decode(encoding)
01390         return infile
01391 
01392     def _decode_element(self, line):
01393         """Decode element to unicode if necessary."""
01394         if not self.encoding:
01395             return line
01396         if isinstance(line, str) and self.default_encoding:
01397             return line.decode(self.default_encoding)
01398         return line
01399 
01400     def _str(self, value):
01401         """
01402         Used by ``stringify`` within validate, to turn non-string values
01403         into strings.
01404         """
01405         if not isinstance(value, StringTypes):
01406             return str(value)
01407         else:
01408             return value
01409 
01410     def _parse(self, infile):
01411         """Actually parse the config file."""
01412         temp_list_values = self.list_values
01413         if self.unrepr:
01414             self.list_values = False
01415         comment_list = []
01416         done_start = False
01417         this_section = self
01418         maxline = len(infile) - 1
01419         cur_index = -1
01420         reset_comment = False
01421         while cur_index < maxline:
01422             if reset_comment:
01423                 comment_list = []
01424             cur_index += 1
01425             line = infile[cur_index]
01426             sline = line.strip()
01427             # do we have anything on the line ?
01428             if not sline or sline.startswith('#') or sline.startswith(';'):
01429                 reset_comment = False
01430                 comment_list.append(line)
01431                 continue
01432             if not done_start:
01433                 # preserve initial comment
01434                 self.initial_comment = comment_list
01435                 comment_list = []
01436                 done_start = True
01437             reset_comment = True
01438             # first we check if it's a section marker
01439             mat = self._sectionmarker.match(line)
01440             if mat is not None:
01441                 # is a section line
01442                 (indent, sect_open, sect_name, sect_close, comment) = (
01443                     mat.groups())
01444                 if indent and (self.indent_type is None):
01445                     self.indent_type = indent
01446                 cur_depth = sect_open.count('[')
01447                 if cur_depth != sect_close.count(']'):
01448                     self._handle_error(
01449                         "Cannot compute the section depth at line %s.",
01450                         NestingError, infile, cur_index)
01451                     continue
01452                 #
01453                 if cur_depth < this_section.depth:
01454                     # the new section is dropping back to a previous level
01455                     try:
01456                         parent = self._match_depth(
01457                             this_section,
01458                             cur_depth).parent
01459                     except SyntaxError:
01460                         self._handle_error(
01461                             "Cannot compute nesting level at line %s.",
01462                             NestingError, infile, cur_index)
01463                         continue
01464                 elif cur_depth == this_section.depth:
01465                     # the new section is a sibling of the current section
01466                     parent = this_section.parent
01467                 elif cur_depth == this_section.depth + 1:
01468                     # the new section is a child the current section
01469                     parent = this_section
01470                 else:
01471                     self._handle_error(
01472                         "Section too nested at line %s.",
01473                         NestingError, infile, cur_index)
01474                 #
01475                 sect_name = self._unquote(sect_name)
01476                 if parent.has_key(sect_name):
01477                     self._handle_error(
01478                         'Duplicate section name at line %s.',
01479                         DuplicateError, infile, cur_index)
01480                     continue
01481                 # create the new section
01482                 this_section = Section(
01483                     parent,
01484                     cur_depth,
01485                     self,
01486                     name=sect_name)
01487                 parent[sect_name] = this_section
01488                 parent.inline_comments[sect_name] = comment
01489                 parent.comments[sect_name] = comment_list
01490                 continue
01491             #
01492             # it's not a section marker,
01493             # so it should be a valid ``key = value`` line
01494             mat = self._keyword.match(line)
01495             if mat is None:
01496                 # it neither matched as a keyword
01497                 # or a section marker
01498                 self._handle_error(
01499                     'Invalid line at line "%s".',
01500                     ParseError, infile, cur_index)
01501             else:
01502                 # is a keyword value
01503                 # value will include any inline comment
01504                 (indent, key, value) = mat.groups()
01505                 if indent and (self.indent_type is None):
01506                     self.indent_type = indent
01507                 # check for a multiline value
01508                 if value[:3] in ['"""', "'''"]:
01509                     try:
01510                         (value, comment, cur_index) = self._multiline(
01511                             value, infile, cur_index, maxline)
01512                     except SyntaxError:
01513                         self._handle_error(
01514                             'Parse error in value at line %s.',
01515                             ParseError, infile, cur_index)
01516                         continue
01517                     else:
01518                         if self.unrepr:
01519                             comment = ''
01520                             try:
01521                                 value = unrepr(value)
01522                             except Exception, e:
01523                                 if type(e) == UnknownType:
01524                                     msg = 'Unknown name or type in value at line %s.'
01525                                 else:
01526                                     msg = 'Parse error in value at line %s.'
01527                                 self._handle_error(msg, UnreprError, infile,
01528                                     cur_index)
01529                                 continue
01530                 else:
01531                     if self.unrepr:
01532                         comment = ''
01533                         try:
01534                             value = unrepr(value)
01535                         except Exception, e:
01536                             if isinstance(e, UnknownType):
01537                                 msg = 'Unknown name or type in value at line %s.'
01538                             else:
01539                                 msg = 'Parse error in value at line %s.'
01540                             self._handle_error(msg, UnreprError, infile,
01541                                 cur_index)
01542                             continue
01543                     else:
01544                         # extract comment and lists
01545                         try:
01546                             (value, comment) = self._handle_value(value)
01547                         except SyntaxError:
01548                             self._handle_error(
01549                                 'Parse error in value at line %s.',
01550                                 ParseError, infile, cur_index)
01551                             continue
01552                 #
01553                 key = self._unquote(key)
01554                 if this_section.has_key(key):
01555                     self._handle_error(
01556                         'Duplicate keyword name at line %s.',
01557                         DuplicateError, infile, cur_index)
01558                     continue
01559                 # add the key.
01560                 # we set unrepr because if we have got this far we will never
01561                 # be creating a new section
01562                 this_section.__setitem__(key, value, unrepr=True)
01563                 this_section.inline_comments[key] = comment
01564                 this_section.comments[key] = comment_list
01565                 continue
01566         #
01567         if self.indent_type is None:
01568             # no indentation used, set the type accordingly
01569             self.indent_type = ''
01570         #
01571         if self._terminated:
01572             comment_list.append('')
01573         # preserve the final comment
01574         if not self and not self.initial_comment:
01575             self.initial_comment = comment_list
01576         elif not reset_comment:
01577             self.final_comment = comment_list
01578         self.list_values = temp_list_values
01579 
01580     def _match_depth(self, sect, depth):
01581         """
01582         Given a section and a depth level, walk back through the sections
01583         parents to see if the depth level matches a previous section.
01584         
01585         Return a reference to the right section,
01586         or raise a SyntaxError.
01587         """
01588         while depth < sect.depth:
01589             if sect is sect.parent:
01590                 # we've reached the top level already
01591                 raise SyntaxError
01592             sect = sect.parent
01593         if sect.depth == depth:
01594             return sect
01595         # shouldn't get here
01596         raise SyntaxError
01597 
01598     def _handle_error(self, text, ErrorClass, infile, cur_index):
01599         """
01600         Handle an error according to the error settings.
01601         
01602         Either raise the error or store it.
01603         The error will have occurred at ``cur_index``
01604         """
01605         line = infile[cur_index]
01606         cur_index += 1
01607         message = text % cur_index
01608         error = ErrorClass(message, cur_index, line)
01609         if self.raise_errors:
01610             # raise the error - parsing stops here
01611             raise error
01612         # store the error
01613         # reraise when parsing has finished
01614         self._errors.append(error)
01615 
01616     def _unquote(self, value):
01617         """Return an unquoted version of a value"""
01618         if (value[0] == value[-1]) and (value[0] in ('"', "'")):
01619             value = value[1:-1]
01620         return value
01621 
01622     def _quote(self, value, multiline=True):
01623         """
01624         Return a safely quoted version of a value.
01625         
01626         Raise a ConfigObjError if the value cannot be safely quoted.
01627         If multiline is ``True`` (default) then use triple quotes
01628         if necessary.
01629         
01630         Don't quote values that don't need it.
01631         Recursively quote members of a list and return a comma joined list.
01632         Multiline is ``False`` for lists.
01633         Obey list syntax for empty and single member lists.
01634         
01635         If ``list_values=False`` then the value is only quoted if it contains
01636         a ``\n`` (is multiline).
01637         
01638         If ``write_empty_values`` is set, and the value is an empty string, it
01639         won't be quoted.
01640         """
01641         if multiline and self.write_empty_values and value == '':
01642             # Only if multiline is set, so that it is used for values not
01643             # keys, and not values that are part of a list
01644             return ''
01645         if multiline and isinstance(value, (list, tuple)):
01646             if not value:
01647                 return ','
01648             elif len(value) == 1:
01649                 return self._quote(value[0], multiline=False) + ','
01650             return ', '.join([self._quote(val, multiline=False)
01651                 for val in value])
01652         if not isinstance(value, StringTypes):
01653             if self.stringify:
01654                 value = str(value)
01655             else:
01656                 raise TypeError, 'Value "%s" is not a string.' % value
01657         squot = "'%s'"
01658         dquot = '"%s"'
01659         noquot = "%s"
01660         wspace_plus = ' \r\t\n\v\t\'"'
01661         tsquot = '"""%s"""'
01662         tdquot = "'''%s'''"
01663         if not value:
01664             return '""'
01665         if (not self.list_values and '\n' not in value) or not (multiline and
01666                 ((("'" in value) and ('"' in value)) or ('\n' in value))):
01667             if not self.list_values:
01668                 # we don't quote if ``list_values=False``
01669                 quot = noquot
01670             # for normal values either single or double quotes will do
01671             elif '\n' in value:
01672                 # will only happen if multiline is off - e.g. '\n' in key
01673                 raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
01674                     value)
01675             elif ((value[0] not in wspace_plus) and
01676                     (value[-1] not in wspace_plus) and
01677                     (',' not in value)):
01678                 quot = noquot
01679             else:
01680                 if ("'" in value) and ('"' in value):
01681                     raise ConfigObjError, (
01682                         'Value "%s" cannot be safely quoted.' % value)
01683                 elif '"' in value:
01684                     quot = squot
01685                 else:
01686                     quot = dquot
01687         else:
01688             # if value has '\n' or "'" *and* '"', it will need triple quotes
01689             if (value.find('"""') != -1) and (value.find("'''") != -1):
01690                 raise ConfigObjError, (
01691                     'Value "%s" cannot be safely quoted.' % value)
01692             if value.find('"""') == -1:
01693                 quot = tdquot
01694             else:
01695                 quot = tsquot
01696         return quot % value
01697 
01698     def _handle_value(self, value):
01699         """
01700         Given a value string, unquote, remove comment,
01701         handle lists. (including empty and single member lists)
01702         """
01703         # do we look for lists in values ?
01704         if not self.list_values:
01705             mat = self._nolistvalue.match(value)
01706             if mat is None:
01707                 raise SyntaxError
01708             # NOTE: we don't unquote here
01709             return mat.groups()
01710         #
01711         mat = self._valueexp.match(value)
01712         if mat is None:
01713             # the value is badly constructed, probably badly quoted,
01714             # or an invalid list
01715             raise SyntaxError
01716         (list_values, single, empty_list, comment) = mat.groups()
01717         if (list_values == '') and (single is None):
01718             # change this if you want to accept empty values
01719             raise SyntaxError
01720         # NOTE: note there is no error handling from here if the regex
01721         # is wrong: then incorrect values will slip through
01722         if empty_list is not None:
01723             # the single comma - meaning an empty list
01724             return ([], comment)
01725         if single is not None:
01726             # handle empty values
01727             if list_values and not single:
01728                 # FIXME: the '' is a workaround because our regex now matches
01729                 #   '' at the end of a list if it has a trailing comma
01730                 single = None
01731             else:
01732                 single = single or '""'
01733                 single = self._unquote(single)
01734         if list_values == '':
01735             # not a list value
01736             return (single, comment)
01737         the_list = self._listvalueexp.findall(list_values)
01738         the_list = [self._unquote(val) for val in the_list]
01739         if single is not None:
01740             the_list += [single]
01741         return (the_list, comment)
01742 
01743     def _multiline(self, value, infile, cur_index, maxline):
01744         """Extract the value, where we are in a multiline situation."""
01745         quot = value[:3]
01746         newvalue = value[3:]
01747         single_line = self._triple_quote[quot][0]
01748         multi_line = self._triple_quote[quot][1]
01749         mat = single_line.match(value)
01750         if mat is not None:
01751             retval = list(mat.groups())
01752             retval.append(cur_index)
01753             return retval
01754         elif newvalue.find(quot) != -1:
01755             # somehow the triple quote is missing
01756             raise SyntaxError
01757         #
01758         while cur_index < maxline:
01759             cur_index += 1
01760             newvalue += '\n'
01761             line = infile[cur_index]
01762             if line.find(quot) == -1:
01763                 newvalue += line
01764             else:
01765                 # end of multiline, process it
01766                 break
01767         else:
01768             # we've got to the end of the config, oops...
01769             raise SyntaxError
01770         mat = multi_line.match(line)
01771         if mat is None:
01772             # a badly formed line
01773             raise SyntaxError
01774         (value, comment) = mat.groups()
01775         return (newvalue + value, comment, cur_index)
01776 
01777     def _handle_configspec(self, configspec):
01778         """Parse the configspec."""
01779         # FIXME: Should we check that the configspec was created with the 
01780         #   correct settings ? (i.e. ``list_values=False``)
01781         if not isinstance(configspec, ConfigObj):
01782             try:
01783                 configspec = ConfigObj(
01784                     configspec,
01785                     raise_errors=True,
01786                     file_error=True,
01787                     list_values=False)
01788             except ConfigObjError, e:
01789                 # FIXME: Should these errors have a reference
01790                 # to the already parsed ConfigObj ?
01791                 raise ConfigspecError('Parsing configspec failed: %s' % e)
01792             except IOError, e:
01793                 raise IOError('Reading configspec failed: %s' % e)
01794         self._set_configspec_value(configspec, self)
01795 
01796     def _set_configspec_value(self, configspec, section):
01797         """Used to recursively set configspec values."""
01798         if '__many__' in configspec.sections:
01799             section.configspec['__many__'] = configspec['__many__']
01800             if len(configspec.sections) > 1:
01801                 # FIXME: can we supply any useful information here ?
01802                 raise RepeatSectionError
01803         if hasattr(configspec, 'initial_comment'):
01804             section._configspec_initial_comment = configspec.initial_comment
01805             section._configspec_final_comment = configspec.final_comment
01806             section._configspec_encoding = configspec.encoding
01807             section._configspec_BOM = configspec.BOM
01808             section._configspec_newlines = configspec.newlines
01809             section._configspec_indent_type = configspec.indent_type
01810         for entry in configspec.scalars:
01811             section._configspec_comments[entry] = configspec.comments[entry]
01812             section._configspec_inline_comments[entry] = (
01813                 configspec.inline_comments[entry])
01814             section.configspec[entry] = configspec[entry]
01815             section._order.append(entry)
01816         for entry in configspec.sections:
01817             if entry == '__many__':
01818                 continue
01819             section._cs_section_comments[entry] = configspec.comments[entry]
01820             section._cs_section_inline_comments[entry] = (
01821                 configspec.inline_comments[entry])
01822             if not section.has_key(entry):
01823                 section[entry] = {}
01824             self._set_configspec_value(configspec[entry], section[entry])
01825 
01826     def _handle_repeat(self, section, configspec):
01827         """Dynamically assign configspec for repeated section."""
01828         try:
01829             section_keys = configspec.sections
01830             scalar_keys = configspec.scalars
01831         except AttributeError:
01832             section_keys = [entry for entry in configspec 
01833                                 if isinstance(configspec[entry], dict)]
01834             scalar_keys = [entry for entry in configspec 
01835                                 if not isinstance(configspec[entry], dict)]
01836         if '__many__' in section_keys and len(section_keys) > 1:
01837             # FIXME: can we supply any useful information here ?
01838             raise RepeatSectionError
01839         scalars = {}
01840         sections = {}
01841         for entry in scalar_keys:
01842             val = configspec[entry]
01843             scalars[entry] = val
01844         for entry in section_keys:
01845             val = configspec[entry]
01846             if entry == '__many__':
01847                 scalars[entry] = val
01848                 continue
01849             sections[entry] = val
01850         #
01851         section.configspec = scalars
01852         for entry in sections:
01853             if not section.has_key(entry):
01854                 section[entry] = {}
01855             self._handle_repeat(section[entry], sections[entry])
01856 
01857     def _write_line(self, indent_string, entry, this_entry, comment):
01858         """Write an individual line, for the write method"""
01859         # NOTE: the calls to self._quote here handles non-StringType values.
01860         if not self.unrepr:
01861             val = self._decode_element(self._quote(this_entry))
01862         else:
01863             val = repr(this_entry)
01864         return '%s%s%s%s%s' % (
01865             indent_string,
01866             self._decode_element(self._quote(entry, multiline=False)),
01867             self._a_to_u(' = '),
01868             val,
01869             self._decode_element(comment))
01870 
01871     def _write_marker(self, indent_string, depth, entry, comment):
01872         """Write a section marker line"""
01873         return '%s%s%s%s%s' % (
01874             indent_string,
01875             self._a_to_u('[' * depth),
01876             self._quote(self._decode_element(entry), multiline=False),
01877             self._a_to_u(']' * depth),
01878             self._decode_element(comment))
01879 
01880     def _handle_comment(self, comment):
01881         """Deal with a comment."""
01882         if not comment:
01883             return ''
01884         start = self.indent_type
01885         if not comment.startswith('#'):
01886             start += self._a_to_u(' # ')
01887         return (start + comment)
01888 
01889     # Public methods
01890 
01891     def write(self, outfile=None, section=None):
01892         """
01893         Write the current ConfigObj as a file
01894         
01895         tekNico: FIXME: use StringIO instead of real files
01896         
01897         >>> filename = a.filename
01898         >>> a.filename = 'test.ini'
01899         >>> a.write()
01900         >>> a.filename = filename
01901         >>> a == ConfigObj('test.ini', raise_errors=True)
01902         1
01903         """
01904         if self.indent_type is None:
01905             # this can be true if initialised from a dictionary
01906             self.indent_type = DEFAULT_INDENT_TYPE
01907         #
01908         out = []
01909         cs = self._a_to_u('#')
01910         csp = self._a_to_u('# ')
01911         if section is None:
01912             int_val = self.interpolation
01913             self.interpolation = False
01914             section = self
01915             for line in self.initial_comment:
01916                 line = self._decode_element(line)
01917                 stripped_line = line.strip()
01918                 if stripped_line and not stripped_line.startswith(cs):
01919                     line = csp + line
01920                 out.append(line)
01921         #
01922         indent_string = self.indent_type * section.depth
01923         for entry in (section.scalars + section.sections):
01924             if entry in section.defaults:
01925                 # don't write out default values
01926                 continue
01927             for comment_line in section.comments[entry]:
01928                 comment_line = self._decode_element(comment_line.lstrip())
01929                 if comment_line and not comment_line.startswith(cs):
01930                     comment_line = csp + comment_line
01931                 out.append(indent_string + comment_line)
01932             this_entry = section[entry]
01933             comment = self._handle_comment(section.inline_comments[entry])
01934             #
01935             if isinstance(this_entry, dict):
01936                 # a section
01937                 out.append(self._write_marker(
01938                     indent_string,
01939                     this_entry.depth,
01940                     entry,
01941                     comment))
01942                 out.extend(self.write(section=this_entry))
01943             else:
01944                 out.append(self._write_line(
01945                     indent_string,
01946                     entry,
01947                     this_entry,
01948                     comment))
01949         #
01950         if section is self:
01951             for line in self.final_comment:
01952                 line = self._decode_element(line)
01953                 stripped_line = line.strip()
01954                 if stripped_line and not stripped_line.startswith(cs):
01955                     line = csp + line
01956                 out.append(line)
01957             self.interpolation = int_val
01958         #
01959         if section is not self:
01960             return out
01961         #
01962         if (self.filename is None) and (outfile is None):
01963             # output a list of lines
01964             # might need to encode
01965             # NOTE: This will *screw* UTF16, each line will start with the BOM
01966             if self.encoding:
01967                 out = [l.encode(self.encoding) for l in out]
01968             if (self.BOM and ((self.encoding is None) or
01969                 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
01970                 # Add the UTF8 BOM
01971                 if not out:
01972                     out.append('')
01973                 out[0] = BOM_UTF8 + out[0]
01974             return out
01975         #
01976         # Turn the list to a string, joined with correct newlines
01977         output = (self._a_to_u(self.newlines or os.linesep)
01978             ).join(out)
01979         if self.encoding:
01980             output = output.encode(self.encoding)
01981         if (self.BOM and ((self.encoding is None) or
01982             (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
01983             # Add the UTF8 BOM
01984             output = BOM_UTF8 + output
01985         if outfile is not None:
01986             outfile.write(output)
01987         else:
01988             h = open(self.filename, 'wb')
01989             h.write(output)
01990             h.close()
01991 
01992     def validate(self, validator, preserve_errors=False, copy=False,
01993         section=None):
01994         """
01995         Test the ConfigObj against a configspec.
01996         
01997         It uses the ``validator`` object from *validate.py*.
01998         
01999         To run ``validate`` on the current ConfigObj, call: ::
02000         
02001             test = config.validate(validator)
02002         
02003         (Normally having previously passed in the configspec when the ConfigObj
02004         was created - you can dynamically assign a dictionary of checks to the
02005         ``configspec`` attribute of a section though).
02006         
02007         It returns ``True`` if everything passes, or a dictionary of
02008         pass/fails (True/False). If every member of a subsection passes, it
02009         will just have the value ``True``. (It also returns ``False`` if all
02010         members fail).
02011         
02012         In addition, it converts the values from strings to their native
02013         types if their checks pass (and ``stringify`` is set).
02014         
02015         If ``preserve_errors`` is ``True`` (``False`` is default) then instead
02016         of a marking a fail with a ``False``, it will preserve the actual
02017         exception object. This can contain info about the reason for failure.
02018         For example the ``VdtValueTooSmallError`` indeicates that the value
02019         supplied was too small. If a value (or section) is missing it will
02020         still be marked as ``False``.
02021         
02022         You must have the validate module to use ``preserve_errors=True``.
02023         
02024         You can then use the ``flatten_errors`` function to turn your nested
02025         results dictionary into a flattened list of failures - useful for
02026         displaying meaningful error messages.
02027         """
02028         if section is None:
02029             if self.configspec is None:
02030                 raise ValueError, 'No configspec supplied.'
02031             if preserve_errors:
02032                 if VdtMissingValue is None:
02033                     raise ImportError('Missing validate module.')
02034             section = self
02035         #
02036         spec_section = section.configspec
02037         if copy and hasattr(section, '_configspec_initial_comment'):
02038             section.initial_comment = section._configspec_initial_comment
02039             section.final_comment = section._configspec_final_comment
02040             section.encoding = section._configspec_encoding
02041             section.BOM = section._configspec_BOM
02042             section.newlines = section._configspec_newlines
02043             section.indent_type = section._configspec_indent_type
02044         if '__many__' in section.configspec:
02045             many = spec_section['__many__']
02046             # dynamically assign the configspecs
02047             # for the sections below
02048             for entry in section.sections:
02049                 self._handle_repeat(section[entry], many)
02050         #
02051         out = {}
02052         ret_true = True
02053         ret_false = True
02054         order = [k for k in section._order if k in spec_section]
02055         order += [k for k in spec_section if k not in order]
02056         for entry in order:
02057             if entry == '__many__':
02058                 continue
02059             if (not entry in section.scalars) or (entry in section.defaults):
02060                 # missing entries
02061                 # or entries from defaults
02062                 missing = True
02063                 val = None
02064                 if copy and not entry in section.scalars:
02065                     # copy comments
02066                     section.comments[entry] = (
02067                         section._configspec_comments.get(entry, []))
02068                     section.inline_comments[entry] = (
02069                         section._configspec_inline_comments.get(entry, ''))
02070                 #
02071             else:
02072                 missing = False
02073                 val = section[entry]
02074             try:
02075                 check = validator.check(spec_section[entry],
02076                                         val,
02077                                         missing=missing
02078                                         )
02079             except validator.baseErrorClass, e:
02080                 if not preserve_errors or isinstance(e, VdtMissingValue):
02081                     out[entry] = False
02082                 else:
02083                     # preserve the error
02084                     out[entry] = e
02085                     ret_false = False
02086                 ret_true = False
02087             else:
02088                 ret_false = False
02089                 out[entry] = True
02090                 if self.stringify or missing:
02091                     # if we are doing type conversion
02092                     # or the value is a supplied default
02093                     if not self.stringify:
02094                         if isinstance(check, (list, tuple)):
02095                             # preserve lists
02096                             check = [self._str(item) for item in check]
02097                         elif missing and check is None:
02098                             # convert the None from a default to a ''
02099                             check = ''
02100                         else:
02101                             check = self._str(check)
02102                     if (check != val) or missing:
02103                         section[entry] = check
02104                 if not copy and missing and entry not in section.defaults:
02105                     section.defaults.append(entry)
02106         #
02107         # Missing sections will have been created as empty ones when the
02108         # configspec was read.
02109         for entry in section.sections:
02110             # FIXME: this means DEFAULT is not copied in copy mode
02111             if section is self and entry == 'DEFAULT':
02112                 continue
02113             if copy:
02114                 section.comments[entry] = section._cs_section_comments[entry]
02115                 section.inline_comments[entry] = (
02116                     section._cs_section_inline_comments[entry])
02117             check = self.validate(validator, preserve_errors=preserve_errors,
02118                 copy=copy, section=section[entry])
02119             out[entry] = check
02120             if check == False:
02121                 ret_true = False
02122             elif check == True:
02123                 ret_false = False
02124             else:
02125                 ret_true = False
02126                 ret_false = False
02127         #
02128         if ret_true:
02129             return True
02130         elif ret_false:
02131             return False
02132         else:
02133             return out
02134 
02135 class SimpleVal(object):
02136     """
02137     A simple validator.
02138     Can be used to check that all members expected are present.
02139     
02140     To use it, provide a configspec with all your members in (the value given
02141     will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
02142     method of your ``ConfigObj``. ``validate`` will return ``True`` if all
02143     members are present, or a dictionary with True/False meaning
02144     present/missing. (Whole missing sections will be replaced with ``False``)
02145     """
02146     
02147     def __init__(self):
02148         self.baseErrorClass = ConfigObjError
02149     
02150     def check(self, check, member, missing=False):
02151         """A dummy check method, always returns the value unchanged."""
02152         if missing:
02153             raise self.baseErrorClass
02154         return member
02155 
02156 # Check / processing functions for options
02157 def flatten_errors(cfg, res, levels=None, results=None):
02158     """
02159     An example function that will turn a nested dictionary of results
02160     (as returned by ``ConfigObj.validate``) into a flat list.
02161     
02162     ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
02163     dictionary returned by ``validate``.
02164     
02165     (This is a recursive function, so you shouldn't use the ``levels`` or
02166     ``results`` arguments - they are used by the function.
02167     
02168     Returns a list of keys that failed. Each member of the list is a tuple :
02169     ::
02170     
02171         ([list of sections...], key, result)
02172     
02173     If ``validate`` was called with ``preserve_errors=False`` (the default)
02174     then ``result`` will always be ``False``.
02175 
02176     *list of sections* is a flattened list of sections that the key was found
02177     in.
02178     
02179     If the section was missing then key will be ``None``.
02180     
02181     If the value (or section) was missing then ``result`` will be ``False``.
02182     
02183     If ``validate`` was called with ``preserve_errors=True`` and a value
02184     was present, but failed the check, then ``result`` will be the exception
02185     object returned. You can use this as a string that describes the failure.
02186     
02187     For example *The value "3" is of the wrong type*.
02188     
02189     >>> import validate
02190     >>> vtor = validate.Validator()
02191     >>> my_ini = '''
02192     ...     option1 = True
02193     ...     [section1]
02194     ...     option1 = True
02195     ...     [section2]
02196     ...     another_option = Probably
02197     ...     [section3]
02198     ...     another_option = True
02199     ...     [[section3b]]
02200     ...     value = 3
02201     ...     value2 = a
02202     ...     value3 = 11
02203     ...     '''
02204     >>> my_cfg = '''
02205     ...     option1 = boolean()
02206     ...     option2 = boolean()
02207     ...     option3 = boolean(default=Bad_value)
02208     ...     [section1]
02209     ...     option1 = boolean()
02210     ...     option2 = boolean()
02211     ...     option3 = boolean(default=Bad_value)
02212     ...     [section2]
02213     ...     another_option = boolean()
02214     ...     [section3]
02215     ...     another_option = boolean()
02216     ...     [[section3b]]
02217     ...     value = integer
02218     ...     value2 = integer
02219     ...     value3 = integer(0, 10)
02220     ...         [[[section3b-sub]]]
02221     ...         value = string
02222     ...     [section4]
02223     ...     another_option = boolean()
02224     ...     '''
02225     >>> cs = my_cfg.split('\\n')
02226     >>> ini = my_ini.split('\\n')
02227     >>> cfg = ConfigObj(ini, configspec=cs)
02228     >>> res = cfg.validate(vtor, preserve_errors=True)
02229     >>> errors = []
02230     >>> for entry in flatten_errors(cfg, res):
02231     ...     section_list, key, error = entry
02232     ...     section_list.insert(0, '[root]')
02233     ...     if key is not None:
02234     ...        section_list.append(key)
02235     ...     else:
02236     ...         section_list.append('[missing]')
02237     ...     section_string = ', '.join(section_list)
02238     ...     errors.append((section_string, ' = ', error))
02239     >>> errors.sort()
02240     >>> for entry in errors:
02241     ...     print entry[0], entry[1], (entry[2] or 0)
02242     [root], option2  =  0
02243     [root], option3  =  the value "Bad_value" is of the wrong type.
02244     [root], section1, option2  =  0
02245     [root], section1, option3  =  the value "Bad_value" is of the wrong type.
02246     [root], section2, another_option  =  the value "Probably" is of the wrong type.
02247     [root], section3, section3b, section3b-sub, [missing]  =  0
02248     [root], section3, section3b, value2  =  the value "a" is of the wrong type.
02249     [root], section3, section3b, value3  =  the value "11" is too big.
02250     [root], section4, [missing]  =  0
02251     """
02252     if levels is None:
02253         # first time called
02254         levels = []
02255         results = []
02256     if res is True:
02257         return results
02258     if res is False:
02259         results.append((levels[:], None, False))
02260         if levels:
02261             levels.pop()
02262         return results
02263     for (key, val) in res.items():
02264         if val == True:
02265             continue
02266         if isinstance(cfg.get(key), dict):
02267             # Go down one level
02268             levels.append(key)
02269             flatten_errors(cfg[key], val, levels, results)
02270             continue
02271         results.append((levels[:], key, val))
02272     #
02273     # Go up one level
02274     if levels:
02275         levels.pop()
02276     #
02277     return results
02278 
02279 """*A programming language is a medium of expression.* - Paul Graham"""