root/ipython/branches/saw/sandbox/tconfig/configobj.py

Revision 2527, 81.6 kB (checked in by fperez, 3 years ago)

Put all of tconfig into its own directory.

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