OSDN Git Service

updated jtalk dictionary binary
[nvdajp/nvdajpmiscdep.git] / source / configobj.py
1 # configobj.py
2 # A config file reader/writer that supports nested sections in config files.
3 # Copyright (C) 2005-2009 Michael Foord, Nicola Larosa
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
5 #         nico AT tekNico DOT net
6
7 # ConfigObj 4
8 # http://www.voidspace.org.uk/python/configobj.html
9
10 # Released subject to the BSD License
11 # Please see http://www.voidspace.org.uk/python/license.shtml
12
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14 # For information about bugfixes, updates and support, please join the
15 # ConfigObj mailing list:
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
17 # Comments, suggestions and bug reports welcome.
18
19
20 from __future__ import generators
21
22 import sys
23 import os
24 import re
25
26 compiler = None
27 try:
28     import compiler
29 except ImportError:
30     # for IronPython
31     pass
32
33
34 try:
35     from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
36 except ImportError:
37     # Python 2.2 does not have these
38     # UTF-8
39     BOM_UTF8 = '\xef\xbb\xbf'
40     # UTF-16, little endian
41     BOM_UTF16_LE = '\xff\xfe'
42     # UTF-16, big endian
43     BOM_UTF16_BE = '\xfe\xff'
44     if sys.byteorder == 'little':
45         # UTF-16, native endianness
46         BOM_UTF16 = BOM_UTF16_LE
47     else:
48         # UTF-16, native endianness
49         BOM_UTF16 = BOM_UTF16_BE
50
51 # A dictionary mapping BOM to
52 # the encoding to decode with, and what to set the
53 # encoding attribute to.
54 BOMS = {
55     BOM_UTF8: ('utf_8', None),
56     BOM_UTF16_BE: ('utf16_be', 'utf_16'),
57     BOM_UTF16_LE: ('utf16_le', 'utf_16'),
58     BOM_UTF16: ('utf_16', 'utf_16'),
59     }
60 # All legal variants of the BOM codecs.
61 # TODO: the list of aliases is not meant to be exhaustive, is there a
62 #   better way ?
63 BOM_LIST = {
64     'utf_16': 'utf_16',
65     'u16': 'utf_16',
66     'utf16': 'utf_16',
67     'utf-16': 'utf_16',
68     'utf16_be': 'utf16_be',
69     'utf_16_be': 'utf16_be',
70     'utf-16be': 'utf16_be',
71     'utf16_le': 'utf16_le',
72     'utf_16_le': 'utf16_le',
73     'utf-16le': 'utf16_le',
74     'utf_8': 'utf_8',
75     'u8': 'utf_8',
76     'utf': 'utf_8',
77     'utf8': 'utf_8',
78     'utf-8': 'utf_8',
79     }
80
81 # Map of encodings to the BOM to write.
82 BOM_SET = {
83     'utf_8': BOM_UTF8,
84     'utf_16': BOM_UTF16,
85     'utf16_be': BOM_UTF16_BE,
86     'utf16_le': BOM_UTF16_LE,
87     None: BOM_UTF8
88     }
89
90
91 def match_utf8(encoding):
92     return BOM_LIST.get(encoding.lower()) == 'utf_8'
93
94
95 # Quote strings used for writing values
96 squot = "'%s'"
97 dquot = '"%s"'
98 noquot = "%s"
99 wspace_plus = ' \r\n\v\t\'"'
100 tsquot = '"""%s"""'
101 tdquot = "'''%s'''"
102
103 try:
104     enumerate
105 except NameError:
106     def enumerate(obj):
107         """enumerate for Python 2.2."""
108         i = -1
109         for item in obj:
110             i += 1
111             yield i, item
112
113 # Sentinel for use in getattr calls to replace hasattr
114 MISSING = object()
115
116 __version__ = '4.6.0'
117
118 __revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
119
120 __docformat__ = "restructuredtext en" 
121
122 __all__ = (
123     '__version__',
124     'DEFAULT_INDENT_TYPE',
125     'DEFAULT_INTERPOLATION',
126     'ConfigObjError',
127     'NestingError',
128     'ParseError',
129     'DuplicateError',
130     'ConfigspecError',
131     'ConfigObj',
132     'SimpleVal',
133     'InterpolationError',
134     'InterpolationLoopError',
135     'MissingInterpolationOption',
136     'RepeatSectionError',
137     'ReloadError',
138     'UnreprError',
139     'UnknownType',
140     '__docformat__',
141     'flatten_errors',
142 )
143
144 DEFAULT_INTERPOLATION = 'configparser'
145 DEFAULT_INDENT_TYPE = '    '
146 MAX_INTERPOL_DEPTH = 10
147
148 OPTION_DEFAULTS = {
149     'interpolation': True,
150     'raise_errors': False,
151     'list_values': True,
152     'create_empty': False,
153     'file_error': False,
154     'configspec': None,
155     'stringify': True,
156     # option may be set to one of ('', ' ', '\t')
157     'indent_type': None,
158     'encoding': None,
159     'default_encoding': None,
160     'unrepr': False,
161     'write_empty_values': False,
162 }
163
164
165
166 def getObj(s):
167     s = "a=" + s
168     if compiler is None:
169         raise ImportError('compiler module not available')
170     p = compiler.parse(s)
171     return p.getChildren()[1].getChildren()[0].getChildren()[1]
172
173
174 class UnknownType(Exception):
175     pass
176
177
178 class Builder(object):
179     
180     def build(self, o):
181         m = getattr(self, 'build_' + o.__class__.__name__, None)
182         if m is None:
183             raise UnknownType(o.__class__.__name__)
184         return m(o)
185     
186     def build_List(self, o):
187         return map(self.build, o.getChildren())
188     
189     def build_Const(self, o):
190         return o.value
191     
192     def build_Dict(self, o):
193         d = {}
194         i = iter(map(self.build, o.getChildren()))
195         for el in i:
196             d[el] = i.next()
197         return d
198     
199     def build_Tuple(self, o):
200         return tuple(self.build_List(o))
201     
202     def build_Name(self, o):
203         if o.name == 'None':
204             return None
205         if o.name == 'True':
206             return True
207         if o.name == 'False':
208             return False
209         
210         # An undefined Name
211         raise UnknownType('Undefined Name')
212     
213     def build_Add(self, o):
214         real, imag = map(self.build_Const, o.getChildren())
215         try:
216             real = float(real)
217         except TypeError:
218             raise UnknownType('Add')
219         if not isinstance(imag, complex) or imag.real != 0.0:
220             raise UnknownType('Add')
221         return real+imag
222     
223     def build_Getattr(self, o):
224         parent = self.build(o.expr)
225         return getattr(parent, o.attrname)
226     
227     def build_UnarySub(self, o):
228         return -self.build_Const(o.getChildren()[0])
229     
230     def build_UnaryAdd(self, o):
231         return self.build_Const(o.getChildren()[0])
232
233
234 _builder = Builder()
235
236
237 def unrepr(s):
238     if not s:
239         return s
240     return _builder.build(getObj(s))
241
242
243
244 class ConfigObjError(SyntaxError):
245     """
246     This is the base class for all errors that ConfigObj raises.
247     It is a subclass of SyntaxError.
248     """
249     def __init__(self, message='', line_number=None, line=''):
250         self.line = line
251         self.line_number = line_number
252         SyntaxError.__init__(self, message)
253
254
255 class NestingError(ConfigObjError):
256     """
257     This error indicates a level of nesting that doesn't match.
258     """
259
260
261 class ParseError(ConfigObjError):
262     """
263     This error indicates that a line is badly written.
264     It is neither a valid ``key = value`` line,
265     nor a valid section marker line.
266     """
267
268
269 class ReloadError(IOError):
270     """
271     A 'reload' operation failed.
272     This exception is a subclass of ``IOError``.
273     """
274     def __init__(self):
275         IOError.__init__(self, 'reload failed, filename is not set.')
276
277
278 class DuplicateError(ConfigObjError):
279     """
280     The keyword or section specified already exists.
281     """
282
283
284 class ConfigspecError(ConfigObjError):
285     """
286     An error occured whilst parsing a configspec.
287     """
288
289
290 class InterpolationError(ConfigObjError):
291     """Base class for the two interpolation errors."""
292
293
294 class InterpolationLoopError(InterpolationError):
295     """Maximum interpolation depth exceeded in string interpolation."""
296
297     def __init__(self, option):
298         InterpolationError.__init__(
299             self,
300             'interpolation loop detected in value "%s".' % option)
301
302
303 class RepeatSectionError(ConfigObjError):
304     """
305     This error indicates additional sections in a section with a
306     ``__many__`` (repeated) section.
307     """
308
309
310 class MissingInterpolationOption(InterpolationError):
311     """A value specified for interpolation was missing."""
312
313     def __init__(self, option):
314         InterpolationError.__init__(
315             self,
316             'missing option "%s" in interpolation.' % option)
317
318
319 class UnreprError(ConfigObjError):
320     """An error parsing in unrepr mode."""
321
322
323
324 class InterpolationEngine(object):
325     """
326     A helper class to help perform string interpolation.
327
328     This class is an abstract base class; its descendants perform
329     the actual work.
330     """
331
332     # compiled regexp to use in self.interpolate()
333     _KEYCRE = re.compile(r"%\(([^)]*)\)s")
334
335     def __init__(self, section):
336         # the Section instance that "owns" this engine
337         self.section = section
338
339
340     def interpolate(self, key, value):
341         def recursive_interpolate(key, value, section, backtrail):
342             """The function that does the actual work.
343
344             ``value``: the string we're trying to interpolate.
345             ``section``: the section in which that string was found
346             ``backtrail``: a dict to keep track of where we've been,
347             to detect and prevent infinite recursion loops
348
349             This is similar to a depth-first-search algorithm.
350             """
351             # Have we been here already?
352             if backtrail.has_key((key, section.name)):
353                 # Yes - infinite loop detected
354                 raise InterpolationLoopError(key)
355             # Place a marker on our backtrail so we won't come back here again
356             backtrail[(key, section.name)] = 1
357
358             # Now start the actual work
359             match = self._KEYCRE.search(value)
360             while match:
361                 # The actual parsing of the match is implementation-dependent,
362                 # so delegate to our helper function
363                 k, v, s = self._parse_match(match)
364                 if k is None:
365                     # That's the signal that no further interpolation is needed
366                     replacement = v
367                 else:
368                     # Further interpolation may be needed to obtain final value
369                     replacement = recursive_interpolate(k, v, s, backtrail)
370                 # Replace the matched string with its final value
371                 start, end = match.span()
372                 value = ''.join((value[:start], replacement, value[end:]))
373                 new_search_start = start + len(replacement)
374                 # Pick up the next interpolation key, if any, for next time
375                 # through the while loop
376                 match = self._KEYCRE.search(value, new_search_start)
377
378             # Now safe to come back here again; remove marker from backtrail
379             del backtrail[(key, section.name)]
380
381             return value
382
383         # Back in interpolate(), all we have to do is kick off the recursive
384         # function with appropriate starting values
385         value = recursive_interpolate(key, value, self.section, {})
386         return value
387
388
389     def _fetch(self, key):
390         """Helper function to fetch values from owning section.
391
392         Returns a 2-tuple: the value, and the section where it was found.
393         """
394         # switch off interpolation before we try and fetch anything !
395         save_interp = self.section.main.interpolation
396         self.section.main.interpolation = False
397
398         # Start at section that "owns" this InterpolationEngine
399         current_section = self.section
400         while True:
401             # try the current section first
402             val = current_section.get(key)
403             if val is not None:
404                 break
405             # try "DEFAULT" next
406             val = current_section.get('DEFAULT', {}).get(key)
407             if val is not None:
408                 break
409             # move up to parent and try again
410             # top-level's parent is itself
411             if current_section.parent is current_section:
412                 # reached top level, time to give up
413                 break
414             current_section = current_section.parent
415
416         # restore interpolation to previous value before returning
417         self.section.main.interpolation = save_interp
418         if val is None:
419             raise MissingInterpolationOption(key)
420         return val, current_section
421
422
423     def _parse_match(self, match):
424         """Implementation-dependent helper function.
425
426         Will be passed a match object corresponding to the interpolation
427         key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
428         key in the appropriate config file section (using the ``_fetch()``
429         helper function) and return a 3-tuple: (key, value, section)
430
431         ``key`` is the name of the key we're looking for
432         ``value`` is the value found for that key
433         ``section`` is a reference to the section where it was found
434
435         ``key`` and ``section`` should be None if no further
436         interpolation should be performed on the resulting value
437         (e.g., if we interpolated "$$" and returned "$").
438         """
439         raise NotImplementedError()
440     
441
442
443 class ConfigParserInterpolation(InterpolationEngine):
444     """Behaves like ConfigParser."""
445     _KEYCRE = re.compile(r"%\(([^)]*)\)s")
446
447     def _parse_match(self, match):
448         key = match.group(1)
449         value, section = self._fetch(key)
450         return key, value, section
451
452
453
454 class TemplateInterpolation(InterpolationEngine):
455     """Behaves like string.Template."""
456     _delimiter = '$'
457     _KEYCRE = re.compile(r"""
458         \$(?:
459           (?P<escaped>\$)              |   # Two $ signs
460           (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
461           {(?P<braced>[^}]*)}              # ${name} format
462         )
463         """, re.IGNORECASE | re.VERBOSE)
464
465     def _parse_match(self, match):
466         # Valid name (in or out of braces): fetch value from section
467         key = match.group('named') or match.group('braced')
468         if key is not None:
469             value, section = self._fetch(key)
470             return key, value, section
471         # Escaped delimiter (e.g., $$): return single delimiter
472         if match.group('escaped') is not None:
473             # Return None for key and section to indicate it's time to stop
474             return None, self._delimiter, None
475         # Anything else: ignore completely, just return it unchanged
476         return None, match.group(), None
477
478
479 interpolation_engines = {
480     'configparser': ConfigParserInterpolation,
481     'template': TemplateInterpolation,
482 }
483
484
485 def __newobj__(cls, *args):
486     # Hack for pickle
487     return cls.__new__(cls, *args) 
488
489 class Section(dict):
490     """
491     A dictionary-like object that represents a section in a config file.
492     
493     It does string interpolation if the 'interpolation' attribute
494     of the 'main' object is set to True.
495     
496     Interpolation is tried first from this object, then from the 'DEFAULT'
497     section of this object, next from the parent and its 'DEFAULT' section,
498     and so on until the main object is reached.
499     
500     A Section will behave like an ordered dictionary - following the
501     order of the ``scalars`` and ``sections`` attributes.
502     You can use this to change the order of members.
503     
504     Iteration follows the order: scalars, then sections.
505     """
506
507     
508     def __setstate__(self, state):
509         dict.update(self, state[0])
510         self.__dict__.update(state[1])
511
512     def __reduce__(self):
513         state = (dict(self), self.__dict__)
514         return (__newobj__, (self.__class__,), state)
515     
516     
517     def __init__(self, parent, depth, main, indict=None, name=None):
518         """
519         * parent is the section above
520         * depth is the depth level of this section
521         * main is the main ConfigObj
522         * indict is a dictionary to initialise the section with
523         """
524         if indict is None:
525             indict = {}
526         dict.__init__(self)
527         # used for nesting level *and* interpolation
528         self.parent = parent
529         # used for the interpolation attribute
530         self.main = main
531         # level of nesting depth of this Section
532         self.depth = depth
533         # purely for information
534         self.name = name
535         #
536         self._initialise()
537         # we do this explicitly so that __setitem__ is used properly
538         # (rather than just passing to ``dict.__init__``)
539         for entry, value in indict.iteritems():
540             self[entry] = value
541             
542             
543     def _initialise(self):
544         # the sequence of scalar values in this Section
545         self.scalars = []
546         # the sequence of sections in this Section
547         self.sections = []
548         # for comments :-)
549         self.comments = {}
550         self.inline_comments = {}
551         # the configspec
552         self.configspec = None
553         # for defaults
554         self.defaults = []
555         self.default_values = {}
556
557
558     def _interpolate(self, key, value):
559         try:
560             # do we already have an interpolation engine?
561             engine = self._interpolation_engine
562         except AttributeError:
563             # not yet: first time running _interpolate(), so pick the engine
564             name = self.main.interpolation
565             if name == True:  # note that "if name:" would be incorrect here
566                 # backwards-compatibility: interpolation=True means use default
567                 name = DEFAULT_INTERPOLATION
568             name = name.lower()  # so that "Template", "template", etc. all work
569             class_ = interpolation_engines.get(name, None)
570             if class_ is None:
571                 # invalid value for self.main.interpolation
572                 self.main.interpolation = False
573                 return value
574             else:
575                 # save reference to engine so we don't have to do this again
576                 engine = self._interpolation_engine = class_(self)
577         # let the engine do the actual work
578         return engine.interpolate(key, value)
579
580
581     def __getitem__(self, key):
582         """Fetch the item and do string interpolation."""
583         val = dict.__getitem__(self, key)
584         if self.main.interpolation and isinstance(val, basestring):
585             return self._interpolate(key, val)
586         return val
587
588
589     def __setitem__(self, key, value, unrepr=False):
590         """
591         Correctly set a value.
592         
593         Making dictionary values Section instances.
594         (We have to special case 'Section' instances - which are also dicts)
595         
596         Keys must be strings.
597         Values need only be strings (or lists of strings) if
598         ``main.stringify`` is set.
599         
600         ``unrepr`` must be set when setting a value to a dictionary, without
601         creating a new sub-section.
602         """
603         if not isinstance(key, basestring):
604             raise ValueError('The key "%s" is not a string.' % key)
605         
606         # add the comment
607         if not self.comments.has_key(key):
608             self.comments[key] = []
609             self.inline_comments[key] = ''
610         # remove the entry from defaults
611         if key in self.defaults:
612             self.defaults.remove(key)
613         #
614         if isinstance(value, Section):
615             if not self.has_key(key):
616                 self.sections.append(key)
617             dict.__setitem__(self, key, value)
618         elif isinstance(value, dict) and not unrepr:
619             # First create the new depth level,
620             # then create the section
621             if not self.has_key(key):
622                 self.sections.append(key)
623             new_depth = self.depth + 1
624             dict.__setitem__(
625                 self,
626                 key,
627                 Section(
628                     self,
629                     new_depth,
630                     self.main,
631                     indict=value,
632                     name=key))
633         else:
634             if not self.has_key(key):
635                 self.scalars.append(key)
636             if not self.main.stringify:
637                 if isinstance(value, basestring):
638                     pass
639                 elif isinstance(value, (list, tuple)):
640                     for entry in value:
641                         if not isinstance(entry, basestring):
642                             raise TypeError('Value is not a string "%s".' % entry)
643                 else:
644                     raise TypeError('Value is not a string "%s".' % value)
645             dict.__setitem__(self, key, value)
646
647
648     def __delitem__(self, key):
649         """Remove items from the sequence when deleting."""
650         dict. __delitem__(self, key)
651         if key in self.scalars:
652             self.scalars.remove(key)
653         else:
654             self.sections.remove(key)
655         del self.comments[key]
656         del self.inline_comments[key]
657
658
659     def get(self, key, default=None):
660         """A version of ``get`` that doesn't bypass string interpolation."""
661         try:
662             return self[key]
663         except KeyError:
664             return default
665
666
667     def update(self, indict):
668         """
669         A version of update that uses our ``__setitem__``.
670         """
671         for entry in indict:
672             self[entry] = indict[entry]
673
674
675     def pop(self, key, *args):
676         """
677         'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
678         If key is not found, d is returned if given, otherwise KeyError is raised'
679         """
680         val = dict.pop(self, key, *args)
681         if key in self.scalars:
682             del self.comments[key]
683             del self.inline_comments[key]
684             self.scalars.remove(key)
685         elif key in self.sections:
686             del self.comments[key]
687             del self.inline_comments[key]
688             self.sections.remove(key)
689         if self.main.interpolation and isinstance(val, basestring):
690             return self._interpolate(key, val)
691         return val
692
693
694     def popitem(self):
695         """Pops the first (key,val)"""
696         sequence = (self.scalars + self.sections)
697         if not sequence:
698             raise KeyError(": 'popitem(): dictionary is empty'")
699         key = sequence[0]
700         val =  self[key]
701         del self[key]
702         return key, val
703
704
705     def clear(self):
706         """
707         A version of clear that also affects scalars/sections
708         Also clears comments and configspec.
709         
710         Leaves other attributes alone :
711             depth/main/parent are not affected
712         """
713         dict.clear(self)
714         self.scalars = []
715         self.sections = []
716         self.comments = {}
717         self.inline_comments = {}
718         self.configspec = None
719
720
721     def setdefault(self, key, default=None):
722         """A version of setdefault that sets sequence if appropriate."""
723         try:
724             return self[key]
725         except KeyError:
726             self[key] = default
727             return self[key]
728
729
730     def items(self):
731         """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
732         return zip((self.scalars + self.sections), self.values())
733
734
735     def keys(self):
736         """D.keys() -> list of D's keys"""
737         return (self.scalars + self.sections)
738
739
740     def values(self):
741         """D.values() -> list of D's values"""
742         return [self[key] for key in (self.scalars + self.sections)]
743
744
745     def iteritems(self):
746         """D.iteritems() -> an iterator over the (key, value) items of D"""
747         return iter(self.items())
748
749
750     def iterkeys(self):
751         """D.iterkeys() -> an iterator over the keys of D"""
752         return iter((self.scalars + self.sections))
753
754     __iter__ = iterkeys
755
756
757     def itervalues(self):
758         """D.itervalues() -> an iterator over the values of D"""
759         return iter(self.values())
760
761
762     def __repr__(self):
763         """x.__repr__() <==> repr(x)"""
764         return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
765             for key in (self.scalars + self.sections)])
766
767     __str__ = __repr__
768     __str__.__doc__ = "x.__str__() <==> str(x)"
769
770
771     # Extra methods - not in a normal dictionary
772
773     def dict(self):
774         """
775         Return a deepcopy of self as a dictionary.
776         
777         All members that are ``Section`` instances are recursively turned to
778         ordinary dictionaries - by calling their ``dict`` method.
779         
780         >>> n = a.dict()
781         >>> n == a
782         1
783         >>> n is a
784         0
785         """
786         newdict = {}
787         for entry in self:
788             this_entry = self[entry]
789             if isinstance(this_entry, Section):
790                 this_entry = this_entry.dict()
791             elif isinstance(this_entry, list):
792                 # create a copy rather than a reference
793                 this_entry = list(this_entry)
794             elif isinstance(this_entry, tuple):
795                 # create a copy rather than a reference
796                 this_entry = tuple(this_entry)
797             newdict[entry] = this_entry
798         return newdict
799
800
801     def merge(self, indict):
802         """
803         A recursive update - useful for merging config files.
804         
805         >>> a = '''[section1]
806         ...     option1 = True
807         ...     [[subsection]]
808         ...     more_options = False
809         ...     # end of file'''.splitlines()
810         >>> b = '''# File is user.ini
811         ...     [section1]
812         ...     option1 = False
813         ...     # end of file'''.splitlines()
814         >>> c1 = ConfigObj(b)
815         >>> c2 = ConfigObj(a)
816         >>> c2.merge(c1)
817         >>> c2
818         ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
819         """
820         for key, val in indict.items():
821             if (key in self and isinstance(self[key], dict) and
822                                 isinstance(val, dict)):
823                 self[key].merge(val)
824             else:   
825                 self[key] = val
826
827
828     def rename(self, oldkey, newkey):
829         """
830         Change a keyname to another, without changing position in sequence.
831         
832         Implemented so that transformations can be made on keys,
833         as well as on values. (used by encode and decode)
834         
835         Also renames comments.
836         """
837         if oldkey in self.scalars:
838             the_list = self.scalars
839         elif oldkey in self.sections:
840             the_list = self.sections
841         else:
842             raise KeyError('Key "%s" not found.' % oldkey)
843         pos = the_list.index(oldkey)
844         #
845         val = self[oldkey]
846         dict.__delitem__(self, oldkey)
847         dict.__setitem__(self, newkey, val)
848         the_list.remove(oldkey)
849         the_list.insert(pos, newkey)
850         comm = self.comments[oldkey]
851         inline_comment = self.inline_comments[oldkey]
852         del self.comments[oldkey]
853         del self.inline_comments[oldkey]
854         self.comments[newkey] = comm
855         self.inline_comments[newkey] = inline_comment
856
857
858     def walk(self, function, raise_errors=True,
859             call_on_sections=False, **keywargs):
860         """
861         Walk every member and call a function on the keyword and value.
862         
863         Return a dictionary of the return values
864         
865         If the function raises an exception, raise the errror
866         unless ``raise_errors=False``, in which case set the return value to
867         ``False``.
868         
869         Any unrecognised keyword arguments you pass to walk, will be pased on
870         to the function you pass in.
871         
872         Note: if ``call_on_sections`` is ``True`` then - on encountering a
873         subsection, *first* the function is called for the *whole* subsection,
874         and then recurses into it's members. This means your function must be
875         able to handle strings, dictionaries and lists. This allows you
876         to change the key of subsections as well as for ordinary members. The
877         return value when called on the whole subsection has to be discarded.
878         
879         See  the encode and decode methods for examples, including functions.
880         
881         .. admonition:: caution
882         
883             You can use ``walk`` to transform the names of members of a section
884             but you mustn't add or delete members.
885         
886         >>> config = '''[XXXXsection]
887         ... XXXXkey = XXXXvalue'''.splitlines()
888         >>> cfg = ConfigObj(config)
889         >>> cfg
890         ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
891         >>> def transform(section, key):
892         ...     val = section[key]
893         ...     newkey = key.replace('XXXX', 'CLIENT1')
894         ...     section.rename(key, newkey)
895         ...     if isinstance(val, (tuple, list, dict)):
896         ...         pass
897         ...     else:
898         ...         val = val.replace('XXXX', 'CLIENT1')
899         ...         section[newkey] = val
900         >>> cfg.walk(transform, call_on_sections=True)
901         {'CLIENT1section': {'CLIENT1key': None}}
902         >>> cfg
903         ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
904         """
905         out = {}
906         # scalars first
907         for i in range(len(self.scalars)):
908             entry = self.scalars[i]
909             try:
910                 val = function(self, entry, **keywargs)
911                 # bound again in case name has changed
912                 entry = self.scalars[i]
913                 out[entry] = val
914             except Exception:
915                 if raise_errors:
916                     raise
917                 else:
918                     entry = self.scalars[i]
919                     out[entry] = False
920         # then sections
921         for i in range(len(self.sections)):
922             entry = self.sections[i]
923             if call_on_sections:
924                 try:
925                     function(self, entry, **keywargs)
926                 except Exception:
927                     if raise_errors:
928                         raise
929                     else:
930                         entry = self.sections[i]
931                         out[entry] = False
932                 # bound again in case name has changed
933                 entry = self.sections[i]
934             # previous result is discarded
935             out[entry] = self[entry].walk(
936                 function,
937                 raise_errors=raise_errors,
938                 call_on_sections=call_on_sections,
939                 **keywargs)
940         return out
941
942
943     def as_bool(self, key):
944         """
945         Accepts a key as input. The corresponding value must be a string or
946         the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
947         retain compatibility with Python 2.2.
948         
949         If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
950         ``True``.
951         
952         If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
953         ``False``.
954         
955         ``as_bool`` is not case sensitive.
956         
957         Any other input will raise a ``ValueError``.
958         
959         >>> a = ConfigObj()
960         >>> a['a'] = 'fish'
961         >>> a.as_bool('a')
962         Traceback (most recent call last):
963         ValueError: Value "fish" is neither True nor False
964         >>> a['b'] = 'True'
965         >>> a.as_bool('b')
966         1
967         >>> a['b'] = 'off'
968         >>> a.as_bool('b')
969         0
970         """
971         val = self[key]
972         if val == True:
973             return True
974         elif val == False:
975             return False
976         else:
977             try:
978                 if not isinstance(val, basestring):
979                     # TODO: Why do we raise a KeyError here?
980                     raise KeyError()
981                 else:
982                     return self.main._bools[val.lower()]
983             except KeyError:
984                 raise ValueError('Value "%s" is neither True nor False' % val)
985
986
987     def as_int(self, key):
988         """
989         A convenience method which coerces the specified value to an integer.
990         
991         If the value is an invalid literal for ``int``, a ``ValueError`` will
992         be raised.
993         
994         >>> a = ConfigObj()
995         >>> a['a'] = 'fish'
996         >>> a.as_int('a')
997         Traceback (most recent call last):
998         ValueError: invalid literal for int() with base 10: 'fish'
999         >>> a['b'] = '1'
1000         >>> a.as_int('b')
1001         1
1002         >>> a['b'] = '3.2'
1003         >>> a.as_int('b')
1004         Traceback (most recent call last):
1005         ValueError: invalid literal for int() with base 10: '3.2'
1006         """
1007         return int(self[key])
1008
1009
1010     def as_float(self, key):
1011         """
1012         A convenience method which coerces the specified value to a float.
1013         
1014         If the value is an invalid literal for ``float``, a ``ValueError`` will
1015         be raised.
1016         
1017         >>> a = ConfigObj()
1018         >>> a['a'] = 'fish'
1019         >>> a.as_float('a')
1020         Traceback (most recent call last):
1021         ValueError: invalid literal for float(): fish
1022         >>> a['b'] = '1'
1023         >>> a.as_float('b')
1024         1.0
1025         >>> a['b'] = '3.2'
1026         >>> a.as_float('b')
1027         3.2000000000000002
1028         """
1029         return float(self[key])
1030     
1031     
1032     def as_list(self, key):
1033         """
1034         A convenience method which fetches the specified value, guaranteeing
1035         that it is a list.
1036         
1037         >>> a = ConfigObj()
1038         >>> a['a'] = 1
1039         >>> a.as_list('a')
1040         [1]
1041         >>> a['a'] = (1,)
1042         >>> a.as_list('a')
1043         [1]
1044         >>> a['a'] = [1]
1045         >>> a.as_list('a')
1046         [1]
1047         """
1048         result = self[key]
1049         if isinstance(result, (tuple, list)):
1050             return list(result)
1051         return [result]
1052         
1053
1054     def restore_default(self, key):
1055         """
1056         Restore (and return) default value for the specified key.
1057         
1058         This method will only work for a ConfigObj that was created
1059         with a configspec and has been validated.
1060         
1061         If there is no default value for this key, ``KeyError`` is raised.
1062         """
1063         default = self.default_values[key]
1064         dict.__setitem__(self, key, default)
1065         if key not in self.defaults:
1066             self.defaults.append(key)
1067         return default
1068
1069     
1070     def restore_defaults(self):
1071         """
1072         Recursively restore default values to all members
1073         that have them.
1074         
1075         This method will only work for a ConfigObj that was created
1076         with a configspec and has been validated.
1077         
1078         It doesn't delete or modify entries without default values.
1079         """
1080         for key in self.default_values:
1081             self.restore_default(key)
1082             
1083         for section in self.sections:
1084             self[section].restore_defaults()
1085
1086
1087 class ConfigObj(Section):
1088     """An object to read, create, and write config files."""
1089
1090     _keyword = re.compile(r'''^ # line start
1091         (\s*)                   # indentation
1092         (                       # keyword
1093             (?:".*?")|          # double quotes
1094             (?:'.*?')|          # single quotes
1095             (?:[^'"=].*?)       # no quotes
1096         )
1097         \s*=\s*                 # divider
1098         (.*)                    # value (including list values and comments)
1099         $   # line end
1100         ''',
1101         re.VERBOSE)
1102
1103     _sectionmarker = re.compile(r'''^
1104         (\s*)                     # 1: indentation
1105         ((?:\[\s*)+)              # 2: section marker open
1106         (                         # 3: section name open
1107             (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
1108             (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
1109             (?:[^'"\s].*?)        # at least one non-space unquoted
1110         )                         # section name close
1111         ((?:\s*\])+)              # 4: section marker close
1112         \s*(\#.*)?                # 5: optional comment
1113         $''',
1114         re.VERBOSE)
1115
1116     # this regexp pulls list values out as a single string
1117     # or single values and comments
1118     # FIXME: this regex adds a '' to the end of comma terminated lists
1119     #   workaround in ``_handle_value``
1120     _valueexp = re.compile(r'''^
1121         (?:
1122             (?:
1123                 (
1124                     (?:
1125                         (?:
1126                             (?:".*?")|              # double quotes
1127                             (?:'.*?')|              # single quotes
1128                             (?:[^'",\#][^,\#]*?)    # unquoted
1129                         )
1130                         \s*,\s*                     # comma
1131                     )*      # match all list items ending in a comma (if any)
1132                 )
1133                 (
1134                     (?:".*?")|                      # double quotes
1135                     (?:'.*?')|                      # single quotes
1136                     (?:[^'",\#\s][^,]*?)|           # unquoted
1137                     (?:(?<!,))                      # Empty value
1138                 )?          # last item in a list - or string value
1139             )|
1140             (,)             # alternatively a single comma - empty list
1141         )
1142         \s*(\#.*)?          # optional comment
1143         $''',
1144         re.VERBOSE)
1145
1146     # use findall to get the members of a list value
1147     _listvalueexp = re.compile(r'''
1148         (
1149             (?:".*?")|          # double quotes
1150             (?:'.*?')|          # single quotes
1151             (?:[^'",\#].*?)       # unquoted
1152         )
1153         \s*,\s*                 # comma
1154         ''',
1155         re.VERBOSE)
1156
1157     # this regexp is used for the value
1158     # when lists are switched off
1159     _nolistvalue = re.compile(r'''^
1160         (
1161             (?:".*?")|          # double quotes
1162             (?:'.*?')|          # single quotes
1163             (?:[^'"\#].*?)|     # unquoted
1164             (?:)                # Empty value
1165         )
1166         \s*(\#.*)?              # optional comment
1167         $''',
1168         re.VERBOSE)
1169
1170     # regexes for finding triple quoted values on one line
1171     _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1172     _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1173     _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1174     _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1175
1176     _triple_quote = {
1177         "'''": (_single_line_single, _multi_line_single),
1178         '"""': (_single_line_double, _multi_line_double),
1179     }
1180
1181     # Used by the ``istrue`` Section method
1182     _bools = {
1183         'yes': True, 'no': False,
1184         'on': True, 'off': False,
1185         '1': True, '0': False,
1186         'true': True, 'false': False,
1187         }
1188
1189
1190     def __init__(self, infile=None, options=None, _inspec=False, **kwargs):
1191         """
1192         Parse a config file or create a config file object.
1193         
1194         ``ConfigObj(infile=None, options=None, **kwargs)``
1195         """
1196         self._inspec = _inspec
1197         # init the superclass
1198         Section.__init__(self, self, 0, self)
1199         
1200         infile = infile or []
1201         options = dict(options or {})
1202             
1203         # keyword arguments take precedence over an options dictionary
1204         options.update(kwargs)
1205         if _inspec:
1206             options['list_values'] = False
1207         
1208         defaults = OPTION_DEFAULTS.copy()
1209         # TODO: check the values too.
1210         for entry in options:
1211             if entry not in defaults:
1212                 raise TypeError('Unrecognised option "%s".' % entry)
1213         
1214         # Add any explicit options to the defaults
1215         defaults.update(options)
1216         self._initialise(defaults)
1217         configspec = defaults['configspec']
1218         self._original_configspec = configspec
1219         self._load(infile, configspec)
1220         
1221         
1222     def _load(self, infile, configspec):
1223         if isinstance(infile, basestring):
1224             self.filename = infile
1225             if os.path.isfile(infile):
1226                 h = open(infile, 'rb')
1227                 infile = h.read() or []
1228                 h.close()
1229             elif self.file_error:
1230                 # raise an error if the file doesn't exist
1231                 raise IOError('Config file not found: "%s".' % self.filename)
1232             else:
1233                 # file doesn't already exist
1234                 if self.create_empty:
1235                     # this is a good test that the filename specified
1236                     # isn't impossible - like on a non-existent device
1237                     h = open(infile, 'w')
1238                     h.write('')
1239                     h.close()
1240                 infile = []
1241                 
1242         elif isinstance(infile, (list, tuple)):
1243             infile = list(infile)
1244             
1245         elif isinstance(infile, dict):
1246             # initialise self
1247             # the Section class handles creating subsections
1248             if isinstance(infile, ConfigObj):
1249                 # get a copy of our ConfigObj
1250                 infile = infile.dict()
1251                 
1252             for entry in infile:
1253                 self[entry] = infile[entry]
1254             del self._errors
1255             
1256             if configspec is not None:
1257                 self._handle_configspec(configspec)
1258             else:
1259                 self.configspec = None
1260             return
1261         
1262         elif getattr(infile, 'read', MISSING) is not MISSING:
1263             # This supports file like objects
1264             infile = infile.read() or []
1265             # needs splitting into lines - but needs doing *after* decoding
1266             # in case it's not an 8 bit encoding
1267         else:
1268             raise TypeError('infile must be a filename, file like object, or list of lines.')
1269         
1270         if infile:
1271             # don't do it for the empty ConfigObj
1272             infile = self._handle_bom(infile)
1273             # infile is now *always* a list
1274             #
1275             # Set the newlines attribute (first line ending it finds)
1276             # and strip trailing '\n' or '\r' from lines
1277             for line in infile:
1278                 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1279                     continue
1280                 for end in ('\r\n', '\n', '\r'):
1281                     if line.endswith(end):
1282                         self.newlines = end
1283                         break
1284                 break
1285
1286             infile = [line.rstrip('\r\n') for line in infile]
1287             
1288         self._parse(infile)
1289         # if we had any errors, now is the time to raise them
1290         if self._errors:
1291             info = "at line %s." % self._errors[0].line_number
1292             if len(self._errors) > 1:
1293                 msg = "Parsing failed with several errors.\nFirst error %s" % info
1294                 error = ConfigObjError(msg)
1295             else:
1296                 error = self._errors[0]
1297             # set the errors attribute; it's a list of tuples:
1298             # (error_type, message, line_number)
1299             error.errors = self._errors
1300             # set the config attribute
1301             error.config = self
1302             raise error
1303         # delete private attributes
1304         del self._errors
1305         
1306         if configspec is None:
1307             self.configspec = None
1308         else:
1309             self._handle_configspec(configspec)
1310     
1311     
1312     def _initialise(self, options=None):
1313         if options is None:
1314             options = OPTION_DEFAULTS
1315             
1316         # initialise a few variables
1317         self.filename = None
1318         self._errors = []
1319         self.raise_errors = options['raise_errors']
1320         self.interpolation = options['interpolation']
1321         self.list_values = options['list_values']
1322         self.create_empty = options['create_empty']
1323         self.file_error = options['file_error']
1324         self.stringify = options['stringify']
1325         self.indent_type = options['indent_type']
1326         self.encoding = options['encoding']
1327         self.default_encoding = options['default_encoding']
1328         self.BOM = False
1329         self.newlines = None
1330         self.write_empty_values = options['write_empty_values']
1331         self.unrepr = options['unrepr']
1332         
1333         self.initial_comment = []
1334         self.final_comment = []
1335         self.configspec = None
1336         
1337         if self._inspec:
1338             self.list_values = False
1339         
1340         # Clear section attributes as well
1341         Section._initialise(self)
1342         
1343         
1344     def __repr__(self):
1345         return ('ConfigObj({%s})' % 
1346                 ', '.join([('%s: %s' % (repr(key), repr(self[key]))) 
1347                 for key in (self.scalars + self.sections)]))
1348     
1349     
1350     def _handle_bom(self, infile):
1351         """
1352         Handle any BOM, and decode if necessary.
1353         
1354         If an encoding is specified, that *must* be used - but the BOM should
1355         still be removed (and the BOM attribute set).
1356         
1357         (If the encoding is wrongly specified, then a BOM for an alternative
1358         encoding won't be discovered or removed.)
1359         
1360         If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1361         removed. The BOM attribute will be set. UTF16 will be decoded to
1362         unicode.
1363         
1364         NOTE: This method must not be called with an empty ``infile``.
1365         
1366         Specifying the *wrong* encoding is likely to cause a
1367         ``UnicodeDecodeError``.
1368         
1369         ``infile`` must always be returned as a list of lines, but may be
1370         passed in as a single string.
1371         """
1372         if ((self.encoding is not None) and
1373             (self.encoding.lower() not in BOM_LIST)):
1374             # No need to check for a BOM
1375             # the encoding specified doesn't have one
1376             # just decode
1377             return self._decode(infile, self.encoding)
1378         
1379         if isinstance(infile, (list, tuple)):
1380             line = infile[0]
1381         else:
1382             line = infile
1383         if self.encoding is not None:
1384             # encoding explicitly supplied
1385             # And it could have an associated BOM
1386             # TODO: if encoding is just UTF16 - we ought to check for both
1387             # TODO: big endian and little endian versions.
1388             enc = BOM_LIST[self.encoding.lower()]
1389             if enc == 'utf_16':
1390                 # For UTF16 we try big endian and little endian
1391                 for BOM, (encoding, final_encoding) in BOMS.items():
1392                     if not final_encoding:
1393                         # skip UTF8
1394                         continue
1395                     if infile.startswith(BOM):
1396                         ### BOM discovered
1397                         ##self.BOM = True
1398                         # Don't need to remove BOM
1399                         return self._decode(infile, encoding)
1400                     
1401                 # If we get this far, will *probably* raise a DecodeError
1402                 # As it doesn't appear to start with a BOM
1403                 return self._decode(infile, self.encoding)
1404             
1405             # Must be UTF8
1406             BOM = BOM_SET[enc]
1407             if not line.startswith(BOM):
1408                 return self._decode(infile, self.encoding)
1409             
1410             newline = line[len(BOM):]
1411             
1412             # BOM removed
1413             if isinstance(infile, (list, tuple)):
1414                 infile[0] = newline
1415             else:
1416                 infile = newline
1417             self.BOM = True
1418             return self._decode(infile, self.encoding)
1419         
1420         # No encoding specified - so we need to check for UTF8/UTF16
1421         for BOM, (encoding, final_encoding) in BOMS.items():
1422             if not line.startswith(BOM):
1423                 continue
1424             else:
1425                 # BOM discovered
1426                 self.encoding = final_encoding
1427                 if not final_encoding:
1428                     self.BOM = True
1429                     # UTF8
1430                     # remove BOM
1431                     newline = line[len(BOM):]
1432                     if isinstance(infile, (list, tuple)):
1433                         infile[0] = newline
1434                     else:
1435                         infile = newline
1436                     # UTF8 - don't decode
1437                     if isinstance(infile, basestring):
1438                         return infile.splitlines(True)
1439                     else:
1440                         return infile
1441                 # UTF16 - have to decode
1442                 return self._decode(infile, encoding)
1443             
1444         # No BOM discovered and no encoding specified, just return
1445         if isinstance(infile, basestring):
1446             # infile read from a file will be a single string
1447             return infile.splitlines(True)
1448         return infile
1449
1450
1451     def _a_to_u(self, aString):
1452         """Decode ASCII strings to unicode if a self.encoding is specified."""
1453         if self.encoding:
1454             return aString.decode('ascii')
1455         else:
1456             return aString
1457
1458
1459     def _decode(self, infile, encoding):
1460         """
1461         Decode infile to unicode. Using the specified encoding.
1462         
1463         if is a string, it also needs converting to a list.
1464         """
1465         if isinstance(infile, basestring):
1466             # can't be unicode
1467             # NOTE: Could raise a ``UnicodeDecodeError``
1468             return infile.decode(encoding).splitlines(True)
1469         for i, line in enumerate(infile):
1470             if not isinstance(line, unicode):
1471                 # NOTE: The isinstance test here handles mixed lists of unicode/string
1472                 # NOTE: But the decode will break on any non-string values
1473                 # NOTE: Or could raise a ``UnicodeDecodeError``
1474                 infile[i] = line.decode(encoding)
1475         return infile
1476
1477
1478     def _decode_element(self, line):
1479         """Decode element to unicode if necessary."""
1480         if not self.encoding:
1481             return line
1482         if isinstance(line, str) and self.default_encoding:
1483             return line.decode(self.default_encoding)
1484         return line
1485
1486
1487     def _str(self, value):
1488         """
1489         Used by ``stringify`` within validate, to turn non-string values
1490         into strings.
1491         """
1492         if not isinstance(value, basestring):
1493             return str(value)
1494         else:
1495             return value
1496
1497
1498     def _parse(self, infile):
1499         """Actually parse the config file."""
1500         temp_list_values = self.list_values
1501         if self.unrepr:
1502             self.list_values = False
1503             
1504         comment_list = []
1505         done_start = False
1506         this_section = self
1507         maxline = len(infile) - 1
1508         cur_index = -1
1509         reset_comment = False
1510         
1511         while cur_index < maxline:
1512             if reset_comment:
1513                 comment_list = []
1514             cur_index += 1
1515             line = infile[cur_index]
1516             sline = line.strip()
1517             # do we have anything on the line ?
1518             if not sline or sline.startswith('#'):
1519                 reset_comment = False
1520                 comment_list.append(line)
1521                 continue
1522             
1523             if not done_start:
1524                 # preserve initial comment
1525                 self.initial_comment = comment_list
1526                 comment_list = []
1527                 done_start = True
1528                 
1529             reset_comment = True
1530             # first we check if it's a section marker
1531             mat = self._sectionmarker.match(line)
1532             if mat is not None:
1533                 # is a section line
1534                 (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1535                 if indent and (self.indent_type is None):
1536                     self.indent_type = indent
1537                 cur_depth = sect_open.count('[')
1538                 if cur_depth != sect_close.count(']'):
1539                     self._handle_error("Cannot compute the section depth at line %s.",
1540                                        NestingError, infile, cur_index)
1541                     continue
1542                 
1543                 if cur_depth < this_section.depth:
1544                     # the new section is dropping back to a previous level
1545                     try:
1546                         parent = self._match_depth(this_section,
1547                                                    cur_depth).parent
1548                     except SyntaxError:
1549                         self._handle_error("Cannot compute nesting level at line %s.",
1550                                            NestingError, infile, cur_index)
1551                         continue
1552                 elif cur_depth == this_section.depth:
1553                     # the new section is a sibling of the current section
1554                     parent = this_section.parent
1555                 elif cur_depth == this_section.depth + 1:
1556                     # the new section is a child the current section
1557                     parent = this_section
1558                 else:
1559                     self._handle_error("Section too nested at line %s.",
1560                                        NestingError, infile, cur_index)
1561                     
1562                 sect_name = self._unquote(sect_name)
1563                 if parent.has_key(sect_name):
1564                     self._handle_error('Duplicate section name at line %s.',
1565                                        DuplicateError, infile, cur_index)
1566                     continue
1567                 
1568                 # create the new section
1569                 this_section = Section(
1570                     parent,
1571                     cur_depth,
1572                     self,
1573                     name=sect_name)
1574                 parent[sect_name] = this_section
1575                 parent.inline_comments[sect_name] = comment
1576                 parent.comments[sect_name] = comment_list
1577                 continue
1578             #
1579             # it's not a section marker,
1580             # so it should be a valid ``key = value`` line
1581             mat = self._keyword.match(line)
1582             if mat is None:
1583                 # it neither matched as a keyword
1584                 # or a section marker
1585                 self._handle_error(
1586                     'Invalid line at line "%s".',
1587                     ParseError, infile, cur_index)
1588             else:
1589                 # is a keyword value
1590                 # value will include any inline comment
1591                 (indent, key, value) = mat.groups()
1592                 if indent and (self.indent_type is None):
1593                     self.indent_type = indent
1594                 # check for a multiline value
1595                 if value[:3] in ['"""', "'''"]:
1596                     try:
1597                         (value, comment, cur_index) = self._multiline(
1598                             value, infile, cur_index, maxline)
1599                     except SyntaxError:
1600                         self._handle_error(
1601                             'Parse error in value at line %s.',
1602                             ParseError, infile, cur_index)
1603                         continue
1604                     else:
1605                         if self.unrepr:
1606                             comment = ''
1607                             try:
1608                                 value = unrepr(value)
1609                             except Exception, e:
1610                                 if type(e) == UnknownType:
1611                                     msg = 'Unknown name or type in value at line %s.'
1612                                 else:
1613                                     msg = 'Parse error in value at line %s.'
1614                                 self._handle_error(msg, UnreprError, infile,
1615                                     cur_index)
1616                                 continue
1617                 else:
1618                     if self.unrepr:
1619                         comment = ''
1620                         try:
1621                             value = unrepr(value)
1622                         except Exception, e:
1623                             if isinstance(e, UnknownType):
1624                                 msg = 'Unknown name or type in value at line %s.'
1625                             else:
1626                                 msg = 'Parse error in value at line %s.'
1627                             self._handle_error(msg, UnreprError, infile,
1628                                 cur_index)
1629                             continue
1630                     else:
1631                         # extract comment and lists
1632                         try:
1633                             (value, comment) = self._handle_value(value)
1634                         except SyntaxError:
1635                             self._handle_error(
1636                                 'Parse error in value at line %s.',
1637                                 ParseError, infile, cur_index)
1638                             continue
1639                 #
1640                 key = self._unquote(key)
1641                 if this_section.has_key(key):
1642                     self._handle_error(
1643                         'Duplicate keyword name at line %s.',
1644                         DuplicateError, infile, cur_index)
1645                     continue
1646                 # add the key.
1647                 # we set unrepr because if we have got this far we will never
1648                 # be creating a new section
1649                 this_section.__setitem__(key, value, unrepr=True)
1650                 this_section.inline_comments[key] = comment
1651                 this_section.comments[key] = comment_list
1652                 continue
1653         #
1654         if self.indent_type is None:
1655             # no indentation used, set the type accordingly
1656             self.indent_type = ''
1657
1658         # preserve the final comment
1659         if not self and not self.initial_comment:
1660             self.initial_comment = comment_list
1661         elif not reset_comment:
1662             self.final_comment = comment_list
1663         self.list_values = temp_list_values
1664
1665
1666     def _match_depth(self, sect, depth):
1667         """
1668         Given a section and a depth level, walk back through the sections
1669         parents to see if the depth level matches a previous section.
1670         
1671         Return a reference to the right section,
1672         or raise a SyntaxError.
1673         """
1674         while depth < sect.depth:
1675             if sect is sect.parent:
1676                 # we've reached the top level already
1677                 raise SyntaxError()
1678             sect = sect.parent
1679         if sect.depth == depth:
1680             return sect
1681         # shouldn't get here
1682         raise SyntaxError()
1683
1684
1685     def _handle_error(self, text, ErrorClass, infile, cur_index):
1686         """
1687         Handle an error according to the error settings.
1688         
1689         Either raise the error or store it.
1690         The error will have occured at ``cur_index``
1691         """
1692         line = infile[cur_index]
1693         cur_index += 1
1694         message = text % cur_index
1695         error = ErrorClass(message, cur_index, line)
1696         if self.raise_errors:
1697             # raise the error - parsing stops here
1698             raise error
1699         # store the error
1700         # reraise when parsing has finished
1701         self._errors.append(error)
1702
1703
1704     def _unquote(self, value):
1705         """Return an unquoted version of a value"""
1706         if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1707             value = value[1:-1]
1708         return value
1709
1710
1711     def _quote(self, value, multiline=True):
1712         """
1713         Return a safely quoted version of a value.
1714         
1715         Raise a ConfigObjError if the value cannot be safely quoted.
1716         If multiline is ``True`` (default) then use triple quotes
1717         if necessary.
1718         
1719         * Don't quote values that don't need it.
1720         * Recursively quote members of a list and return a comma joined list.
1721         * Multiline is ``False`` for lists.
1722         * Obey list syntax for empty and single member lists.
1723         
1724         If ``list_values=False`` then the value is only quoted if it contains
1725         a ``\\n`` (is multiline) or '#'.
1726         
1727         If ``write_empty_values`` is set, and the value is an empty string, it
1728         won't be quoted.
1729         """
1730         if multiline and self.write_empty_values and value == '':
1731             # Only if multiline is set, so that it is used for values not
1732             # keys, and not values that are part of a list
1733             return ''
1734         
1735         if multiline and isinstance(value, (list, tuple)):
1736             if not value:
1737                 return ','
1738             elif len(value) == 1:
1739                 return self._quote(value[0], multiline=False) + ','
1740             return ', '.join([self._quote(val, multiline=False)
1741                 for val in value])
1742         if not isinstance(value, basestring):
1743             if self.stringify:
1744                 value = str(value)
1745             else:
1746                 raise TypeError('Value "%s" is not a string.' % value)
1747
1748         if not value:
1749             return '""'
1750         
1751         no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1752         need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1753         hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1754         check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1755         
1756         if check_for_single:
1757             if not self.list_values:
1758                 # we don't quote if ``list_values=False``
1759                 quot = noquot
1760             # for normal values either single or double quotes will do
1761             elif '\n' in value:
1762                 # will only happen if multiline is off - e.g. '\n' in key
1763                 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1764             elif ((value[0] not in wspace_plus) and
1765                     (value[-1] not in wspace_plus) and
1766                     (',' not in value)):
1767                 quot = noquot
1768             else:
1769                 quot = self._get_single_quote(value)
1770         else:
1771             # if value has '\n' or "'" *and* '"', it will need triple quotes
1772             quot = self._get_triple_quote(value)
1773         
1774         if quot == noquot and '#' in value and self.list_values:
1775             quot = self._get_single_quote(value)
1776                 
1777         return quot % value
1778     
1779     
1780     def _get_single_quote(self, value):
1781         if ("'" in value) and ('"' in value):
1782             raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1783         elif '"' in value:
1784             quot = squot
1785         else:
1786             quot = dquot
1787         return quot
1788     
1789     
1790     def _get_triple_quote(self, value):
1791         if (value.find('"""') != -1) and (value.find("'''") != -1):
1792             raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1793         if value.find('"""') == -1:
1794             quot = tdquot
1795         else:
1796             quot = tsquot 
1797         return quot
1798
1799
1800     def _handle_value(self, value):
1801         """
1802         Given a value string, unquote, remove comment,
1803         handle lists. (including empty and single member lists)
1804         """
1805         if self._inspec:
1806             # Parsing a configspec so don't handle comments
1807             return (value, '')
1808         # do we look for lists in values ?
1809         if not self.list_values:
1810             mat = self._nolistvalue.match(value)
1811             if mat is None:
1812                 raise SyntaxError()
1813             # NOTE: we don't unquote here
1814             return mat.groups()
1815         #
1816         mat = self._valueexp.match(value)
1817         if mat is None:
1818             # the value is badly constructed, probably badly quoted,
1819             # or an invalid list
1820             raise SyntaxError()
1821         (list_values, single, empty_list, comment) = mat.groups()
1822         if (list_values == '') and (single is None):
1823             # change this if you want to accept empty values
1824             raise SyntaxError()
1825         # NOTE: note there is no error handling from here if the regex
1826         # is wrong: then incorrect values will slip through
1827         if empty_list is not None:
1828             # the single comma - meaning an empty list
1829             return ([], comment)
1830         if single is not None:
1831             # handle empty values
1832             if list_values and not single:
1833                 # FIXME: the '' is a workaround because our regex now matches
1834                 #   '' at the end of a list if it has a trailing comma
1835                 single = None
1836             else:
1837                 single = single or '""'
1838                 single = self._unquote(single)
1839         if list_values == '':
1840             # not a list value
1841             return (single, comment)
1842         the_list = self._listvalueexp.findall(list_values)
1843         the_list = [self._unquote(val) for val in the_list]
1844         if single is not None:
1845             the_list += [single]
1846         return (the_list, comment)
1847
1848
1849     def _multiline(self, value, infile, cur_index, maxline):
1850         """Extract the value, where we are in a multiline situation."""
1851         quot = value[:3]
1852         newvalue = value[3:]
1853         single_line = self._triple_quote[quot][0]
1854         multi_line = self._triple_quote[quot][1]
1855         mat = single_line.match(value)
1856         if mat is not None:
1857             retval = list(mat.groups())
1858             retval.append(cur_index)
1859             return retval
1860         elif newvalue.find(quot) != -1:
1861             # somehow the triple quote is missing
1862             raise SyntaxError()
1863         #
1864         while cur_index < maxline:
1865             cur_index += 1
1866             newvalue += '\n'
1867             line = infile[cur_index]
1868             if line.find(quot) == -1:
1869                 newvalue += line
1870             else:
1871                 # end of multiline, process it
1872                 break
1873         else:
1874             # we've got to the end of the config, oops...
1875             raise SyntaxError()
1876         mat = multi_line.match(line)
1877         if mat is None:
1878             # a badly formed line
1879             raise SyntaxError()
1880         (value, comment) = mat.groups()
1881         return (newvalue + value, comment, cur_index)
1882
1883
1884     def _handle_configspec(self, configspec):
1885         """Parse the configspec."""
1886         # FIXME: Should we check that the configspec was created with the 
1887         #        correct settings ? (i.e. ``list_values=False``)
1888         if not isinstance(configspec, ConfigObj):
1889             try:
1890                 configspec = ConfigObj(configspec,
1891                                        raise_errors=True,
1892                                        file_error=True,
1893                                        _inspec=True)
1894             except ConfigObjError, e:
1895                 # FIXME: Should these errors have a reference
1896                 #        to the already parsed ConfigObj ?
1897                 raise ConfigspecError('Parsing configspec failed: %s' % e)
1898             except IOError, e:
1899                 raise IOError('Reading configspec failed: %s' % e)
1900         
1901         self.configspec = configspec
1902             
1903
1904         
1905     def _set_configspec(self, section, copy):
1906         """
1907         Called by validate. Handles setting the configspec on subsections
1908         including sections to be validated by __many__
1909         """
1910         configspec = section.configspec
1911         many = configspec.get('__many__')
1912         if isinstance(many, dict):
1913             for entry in section.sections:
1914                 if entry not in configspec:
1915                     section[entry].configspec = many
1916                     
1917         for entry in configspec.sections:
1918             if entry == '__many__':
1919                 continue
1920             if entry not in section:
1921                 section[entry] = {}
1922                 if copy:
1923                     # copy comments
1924                     section.comments[entry] = configspec.comments.get(entry, [])
1925                     section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
1926                 
1927             # Could be a scalar when we expect a section
1928             if isinstance(section[entry], Section):
1929                 section[entry].configspec = configspec[entry]
1930                         
1931
1932     def _write_line(self, indent_string, entry, this_entry, comment):
1933         """Write an individual line, for the write method"""
1934         # NOTE: the calls to self._quote here handles non-StringType values.
1935         if not self.unrepr:
1936             val = self._decode_element(self._quote(this_entry))
1937         else:
1938             val = repr(this_entry)
1939         return '%s%s%s%s%s' % (indent_string,
1940                                self._decode_element(self._quote(entry, multiline=False)),
1941                                self._a_to_u(' = '),
1942                                val,
1943                                self._decode_element(comment))
1944
1945
1946     def _write_marker(self, indent_string, depth, entry, comment):
1947         """Write a section marker line"""
1948         return '%s%s%s%s%s' % (indent_string,
1949                                self._a_to_u('[' * depth),
1950                                self._quote(self._decode_element(entry), multiline=False),
1951                                self._a_to_u(']' * depth),
1952                                self._decode_element(comment))
1953
1954
1955     def _handle_comment(self, comment):
1956         """Deal with a comment."""
1957         if not comment:
1958             return ''
1959         start = self.indent_type
1960         if not comment.startswith('#'):
1961             start += self._a_to_u(' # ')
1962         return (start + comment)
1963
1964
1965     # Public methods
1966
1967     def write(self, outfile=None, section=None):
1968         """
1969         Write the current ConfigObj as a file
1970         
1971         tekNico: FIXME: use StringIO instead of real files
1972         
1973         >>> filename = a.filename
1974         >>> a.filename = 'test.ini'
1975         >>> a.write()
1976         >>> a.filename = filename
1977         >>> a == ConfigObj('test.ini', raise_errors=True)
1978         1
1979         """
1980         if self.indent_type is None:
1981             # this can be true if initialised from a dictionary
1982             self.indent_type = DEFAULT_INDENT_TYPE
1983             
1984         out = []
1985         cs = self._a_to_u('#')
1986         csp = self._a_to_u('# ')
1987         if section is None:
1988             int_val = self.interpolation
1989             self.interpolation = False
1990             section = self
1991             for line in self.initial_comment:
1992                 line = self._decode_element(line)
1993                 stripped_line = line.strip()
1994                 if stripped_line and not stripped_line.startswith(cs):
1995                     line = csp + line
1996                 out.append(line)
1997                 
1998         indent_string = self.indent_type * section.depth
1999         for entry in (section.scalars + section.sections):
2000             if entry in section.defaults:
2001                 # don't write out default values
2002                 continue
2003             for comment_line in section.comments[entry]:
2004                 comment_line = self._decode_element(comment_line.lstrip())
2005                 if comment_line and not comment_line.startswith(cs):
2006                     comment_line = csp + comment_line
2007                 out.append(indent_string + comment_line)
2008             this_entry = section[entry]
2009             comment = self._handle_comment(section.inline_comments[entry])
2010             
2011             if isinstance(this_entry, dict):
2012                 # a section
2013                 out.append(self._write_marker(
2014                     indent_string,
2015                     this_entry.depth,
2016                     entry,
2017                     comment))
2018                 out.extend(self.write(section=this_entry))
2019             else:
2020                 out.append(self._write_line(
2021                     indent_string,
2022                     entry,
2023                     this_entry,
2024                     comment))
2025                 
2026         if section is self:
2027             for line in self.final_comment:
2028                 line = self._decode_element(line)
2029                 stripped_line = line.strip()
2030                 if stripped_line and not stripped_line.startswith(cs):
2031                     line = csp + line
2032                 out.append(line)
2033             self.interpolation = int_val
2034             
2035         if section is not self:
2036             return out
2037         
2038         if (self.filename is None) and (outfile is None):
2039             # output a list of lines
2040             # might need to encode
2041             # NOTE: This will *screw* UTF16, each line will start with the BOM
2042             if self.encoding:
2043                 out = [l.encode(self.encoding) for l in out]
2044             if (self.BOM and ((self.encoding is None) or
2045                 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2046                 # Add the UTF8 BOM
2047                 if not out:
2048                     out.append('')
2049                 out[0] = BOM_UTF8 + out[0]
2050             return out
2051         
2052         # Turn the list to a string, joined with correct newlines
2053         newline = self.newlines or os.linesep
2054         output = self._a_to_u(newline).join(out)
2055         if self.encoding:
2056             output = output.encode(self.encoding)
2057         if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2058             # Add the UTF8 BOM
2059             output = BOM_UTF8 + output
2060             
2061         if not output.endswith(newline):
2062             output += newline
2063         if outfile is not None:
2064             outfile.write(output)
2065         else:
2066             h = open(self.filename, 'wb')
2067             h.write(output)
2068             h.close()
2069
2070
2071     def validate(self, validator, preserve_errors=False, copy=False,
2072                  section=None):
2073         """
2074         Test the ConfigObj against a configspec.
2075         
2076         It uses the ``validator`` object from *validate.py*.
2077         
2078         To run ``validate`` on the current ConfigObj, call: ::
2079         
2080             test = config.validate(validator)
2081         
2082         (Normally having previously passed in the configspec when the ConfigObj
2083         was created - you can dynamically assign a dictionary of checks to the
2084         ``configspec`` attribute of a section though).
2085         
2086         It returns ``True`` if everything passes, or a dictionary of
2087         pass/fails (True/False). If every member of a subsection passes, it
2088         will just have the value ``True``. (It also returns ``False`` if all
2089         members fail).
2090         
2091         In addition, it converts the values from strings to their native
2092         types if their checks pass (and ``stringify`` is set).
2093         
2094         If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2095         of a marking a fail with a ``False``, it will preserve the actual
2096         exception object. This can contain info about the reason for failure.
2097         For example the ``VdtValueTooSmallError`` indicates that the value
2098         supplied was too small. If a value (or section) is missing it will
2099         still be marked as ``False``.
2100         
2101         You must have the validate module to use ``preserve_errors=True``.
2102         
2103         You can then use the ``flatten_errors`` function to turn your nested
2104         results dictionary into a flattened list of failures - useful for
2105         displaying meaningful error messages.
2106         """
2107         if section is None:
2108             if self.configspec is None:
2109                 raise ValueError('No configspec supplied.')
2110             if preserve_errors:
2111                 # We do this once to remove a top level dependency on the validate module
2112                 # Which makes importing configobj faster
2113                 from validate import VdtMissingValue
2114                 self._vdtMissingValue = VdtMissingValue
2115                 
2116             section = self
2117
2118             if copy:
2119                 section.initial_comment = section.configspec.initial_comment
2120                 section.final_comment = section.configspec.final_comment
2121                 section.encoding = section.configspec.encoding
2122                 section.BOM = section.configspec.BOM
2123                 section.newlines = section.configspec.newlines
2124                 section.indent_type = section.configspec.indent_type
2125             
2126         #
2127         configspec = section.configspec
2128         self._set_configspec(section, copy)
2129         
2130         def validate_entry(entry, spec, val, missing, ret_true, ret_false):
2131             try:
2132                 check = validator.check(spec,
2133                                         val,
2134                                         missing=missing
2135                                         )
2136             except validator.baseErrorClass, e:
2137                 if not preserve_errors or isinstance(e, self._vdtMissingValue):
2138                     out[entry] = False
2139                 else:
2140                     # preserve the error
2141                     out[entry] = e
2142                     ret_false = False
2143                 ret_true = False
2144             else:
2145                 try: 
2146                     section.default_values.pop(entry, None)
2147                 except AttributeError: 
2148                     # For Python 2.2 compatibility
2149                     try:
2150                         del section.default_values[entry]
2151                     except KeyError:
2152                         pass
2153                     
2154                 try: 
2155                     section.default_values[entry] = validator.get_default_value(configspec[entry])
2156                 except (KeyError, AttributeError):
2157                     # No default or validator has no 'get_default_value' (e.g. SimpleVal)
2158                     pass
2159                     
2160                 ret_false = False
2161                 out[entry] = True
2162                 if self.stringify or missing:
2163                     # if we are doing type conversion
2164                     # or the value is a supplied default
2165                     if not self.stringify:
2166                         if isinstance(check, (list, tuple)):
2167                             # preserve lists
2168                             check = [self._str(item) for item in check]
2169                         elif missing and check is None:
2170                             # convert the None from a default to a ''
2171                             check = ''
2172                         else:
2173                             check = self._str(check)
2174                     if (check != val) or missing:
2175                         section[entry] = check
2176                 if not copy and missing and entry not in section.defaults:
2177                     section.defaults.append(entry)
2178             return ret_true, ret_false
2179         
2180         #
2181         out = {}
2182         ret_true = True
2183         ret_false = True
2184         
2185         unvalidated = [k for k in section.scalars if k not in configspec]
2186         incorrect_sections = [k for k in configspec.sections if k in section.scalars]        
2187         incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
2188         
2189         for entry in configspec.scalars:
2190             if entry in ('__many__', '___many___'):
2191                 # reserved names
2192                 continue
2193             
2194             if (not entry in section.scalars) or (entry in section.defaults):
2195                 # missing entries
2196                 # or entries from defaults
2197                 missing = True
2198                 val = None
2199                 if copy and not entry in section.scalars:
2200                     # copy comments
2201                     section.comments[entry] = (
2202                         configspec.comments.get(entry, []))
2203                     section.inline_comments[entry] = (
2204                         configspec.inline_comments.get(entry, ''))
2205                 #
2206             else:
2207                 missing = False
2208                 val = section[entry]
2209                 
2210             ret_true, ret_false = validate_entry(entry, configspec[entry], val, 
2211                                                  missing, ret_true, ret_false)
2212         
2213         many = None
2214         if '__many__' in configspec.scalars:
2215             many = configspec['__many__']
2216         elif '___many___' in configspec.scalars:
2217             many = configspec['___many___']
2218         
2219         if many is not None:
2220             for entry in unvalidated:
2221                 val = section[entry]
2222                 ret_true, ret_false = validate_entry(entry, many, val, False,
2223                                                      ret_true, ret_false)
2224
2225         for entry in incorrect_scalars:
2226             ret_true = False
2227             if not preserve_errors:
2228                 out[entry] = False
2229             else:
2230                 ret_false = False
2231                 msg = 'Value %r was provided as a section' % entry
2232                 out[entry] = validator.baseErrorClass(msg)
2233         for entry in incorrect_sections:
2234             ret_true = False
2235             if not preserve_errors:
2236                 out[entry] = False
2237             else:
2238                 ret_false = False
2239                 msg = 'Section %r was provided as a single value' % entry
2240                 out[entry] = validator.baseErrorClass(msg)
2241                 
2242         # Missing sections will have been created as empty ones when the
2243         # configspec was read.
2244         for entry in section.sections:
2245             # FIXME: this means DEFAULT is not copied in copy mode
2246             if section is self and entry == 'DEFAULT':
2247                 continue
2248             if section[entry].configspec is None:
2249                 continue
2250             if copy:
2251                 section.comments[entry] = configspec.comments.get(entry, [])
2252                 section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
2253             check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
2254             out[entry] = check
2255             if check == False:
2256                 ret_true = False
2257             elif check == True:
2258                 ret_false = False
2259             else:
2260                 ret_true = False
2261                 ret_false = False
2262         #
2263         if ret_true:
2264             return True
2265         elif ret_false:
2266             return False
2267         return out
2268
2269
2270     def reset(self):
2271         """Clear ConfigObj instance and restore to 'freshly created' state."""
2272         self.clear()
2273         self._initialise()
2274         # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2275         #        requires an empty dictionary
2276         self.configspec = None
2277         # Just to be sure ;-)
2278         self._original_configspec = None
2279         
2280         
2281     def reload(self):
2282         """
2283         Reload a ConfigObj from file.
2284         
2285         This method raises a ``ReloadError`` if the ConfigObj doesn't have
2286         a filename attribute pointing to a file.
2287         """
2288         if not isinstance(self.filename, basestring):
2289             raise ReloadError()
2290
2291         filename = self.filename
2292         current_options = {}
2293         for entry in OPTION_DEFAULTS:
2294             if entry == 'configspec':
2295                 continue
2296             current_options[entry] = getattr(self, entry)
2297             
2298         configspec = self._original_configspec
2299         current_options['configspec'] = configspec
2300             
2301         self.clear()
2302         self._initialise(current_options)
2303         self._load(filename, configspec)
2304         
2305
2306
2307 class SimpleVal(object):
2308     """
2309     A simple validator.
2310     Can be used to check that all members expected are present.
2311     
2312     To use it, provide a configspec with all your members in (the value given
2313     will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2314     method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2315     members are present, or a dictionary with True/False meaning
2316     present/missing. (Whole missing sections will be replaced with ``False``)
2317     """
2318     
2319     def __init__(self):
2320         self.baseErrorClass = ConfigObjError
2321     
2322     def check(self, check, member, missing=False):
2323         """A dummy check method, always returns the value unchanged."""
2324         if missing:
2325             raise self.baseErrorClass()
2326         return member
2327
2328
2329 # Check / processing functions for options
2330 def flatten_errors(cfg, res, levels=None, results=None):
2331     """
2332     An example function that will turn a nested dictionary of results
2333     (as returned by ``ConfigObj.validate``) into a flat list.
2334     
2335     ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2336     dictionary returned by ``validate``.
2337     
2338     (This is a recursive function, so you shouldn't use the ``levels`` or
2339     ``results`` arguments - they are used by the function.)
2340     
2341     Returns a list of keys that failed. Each member of the list is a tuple :
2342     
2343     ::
2344     
2345         ([list of sections...], key, result)
2346     
2347     If ``validate`` was called with ``preserve_errors=False`` (the default)
2348     then ``result`` will always be ``False``.
2349
2350     *list of sections* is a flattened list of sections that the key was found
2351     in.
2352     
2353     If the section was missing (or a section was expected and a scalar provided
2354     - or vice-versa) then key will be ``None``.
2355     
2356     If the value (or section) was missing then ``result`` will be ``False``.
2357     
2358     If ``validate`` was called with ``preserve_errors=True`` and a value
2359     was present, but failed the check, then ``result`` will be the exception
2360     object returned. You can use this as a string that describes the failure.
2361     
2362     For example *The value "3" is of the wrong type*.
2363     
2364     >>> import validate
2365     >>> vtor = validate.Validator()
2366     >>> my_ini = '''
2367     ...     option1 = True
2368     ...     [section1]
2369     ...     option1 = True
2370     ...     [section2]
2371     ...     another_option = Probably
2372     ...     [section3]
2373     ...     another_option = True
2374     ...     [[section3b]]
2375     ...     value = 3
2376     ...     value2 = a
2377     ...     value3 = 11
2378     ...     '''
2379     >>> my_cfg = '''
2380     ...     option1 = boolean()
2381     ...     option2 = boolean()
2382     ...     option3 = boolean(default=Bad_value)
2383     ...     [section1]
2384     ...     option1 = boolean()
2385     ...     option2 = boolean()
2386     ...     option3 = boolean(default=Bad_value)
2387     ...     [section2]
2388     ...     another_option = boolean()
2389     ...     [section3]
2390     ...     another_option = boolean()
2391     ...     [[section3b]]
2392     ...     value = integer
2393     ...     value2 = integer
2394     ...     value3 = integer(0, 10)
2395     ...         [[[section3b-sub]]]
2396     ...         value = string
2397     ...     [section4]
2398     ...     another_option = boolean()
2399     ...     '''
2400     >>> cs = my_cfg.split('\\n')
2401     >>> ini = my_ini.split('\\n')
2402     >>> cfg = ConfigObj(ini, configspec=cs)
2403     >>> res = cfg.validate(vtor, preserve_errors=True)
2404     >>> errors = []
2405     >>> for entry in flatten_errors(cfg, res):
2406     ...     section_list, key, error = entry
2407     ...     section_list.insert(0, '[root]')
2408     ...     if key is not None:
2409     ...        section_list.append(key)
2410     ...     else:
2411     ...         section_list.append('[missing]')
2412     ...     section_string = ', '.join(section_list)
2413     ...     errors.append((section_string, ' = ', error))
2414     >>> errors.sort()
2415     >>> for entry in errors:
2416     ...     print entry[0], entry[1], (entry[2] or 0)
2417     [root], option2  =  0
2418     [root], option3  =  the value "Bad_value" is of the wrong type.
2419     [root], section1, option2  =  0
2420     [root], section1, option3  =  the value "Bad_value" is of the wrong type.
2421     [root], section2, another_option  =  the value "Probably" is of the wrong type.
2422     [root], section3, section3b, section3b-sub, [missing]  =  0
2423     [root], section3, section3b, value2  =  the value "a" is of the wrong type.
2424     [root], section3, section3b, value3  =  the value "11" is too big.
2425     [root], section4, [missing]  =  0
2426     """
2427     if levels is None:
2428         # first time called
2429         levels = []
2430         results = []
2431     if res is True:
2432         return results
2433     if res is False or isinstance(res, Exception):
2434         results.append((levels[:], None, res))
2435         if levels:
2436             levels.pop()
2437         return results
2438     for (key, val) in res.items():
2439         if val == True:
2440             continue
2441         if isinstance(cfg.get(key), dict):
2442             # Go down one level
2443             levels.append(key)
2444             flatten_errors(cfg[key], val, levels, results)
2445             continue
2446         results.append((levels[:], key, val))
2447     #
2448     # Go up one level
2449     if levels:
2450         levels.pop()
2451     #
2452     return results
2453
2454
2455 """*A programming language is a medium of expression.* - Paul Graham"""