2
# A config file reader/writer that supports nested sections in config files.
 
 
3
# Copyright (C) 2005 Michael Foord, Nicola Larosa
 
 
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
 
 
5
#         nico AT tekNico DOT net
 
 
8
# http://www.voidspace.org.uk/python/configobj.html
 
 
10
# Released subject to the BSD License
 
 
11
# Please see http://www.voidspace.org.uk/python/license.shtml
 
 
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.
 
 
19
from __future__ import generators
 
 
29
    ...     'member': 'value',
 
 
31
    >>> x = ConfigObj(z.write())
 
 
37
INTP_VER = sys.version_info[:2]
 
 
39
    raise RuntimeError("Python v.2.2 or later needed")
 
 
42
from types import StringTypes
 
 
43
from warnings import warn
 
 
44
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
 
 
46
# A dictionary mapping BOM to
 
 
47
# the encoding to decode with, and what to set the
 
 
48
# encoding attribute to.
 
 
50
    BOM_UTF8: ('utf_8', None),
 
 
51
    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
 
 
52
    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
 
 
53
    BOM_UTF16: ('utf_16', 'utf_16'),
 
 
55
# All legal variants of the BOM codecs.
 
 
56
# TODO: the list of aliases is not meant to be exhaustive, is there a
 
 
63
    'utf16_be': 'utf16_be',
 
 
64
    'utf_16_be': 'utf16_be',
 
 
65
    'utf-16be': 'utf16_be',
 
 
66
    'utf16_le': 'utf16_le',
 
 
67
    'utf_16_le': 'utf16_le',
 
 
68
    'utf-16le': 'utf16_le',
 
 
76
# Map of encodings to the BOM to write.
 
 
80
    'utf16_be': BOM_UTF16_BE,
 
 
81
    'utf16_le': BOM_UTF16_LE,
 
 
86
    from validate import VdtMissingValue
 
 
88
    VdtMissingValue = None
 
 
94
        """enumerate for Python 2.2."""
 
 
106
__version__ = '4.2.0beta2'
 
 
108
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
 
 
110
__docformat__ = "restructuredtext en"
 
 
112
# NOTE: Does it make sense to have the following in __all__ ?
 
 
113
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
 
 
114
# NOTE: If used as from configobj import...
 
 
115
# NOTE: They are effectively read only
 
 
118
    'DEFAULT_INDENT_TYPE',
 
 
120
    'MAX_INTERPOL_DEPTH',
 
 
128
    'InterpolationError',
 
 
129
    'InterpolationDepthError',
 
 
130
    'MissingInterpolationOption',
 
 
131
    'RepeatSectionError',
 
 
136
DEFAULT_INDENT_TYPE = ' '
 
 
137
NUM_INDENT_SPACES = 4
 
 
138
MAX_INTERPOL_DEPTH = 10
 
 
141
    'interpolation': True,
 
 
142
    'raise_errors': False,
 
 
144
    'create_empty': False,
 
 
148
    # option may be set to one of ('', ' ', '\t')
 
 
151
    'default_encoding': None,
 
 
154
class ConfigObjError(SyntaxError):
 
 
156
    This is the base class for all errors that ConfigObj raises.
 
 
157
    It is a subclass of SyntaxError.
 
 
159
    >>> raise ConfigObjError
 
 
160
    Traceback (most recent call last):
 
 
163
    def __init__(self, message='', line_number=None, line=''):
 
 
165
        self.line_number = line_number
 
 
166
        self.message = message
 
 
167
        SyntaxError.__init__(self, message)
 
 
169
class NestingError(ConfigObjError):
 
 
171
    This error indicates a level of nesting that doesn't match.
 
 
173
    >>> raise NestingError
 
 
174
    Traceback (most recent call last):
 
 
178
class ParseError(ConfigObjError):
 
 
180
    This error indicates that a line is badly written.
 
 
181
    It is neither a valid ``key = value`` line,
 
 
182
    nor a valid section marker line.
 
 
185
    Traceback (most recent call last):
 
 
189
class DuplicateError(ConfigObjError):
 
 
191
    The keyword or section specified already exists.
 
 
193
    >>> raise DuplicateError
 
 
194
    Traceback (most recent call last):
 
 
198
class ConfigspecError(ConfigObjError):
 
 
200
    An error occured whilst parsing a configspec.
 
 
202
    >>> raise ConfigspecError
 
 
203
    Traceback (most recent call last):
 
 
207
class InterpolationError(ConfigObjError):
 
 
208
    """Base class for the two interpolation errors."""
 
 
210
class InterpolationDepthError(InterpolationError):
 
 
211
    """Maximum interpolation depth exceeded in string interpolation."""
 
 
213
    def __init__(self, option):
 
 
215
        >>> raise InterpolationDepthError('yoda')
 
 
216
        Traceback (most recent call last):
 
 
217
        InterpolationDepthError: max interpolation depth exceeded in value "yoda".
 
 
219
        InterpolationError.__init__(
 
 
221
            'max interpolation depth exceeded in value "%s".' % option)
 
 
223
class RepeatSectionError(ConfigObjError):
 
 
225
    This error indicates additional sections in a section with a
 
 
226
    ``__many__`` (repeated) section.
 
 
228
    >>> raise RepeatSectionError
 
 
229
    Traceback (most recent call last):
 
 
233
class MissingInterpolationOption(InterpolationError):
 
 
234
    """A value specified for interpolation was missing."""
 
 
236
    def __init__(self, option):
 
 
238
        >>> raise MissingInterpolationOption('yoda')
 
 
239
        Traceback (most recent call last):
 
 
240
        MissingInterpolationOption: missing option "yoda" in interpolation.
 
 
242
        InterpolationError.__init__(
 
 
244
            'missing option "%s" in interpolation.' % option)
 
 
248
    A dictionary-like object that represents a section in a config file.
 
 
250
    It does string interpolation if the 'interpolate' attribute
 
 
251
    of the 'main' object is set to True.
 
 
253
    Interpolation is tried first from the 'DEFAULT' section of this object,
 
 
254
    next from the 'DEFAULT' section of the parent, lastly the main object.
 
 
256
    A Section will behave like an ordered dictionary - following the
 
 
257
    order of the ``scalars`` and ``sections`` attributes.
 
 
258
    You can use this to change the order of members.
 
 
260
    Iteration follows the order: scalars, then sections.
 
 
263
    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
 
 
265
    def __init__(self, parent, depth, main, indict=None, name=None):
 
 
267
        * parent is the section above
 
 
268
        * depth is the depth level of this section
 
 
269
        * main is the main ConfigObj
 
 
270
        * indict is a dictionary to initialise the section with
 
 
275
        # used for nesting level *and* interpolation
 
 
277
        # used for the interpolation attribute
 
 
279
        # level of nesting depth of this Section
 
 
281
        # the sequence of scalar values in this Section
 
 
283
        # the sequence of sections in this Section
 
 
285
        # purely for information
 
 
289
        self.inline_comments = {}
 
 
295
        # we do this explicitly so that __setitem__ is used properly
 
 
296
        # (rather than just passing to ``dict.__init__``)
 
 
298
            self[entry] = indict[entry]
 
 
300
    def _interpolate(self, value):
 
 
301
        """Nicked from ConfigParser."""
 
 
302
        depth = MAX_INTERPOL_DEPTH
 
 
303
        # loop through this until it's done
 
 
306
            if value.find("%(") != -1:
 
 
307
                value = self._KEYCRE.sub(self._interpolation_replace, value)
 
 
311
            raise InterpolationDepthError(value)
 
 
314
    def _interpolation_replace(self, match):
 
 
320
            # switch off interpolation before we try and fetch anything !
 
 
321
            self.main.interpolation = False
 
 
322
            # try the 'DEFAULT' member of *this section* first
 
 
323
            val = self.get('DEFAULT', {}).get(s)
 
 
324
            # try the 'DEFAULT' member of the *parent section* next
 
 
326
                val = self.parent.get('DEFAULT', {}).get(s)
 
 
327
            # last, try the 'DEFAULT' member of the *main section*
 
 
329
                val = self.main.get('DEFAULT', {}).get(s)
 
 
330
            self.main.interpolation = True
 
 
332
                raise MissingInterpolationOption(s)
 
 
335
    def __getitem__(self, key):
 
 
336
        """Fetch the item and do string interpolation."""
 
 
337
        val = dict.__getitem__(self, key)
 
 
338
        if self.main.interpolation and isinstance(val, StringTypes):
 
 
339
            return self._interpolate(val)
 
 
342
    def __setitem__(self, key, value):
 
 
344
        Correctly set a value.
 
 
346
        Making dictionary values Section instances.
 
 
347
        (We have to special case 'Section' instances - which are also dicts)
 
 
349
        Keys must be strings.
 
 
350
        Values need only be strings (or lists of strings) if
 
 
351
        ``main.stringify`` is set.
 
 
353
        if not isinstance(key, StringTypes):
 
 
354
            raise ValueError, 'The key "%s" is not a string.' % key
 
 
356
        if key not in self.comments:
 
 
357
            self.comments[key] = []
 
 
358
            self.inline_comments[key] = ''
 
 
359
        # remove the entry from defaults
 
 
360
        if key in self.defaults:
 
 
361
            self.defaults.remove(key)
 
 
363
        if isinstance(value, Section):
 
 
365
                self.sections.append(key)
 
 
366
            dict.__setitem__(self, key, value)
 
 
367
        elif isinstance(value, dict):
 
 
368
            # First create the new depth level,
 
 
369
            # then create the section
 
 
371
                self.sections.append(key)
 
 
372
            new_depth = self.depth + 1
 
 
384
                self.scalars.append(key)
 
 
385
            if not self.main.stringify:
 
 
386
                if isinstance(value, StringTypes):
 
 
388
                elif isinstance(value, (list, tuple)):
 
 
390
                        if not isinstance(entry, StringTypes):
 
 
392
                                'Value is not a string "%s".' % entry)
 
 
394
                    raise TypeError, 'Value is not a string "%s".' % value
 
 
395
            dict.__setitem__(self, key, value)
 
 
397
    def __delitem__(self, key):
 
 
398
        """Remove items from the sequence when deleting."""
 
 
399
        dict. __delitem__(self, key)
 
 
400
        if key in self.scalars:
 
 
401
            self.scalars.remove(key)
 
 
403
            self.sections.remove(key)
 
 
404
        del self.comments[key]
 
 
405
        del self.inline_comments[key]
 
 
407
    def get(self, key, default=None):
 
 
408
        """A version of ``get`` that doesn't bypass string interpolation."""
 
 
414
    def update(self, indict):
 
 
416
        A version of update that uses our ``__setitem__``.
 
 
419
            self[entry] = indict[entry]
 
 
422
    def pop(self, key, *args):
 
 
424
        val = dict.pop(self, key, *args)
 
 
425
        if key in self.scalars:
 
 
426
            del self.comments[key]
 
 
427
            del self.inline_comments[key]
 
 
428
            self.scalars.remove(key)
 
 
429
        elif key in self.sections:
 
 
430
            del self.comments[key]
 
 
431
            del self.inline_comments[key]
 
 
432
            self.sections.remove(key)
 
 
433
        if self.main.interpolation and isinstance(val, StringTypes):
 
 
434
            return self._interpolate(val)
 
 
438
        """Pops the first (key,val)"""
 
 
439
        sequence = (self.scalars + self.sections)
 
 
441
            raise KeyError, ": 'popitem(): dictionary is empty'"
 
 
449
        A version of clear that also affects scalars/sections
 
 
450
        Also clears comments and configspec.
 
 
452
        Leaves other attributes alone :
 
 
453
            depth/main/parent are not affected
 
 
459
        self.inline_comments = {}
 
 
462
    def setdefault(self, key, default=None):
 
 
463
        """A version of setdefault that sets sequence if appropriate."""
 
 
472
        return zip((self.scalars + self.sections), self.values())
 
 
476
        return (self.scalars + self.sections)
 
 
480
        return [self[key] for key in (self.scalars + self.sections)]
 
 
484
        return iter(self.items())
 
 
488
        return iter((self.scalars + self.sections))
 
 
492
    def itervalues(self):
 
 
494
        return iter(self.values())
 
 
497
        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
 
 
498
            for key in (self.scalars + self.sections)])
 
 
502
    # Extra methods - not in a normal dictionary
 
 
506
        Return a deepcopy of self as a dictionary.
 
 
508
        All members that are ``Section`` instances are recursively turned to
 
 
509
        ordinary dictionaries - by calling their ``dict`` method.
 
 
519
            this_entry = self[entry]
 
 
520
            if isinstance(this_entry, Section):
 
 
521
                this_entry = this_entry.dict()
 
 
522
            elif isinstance(this_entry, (list, tuple)):
 
 
523
                # create a copy rather than a reference
 
 
524
                this_entry = list(this_entry)
 
 
525
            newdict[entry] = this_entry
 
 
528
    def merge(self, indict):
 
 
530
        A recursive update - useful for merging config files.
 
 
532
        >>> a = '''[section1]
 
 
535
        ...     more_options = False
 
 
536
        ...     # end of file'''.splitlines()
 
 
537
        >>> b = '''# File is user.ini
 
 
540
        ...     # end of file'''.splitlines()
 
 
541
        >>> c1 = ConfigObj(b)
 
 
542
        >>> c2 = ConfigObj(a)
 
 
545
        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
 
 
547
        for key, val in indict.items():
 
 
548
            if (key in self and isinstance(self[key], dict) and
 
 
549
                                isinstance(val, dict)):
 
 
554
    def rename(self, oldkey, newkey):
 
 
556
        Change a keyname to another, without changing position in sequence.
 
 
558
        Implemented so that transformations can be made on keys,
 
 
559
        as well as on values. (used by encode and decode)
 
 
561
        Also renames comments.
 
 
563
        if oldkey in self.scalars:
 
 
564
            the_list = self.scalars
 
 
565
        elif oldkey in self.sections:
 
 
566
            the_list = self.sections
 
 
568
            raise KeyError, 'Key "%s" not found.' % oldkey
 
 
569
        pos = the_list.index(oldkey)
 
 
572
        dict.__delitem__(self, oldkey)
 
 
573
        dict.__setitem__(self, newkey, val)
 
 
574
        the_list.remove(oldkey)
 
 
575
        the_list.insert(pos, newkey)
 
 
576
        comm = self.comments[oldkey]
 
 
577
        inline_comment = self.inline_comments[oldkey]
 
 
578
        del self.comments[oldkey]
 
 
579
        del self.inline_comments[oldkey]
 
 
580
        self.comments[newkey] = comm
 
 
581
        self.inline_comments[newkey] = inline_comment
 
 
583
    def walk(self, function, raise_errors=True,
 
 
584
            call_on_sections=False, **keywargs):
 
 
586
        Walk every member and call a function on the keyword and value.
 
 
588
        Return a dictionary of the return values
 
 
590
        If the function raises an exception, raise the errror
 
 
591
        unless ``raise_errors=False``, in which case set the return value to
 
 
594
        Any unrecognised keyword arguments you pass to walk, will be pased on
 
 
595
        to the function you pass in.
 
 
597
        Note: if ``call_on_sections`` is ``True`` then - on encountering a
 
 
598
        subsection, *first* the function is called for the *whole* subsection,
 
 
599
        and then recurses into it's members. This means your function must be
 
 
600
        able to handle strings, dictionaries and lists. This allows you
 
 
601
        to change the key of subsections as well as for ordinary members. The
 
 
602
        return value when called on the whole subsection has to be discarded.
 
 
604
        See  the encode and decode methods for examples, including functions.
 
 
608
            You can use ``walk`` to transform the names of members of a section
 
 
609
            but you mustn't add or delete members.
 
 
611
        >>> config = '''[XXXXsection]
 
 
612
        ... XXXXkey = XXXXvalue'''.splitlines()
 
 
613
        >>> cfg = ConfigObj(config)
 
 
615
        {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
 
 
616
        >>> def transform(section, key):
 
 
617
        ...     val = section[key]
 
 
618
        ...     newkey = key.replace('XXXX', 'CLIENT1')
 
 
619
        ...     section.rename(key, newkey)
 
 
620
        ...     if isinstance(val, (tuple, list, dict)):
 
 
623
        ...         val = val.replace('XXXX', 'CLIENT1')
 
 
624
        ...         section[newkey] = val
 
 
625
        >>> cfg.walk(transform, call_on_sections=True)
 
 
626
        {'CLIENT1section': {'CLIENT1key': None}}
 
 
628
        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
 
 
632
        for i in range(len(self.scalars)):
 
 
633
            entry = self.scalars[i]
 
 
635
                val = function(self, entry, **keywargs)
 
 
636
                # bound again in case name has changed
 
 
637
                entry = self.scalars[i]
 
 
643
                    entry = self.scalars[i]
 
 
646
        for i in range(len(self.sections)):
 
 
647
            entry = self.sections[i]
 
 
650
                    function(self, entry, **keywargs)
 
 
655
                        entry = self.sections[i]
 
 
657
                # bound again in case name has changed
 
 
658
                entry = self.sections[i]
 
 
659
            # previous result is discarded
 
 
660
            out[entry] = self[entry].walk(
 
 
662
                raise_errors=raise_errors,
 
 
663
                call_on_sections=call_on_sections,
 
 
667
    def decode(self, encoding):
 
 
669
        Decode all strings and values to unicode, using the specified encoding.
 
 
671
        Works with subsections and list values.
 
 
673
        Uses the ``walk`` method.
 
 
675
        Testing ``encode`` and ``decode``.
 
 
677
        >>> m.decode('ascii')
 
 
678
        >>> def testuni(val):
 
 
679
        ...     for entry in val:
 
 
680
        ...         if not isinstance(entry, unicode):
 
 
681
        ...             print >> sys.stderr, type(entry)
 
 
682
        ...             raise AssertionError, 'decode failed.'
 
 
683
        ...         if isinstance(val[entry], dict):
 
 
684
        ...             testuni(val[entry])
 
 
685
        ...         elif not isinstance(val[entry], unicode):
 
 
686
        ...             raise AssertionError, 'decode failed.'
 
 
688
        >>> m.encode('ascii')
 
 
692
        def decode(section, key, encoding=encoding):
 
 
695
            if isinstance(val, (list, tuple)):
 
 
698
                    newval.append(entry.decode(encoding))
 
 
699
            elif isinstance(val, dict):
 
 
702
                newval = val.decode(encoding)
 
 
703
            newkey = key.decode(encoding)
 
 
704
            section.rename(key, newkey)
 
 
705
            section[newkey] = newval
 
 
706
        # using ``call_on_sections`` allows us to modify section names
 
 
707
        self.walk(decode, call_on_sections=True)
 
 
709
    def encode(self, encoding):
 
 
711
        Encode all strings and values from unicode,
 
 
712
        using the specified encoding.
 
 
714
        Works with subsections and list values.
 
 
715
        Uses the ``walk`` method.
 
 
717
        def encode(section, key, encoding=encoding):
 
 
720
            if isinstance(val, (list, tuple)):
 
 
723
                    newval.append(entry.encode(encoding))
 
 
724
            elif isinstance(val, dict):
 
 
727
                newval = val.encode(encoding)
 
 
728
            newkey = key.encode(encoding)
 
 
729
            section.rename(key, newkey)
 
 
730
            section[newkey] = newval
 
 
731
        self.walk(encode, call_on_sections=True)
 
 
733
    def istrue(self, key):
 
 
734
        """A deprecated version of ``as_bool``."""
 
 
735
        warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
 
 
736
                'instead.', DeprecationWarning)
 
 
737
        return self.as_bool(key)
 
 
739
    def as_bool(self, key):
 
 
741
        Accepts a key as input. The corresponding value must be a string or
 
 
742
        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
 
 
743
        retain compatibility with Python 2.2.
 
 
745
        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
 
 
748
        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
 
 
751
        ``as_bool`` is not case sensitive.
 
 
753
        Any other input will raise a ``ValueError``.
 
 
758
        Traceback (most recent call last):
 
 
759
        ValueError: Value "fish" is neither True nor False
 
 
774
                if not isinstance(val, StringTypes):
 
 
777
                    return self.main._bools[val.lower()]
 
 
779
                raise ValueError('Value "%s" is neither True nor False' % val)
 
 
781
    def as_int(self, key):
 
 
783
        A convenience method which coerces the specified value to an integer.
 
 
785
        If the value is an invalid literal for ``int``, a ``ValueError`` will
 
 
791
        Traceback (most recent call last):
 
 
792
        ValueError: invalid literal for int(): fish
 
 
798
        Traceback (most recent call last):
 
 
799
        ValueError: invalid literal for int(): 3.2
 
 
801
        return int(self[key])
 
 
803
    def as_float(self, key):
 
 
805
        A convenience method which coerces the specified value to a float.
 
 
807
        If the value is an invalid literal for ``float``, a ``ValueError`` will
 
 
813
        Traceback (most recent call last):
 
 
814
        ValueError: invalid literal for float(): fish
 
 
822
        return float(self[key])
 
 
825
class ConfigObj(Section):
 
 
827
    An object to read, create, and write config files.
 
 
829
    Testing with duplicate keys and sections.
 
 
839
    >>> ConfigObj(c.split('\\n'), raise_errors = True)
 
 
840
    Traceback (most recent call last):
 
 
841
    DuplicateError: Duplicate section name at line 5.
 
 
849
    ... 'member1' = value
 
 
853
    >>> ConfigObj(d.split('\\n'), raise_errors = True)
 
 
854
    Traceback (most recent call last):
 
 
855
    DuplicateError: Duplicate keyword name at line 6.
 
 
858
    _keyword = re.compile(r'''^ # line start
 
 
861
            (?:".*?")|          # double quotes
 
 
862
            (?:'.*?')|          # single quotes
 
 
863
            (?:[^'"=].*?)       # no quotes
 
 
866
        (.*)                    # value (including list values and comments)
 
 
871
    _sectionmarker = re.compile(r'''^
 
 
872
        (\s*)                     # 1: indentation
 
 
873
        ((?:\[\s*)+)              # 2: section marker open
 
 
874
        (                         # 3: section name open
 
 
875
            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
 
 
876
            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
 
 
877
            (?:[^'"\s].*?)        # at least one non-space unquoted
 
 
878
        )                         # section name close
 
 
879
        ((?:\s*\])+)              # 4: section marker close
 
 
880
        \s*(\#.*)?                # 5: optional comment
 
 
884
    # this regexp pulls list values out as a single string
 
 
885
    # or single values and comments
 
 
886
    _valueexp = re.compile(r'''^
 
 
892
                            (?:".*?")|              # double quotes
 
 
893
                            (?:'.*?')|              # single quotes
 
 
894
                            (?:[^'",\#][^,\#]*?)       # unquoted
 
 
897
                    )*      # match all list items ending in a comma (if any)
 
 
900
                    (?:".*?")|                      # double quotes
 
 
901
                    (?:'.*?')|                      # single quotes
 
 
902
                    (?:[^'",\#\s][^,]*?)             # unquoted
 
 
903
                )?          # last item in a list - or string value
 
 
905
            (,)             # alternatively a single comma - empty list
 
 
907
        \s*(\#.*)?          # optional comment
 
 
911
    # use findall to get the members of a list value
 
 
912
    _listvalueexp = re.compile(r'''
 
 
914
            (?:".*?")|          # double quotes
 
 
915
            (?:'.*?')|          # single quotes
 
 
916
            (?:[^'",\#].*?)       # unquoted
 
 
922
    # this regexp is used for the value
 
 
923
    # when lists are switched off
 
 
924
    _nolistvalue = re.compile(r'''^
 
 
926
            (?:".*?")|          # double quotes
 
 
927
            (?:'.*?')|          # single quotes
 
 
928
            (?:[^'"\#].*?)      # unquoted
 
 
930
        \s*(\#.*)?              # optional comment
 
 
934
    # regexes for finding triple quoted values on one line
 
 
935
    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
 
 
936
    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
 
 
937
    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
 
 
938
    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
 
 
941
        "'''": (_single_line_single, _multi_line_single),
 
 
942
        '"""': (_single_line_double, _multi_line_double),
 
 
945
    # Used by the ``istrue`` Section method
 
 
947
        'yes': True, 'no': False,
 
 
948
        'on': True, 'off': False,
 
 
949
        '1': True, '0': False,
 
 
950
        'true': True, 'false': False,
 
 
953
    def __init__(self, infile=None, options=None, **kwargs):
 
 
955
        Parse or create a config file object.
 
 
957
        ``ConfigObj(infile=None, options=None, **kwargs)``
 
 
963
        # keyword arguments take precedence over an options dictionary
 
 
964
        options.update(kwargs)
 
 
965
        # init the superclass
 
 
966
        Section.__init__(self, self, 0, self)
 
 
968
        defaults = OPTION_DEFAULTS.copy()
 
 
969
        for entry in options.keys():
 
 
970
            if entry not in defaults.keys():
 
 
971
                raise TypeError, 'Unrecognised option "%s".' % entry
 
 
972
        # TODO: check the values too.
 
 
974
        # Add any explicit options to the defaults
 
 
975
        defaults.update(options)
 
 
977
        # initialise a few variables
 
 
980
        self.raise_errors = defaults['raise_errors']
 
 
981
        self.interpolation = defaults['interpolation']
 
 
982
        self.list_values = defaults['list_values']
 
 
983
        self.create_empty = defaults['create_empty']
 
 
984
        self.file_error = defaults['file_error']
 
 
985
        self.stringify = defaults['stringify']
 
 
986
        self.indent_type = defaults['indent_type']
 
 
987
        self.encoding = defaults['encoding']
 
 
988
        self.default_encoding = defaults['default_encoding']
 
 
992
        self.initial_comment = []
 
 
993
        self.final_comment = []
 
 
995
        if isinstance(infile, StringTypes):
 
 
996
            self.filename = infile
 
 
997
            if os.path.isfile(infile):
 
 
998
                infile = open(infile).read() or []
 
 
999
            elif self.file_error:
 
 
1000
                # raise an error if the file doesn't exist
 
 
1001
                raise IOError, 'Config file not found: "%s".' % self.filename
 
 
1003
                # file doesn't already exist
 
 
1004
                if self.create_empty:
 
 
1005
                    # this is a good test that the filename specified
 
 
1006
                    # isn't impossible - like on a non existent device
 
 
1007
                    h = open(infile, 'w')
 
 
1011
        elif isinstance(infile, (list, tuple)):
 
 
1012
            infile = list(infile)
 
 
1013
        elif isinstance(infile, dict):
 
 
1015
            # the Section class handles creating subsections
 
 
1016
            if isinstance(infile, ConfigObj):
 
 
1017
                # get a copy of our ConfigObj
 
 
1018
                infile = infile.dict()
 
 
1019
            for entry in infile:
 
 
1020
                self[entry] = infile[entry]
 
 
1022
            if defaults['configspec'] is not None:
 
 
1023
                self._handle_configspec(defaults['configspec'])
 
 
1025
                self.configspec = None
 
 
1027
        elif getattr(infile, 'read', None) is not None:
 
 
1028
            # This supports file like objects
 
 
1029
            infile = infile.read() or []
 
 
1030
            # needs splitting into lines - but needs doing *after* decoding
 
 
1031
            # in case it's not an 8 bit encoding
 
 
1033
            raise TypeError, ('infile must be a filename,'
 
 
1034
                ' file like object, or list of lines.')
 
 
1037
            # don't do it for the empty ConfigObj
 
 
1038
            infile = self._handle_bom(infile)
 
 
1039
            # infile is now *always* a list
 
 
1041
            # Set the newlines attribute (first line ending it finds)
 
 
1042
            # and strip trailing '\n' or '\r' from lines
 
 
1044
                if (not line) or (line[-1] not in '\r\n'):
 
 
1046
                for end in ('\r\n', '\n', '\r'):
 
 
1047
                    if line.endswith(end):
 
 
1051
            infile = [line.rstrip('\r\n') for line in infile]
 
 
1054
        # if we had any errors, now is the time to raise them
 
 
1056
            error = ConfigObjError("Parsing failed.")
 
 
1057
            # set the errors attribute; it's a list of tuples:
 
 
1058
            # (error_type, message, line_number)
 
 
1059
            error.errors = self._errors
 
 
1060
            # set the config attribute
 
 
1063
        # delete private attributes
 
 
1066
        if defaults['configspec'] is None:
 
 
1067
            self.configspec = None
 
 
1069
            self._handle_configspec(defaults['configspec'])
 
 
1071
    def _handle_bom(self, infile):
 
 
1073
        Handle any BOM, and decode if necessary.
 
 
1075
        If an encoding is specified, that *must* be used - but the BOM should
 
 
1076
        still be removed (and the BOM attribute set).
 
 
1078
        (If the encoding is wrongly specified, then a BOM for an alternative
 
 
1079
        encoding won't be discovered or removed.)
 
 
1081
        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
 
 
1082
        removed. The BOM attribute will be set. UTF16 will be decoded to
 
 
1085
        NOTE: This method must not be called with an empty ``infile``.
 
 
1087
        Specifying the *wrong* encoding is likely to cause a
 
 
1088
        ``UnicodeDecodeError``.
 
 
1090
        ``infile`` must always be returned as a list of lines, but may be
 
 
1091
        passed in as a single string.
 
 
1093
        if ((self.encoding is not None) and
 
 
1094
            (self.encoding.lower() not in BOM_LIST)):
 
 
1095
            # No need to check for a BOM
 
 
1096
            # encoding specified doesn't have one
 
 
1098
            return self._decode(infile, self.encoding)
 
 
1100
        if isinstance(infile, (list, tuple)):
 
 
1104
        if self.encoding is not None:
 
 
1105
            # encoding explicitly supplied
 
 
1106
            # And it could have an associated BOM
 
 
1107
            # TODO: if encoding is just UTF16 - we ought to check for both
 
 
1108
            # TODO: big endian and little endian versions.
 
 
1109
            enc = BOM_LIST[self.encoding.lower()]
 
 
1111
                # For UTF16 we try big endian and little endian
 
 
1112
                for BOM, (encoding, final_encoding) in BOMS.items():
 
 
1113
                    if not final_encoding:
 
 
1116
                    if infile.startswith(BOM):
 
 
1119
                        # Don't need to remove BOM
 
 
1120
                        return self._decode(infile, encoding)
 
 
1122
                # If we get this far, will *probably* raise a DecodeError
 
 
1123
                # As it doesn't appear to start with a BOM
 
 
1124
                return self._decode(infile, self.encoding)
 
 
1128
            if not line.startswith(BOM):
 
 
1129
                return self._decode(infile, self.encoding)
 
 
1131
            newline = line[len(BOM):]
 
 
1134
            if isinstance(infile, (list, tuple)):
 
 
1139
            return self._decode(infile, self.encoding)
 
 
1141
        # No encoding specified - so we need to check for UTF8/UTF16
 
 
1142
        for BOM, (encoding, final_encoding) in BOMS.items():
 
 
1143
            if not line.startswith(BOM):
 
 
1147
                self.encoding = final_encoding
 
 
1148
                if not final_encoding:
 
 
1152
                    newline = line[len(BOM):]
 
 
1153
                    if isinstance(infile, (list, tuple)):
 
 
1157
                    # UTF8 - don't decode
 
 
1158
                    if isinstance(infile, StringTypes):
 
 
1159
                        return infile.splitlines(True)
 
 
1162
                # UTF16 - have to decode
 
 
1163
                return self._decode(infile, encoding)
 
 
1165
        # No BOM discovered and no encoding specified, just return
 
 
1166
        if isinstance(infile, StringTypes):
 
 
1167
            # infile read from a file will be a single string
 
 
1168
            return infile.splitlines(True)
 
 
1172
    def _a_to_u(self, string):
 
 
1173
        """Decode ascii strings to unicode if a self.encoding is specified."""
 
 
1174
        if not self.encoding:
 
 
1177
            return string.decode('ascii')
 
 
1179
    def _decode(self, infile, encoding):
 
 
1181
        Decode infile to unicode. Using the specified encoding.
 
 
1183
        if is a string, it also needs converting to a list.
 
 
1185
        if isinstance(infile, StringTypes):
 
 
1187
            # NOTE: Could raise a ``UnicodeDecodeError``
 
 
1188
            return infile.decode(encoding).splitlines(True)
 
 
1189
        for i, line in enumerate(infile):
 
 
1190
            if not isinstance(line, unicode):
 
 
1191
                # NOTE: The isinstance test here handles mixed lists of unicode/string
 
 
1192
                # NOTE: But the decode will break on any non-string values
 
 
1193
                # NOTE: Or could raise a ``UnicodeDecodeError``
 
 
1194
                infile[i] = line.decode(encoding)
 
 
1197
    def _decode_element(self, line):
 
 
1198
        """Decode element to unicode if necessary."""
 
 
1199
        if not self.encoding:
 
 
1201
        if isinstance(line, str) and self.default_encoding:
 
 
1202
            return line.decode(self.default_encoding)
 
 
1205
    def _str(self, value):
 
 
1207
        Used by ``stringify`` within validate, to turn non-string values
 
 
1210
        if not isinstance(value, StringTypes):
 
 
1215
    def _parse(self, infile):
 
 
1217
        Actually parse the config file
 
 
1219
        Testing Interpolation
 
 
1222
        >>> c['DEFAULT'] = {
 
 
1224
        ...     'userdir': 'c:\\\\home',
 
 
1228
        >>> c['section'] = {
 
 
1229
        ...     'a': '%(datadir)s\\\\some path\\\\file.py',
 
 
1230
        ...     'b': '%(userdir)s\\\\some path\\\\file.py',
 
 
1231
        ...     'c': 'Yo %(a)s',
 
 
1232
        ...     'd': '%(not_here)s',
 
 
1235
        >>> c['section']['DEFAULT'] = {
 
 
1236
        ...     'datadir': 'c:\\\\silly_test',
 
 
1237
        ...     'a': 'hello - %(b)s',
 
 
1239
        >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
 
 
1241
        >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
 
 
1243
        >>> c['section']['c'] == 'Yo hello - goodbye'
 
 
1246
        Switching Interpolation Off
 
 
1248
        >>> c.interpolation = False
 
 
1249
        >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
 
 
1251
        >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
 
 
1253
        >>> c['section']['c'] == 'Yo %(a)s'
 
 
1256
        Testing the interpolation errors.
 
 
1258
        >>> c.interpolation = True
 
 
1259
        >>> c['section']['d']
 
 
1260
        Traceback (most recent call last):
 
 
1261
        MissingInterpolationOption: missing option "not_here" in interpolation.
 
 
1262
        >>> c['section']['e']
 
 
1263
        Traceback (most recent call last):
 
 
1264
        InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
 
 
1266
        Testing our quoting.
 
 
1268
        >>> i._quote('\"""\'\'\'')
 
 
1269
        Traceback (most recent call last):
 
 
1270
        SyntaxError: EOF while scanning triple-quoted string
 
 
1272
        ...     i._quote('\\n', multiline=False)
 
 
1273
        ... except ConfigObjError, e:
 
 
1275
        'Value "\\n" cannot be safely quoted.'
 
 
1276
        >>> k._quote(' "\' ', multiline=False)
 
 
1277
        Traceback (most recent call last):
 
 
1278
        SyntaxError: EOL while scanning single-quoted string
 
 
1280
        Testing with "stringify" off.
 
 
1281
        >>> c.stringify = False
 
 
1283
        Traceback (most recent call last):
 
 
1284
        TypeError: Value is not a string "1".
 
 
1289
        maxline = len(infile) - 1
 
 
1291
        reset_comment = False
 
 
1292
        while cur_index < maxline:
 
 
1296
            line = infile[cur_index]
 
 
1297
            sline = line.strip()
 
 
1298
            # do we have anything on the line ?
 
 
1299
            if not sline or sline.startswith('#'):
 
 
1300
                reset_comment = False
 
 
1301
                comment_list.append(line)
 
 
1304
                # preserve initial comment
 
 
1305
                self.initial_comment = comment_list
 
 
1308
            reset_comment = True
 
 
1309
            # first we check if it's a section marker
 
 
1310
            mat = self._sectionmarker.match(line)
 
 
1311
##            print >> sys.stderr, sline, mat
 
 
1314
                (indent, sect_open, sect_name, sect_close, comment) = (
 
 
1316
                if indent and (self.indent_type is None):
 
 
1317
                    self.indent_type = indent[0]
 
 
1318
                cur_depth = sect_open.count('[')
 
 
1319
                if cur_depth != sect_close.count(']'):
 
 
1321
                        "Cannot compute the section depth at line %s.",
 
 
1322
                        NestingError, infile, cur_index)
 
 
1324
                if cur_depth < this_section.depth:
 
 
1325
                    # the new section is dropping back to a previous level
 
 
1327
                        parent = self._match_depth(
 
 
1332
                            "Cannot compute nesting level at line %s.",
 
 
1333
                            NestingError, infile, cur_index)
 
 
1335
                elif cur_depth == this_section.depth:
 
 
1336
                    # the new section is a sibling of the current section
 
 
1337
                    parent = this_section.parent
 
 
1338
                elif cur_depth == this_section.depth + 1:
 
 
1339
                    # the new section is a child the current section
 
 
1340
                    parent = this_section
 
 
1343
                        "Section too nested at line %s.",
 
 
1344
                        NestingError, infile, cur_index)
 
 
1346
                sect_name = self._unquote(sect_name)
 
 
1347
                if sect_name in parent:
 
 
1348
##                    print >> sys.stderr, sect_name
 
 
1350
                        'Duplicate section name at line %s.',
 
 
1351
                        DuplicateError, infile, cur_index)
 
 
1353
                # create the new section
 
 
1354
                this_section = Section(
 
 
1359
                parent[sect_name] = this_section
 
 
1360
                parent.inline_comments[sect_name] = comment
 
 
1361
                parent.comments[sect_name] = comment_list
 
 
1362
##                print >> sys.stderr, parent[sect_name] is this_section
 
 
1365
            # it's not a section marker,
 
 
1366
            # so it should be a valid ``key = value`` line
 
 
1367
            mat = self._keyword.match(line)
 
 
1368
##            print >> sys.stderr, sline, mat
 
 
1370
                # is a keyword value
 
 
1371
                # value will include any inline comment
 
 
1372
                (indent, key, value) = mat.groups()
 
 
1373
                if indent and (self.indent_type is None):
 
 
1374
                    self.indent_type = indent[0]
 
 
1375
                # check for a multiline value
 
 
1376
                if value[:3] in ['"""', "'''"]:
 
 
1378
                        (value, comment, cur_index) = self._multiline(
 
 
1379
                            value, infile, cur_index, maxline)
 
 
1382
                            'Parse error in value at line %s.',
 
 
1383
                            ParseError, infile, cur_index)
 
 
1386
                    # extract comment and lists
 
 
1388
                        (value, comment) = self._handle_value(value)
 
 
1391
                            'Parse error in value at line %s.',
 
 
1392
                            ParseError, infile, cur_index)
 
 
1395
##                print >> sys.stderr, sline
 
 
1396
                key = self._unquote(key)
 
 
1397
                if key in this_section:
 
 
1399
                        'Duplicate keyword name at line %s.',
 
 
1400
                        DuplicateError, infile, cur_index)
 
 
1403
##                print >> sys.stderr, this_section.name
 
 
1404
                this_section[key] = value
 
 
1405
                this_section.inline_comments[key] = comment
 
 
1406
                this_section.comments[key] = comment_list
 
 
1407
##                print >> sys.stderr, key, this_section[key]
 
 
1408
##                if this_section.name is not None:
 
 
1409
##                    print >> sys.stderr, this_section
 
 
1410
##                    print >> sys.stderr, this_section.parent
 
 
1411
##                    print >> sys.stderr, this_section.parent[this_section.name]
 
 
1414
            # it neither matched as a keyword
 
 
1415
            # or a section marker
 
 
1417
                'Invalid line at line "%s".',
 
 
1418
                ParseError, infile, cur_index)
 
 
1419
        if self.indent_type is None:
 
 
1420
            # no indentation used, set the type accordingly
 
 
1421
            self.indent_type = ''
 
 
1422
        # preserve the final comment
 
 
1423
        if not self and not self.initial_comment:
 
 
1424
            self.initial_comment = comment_list
 
 
1426
            self.final_comment = comment_list
 
 
1428
    def _match_depth(self, sect, depth):
 
 
1430
        Given a section and a depth level, walk back through the sections
 
 
1431
        parents to see if the depth level matches a previous section.
 
 
1433
        Return a reference to the right section,
 
 
1434
        or raise a SyntaxError.
 
 
1436
        while depth < sect.depth:
 
 
1437
            if sect is sect.parent:
 
 
1438
                # we've reached the top level already
 
 
1441
        if sect.depth == depth:
 
 
1443
        # shouldn't get here
 
 
1446
    def _handle_error(self, text, ErrorClass, infile, cur_index):
 
 
1448
        Handle an error according to the error settings.
 
 
1450
        Either raise the error or store it.
 
 
1451
        The error will have occured at ``cur_index``
 
 
1453
        line = infile[cur_index]
 
 
1454
        message = text % cur_index
 
 
1455
        error = ErrorClass(message, cur_index, line)
 
 
1456
        if self.raise_errors:
 
 
1457
            # raise the error - parsing stops here
 
 
1460
        # reraise when parsing has finished
 
 
1461
        self._errors.append(error)
 
 
1463
    def _unquote(self, value):
 
 
1464
        """Return an unquoted version of a value"""
 
 
1465
        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
 
 
1469
    def _quote(self, value, multiline=True):
 
 
1471
        Return a safely quoted version of a value.
 
 
1473
        Raise a ConfigObjError if the value cannot be safely quoted.
 
 
1474
        If multiline is ``True`` (default) then use triple quotes
 
 
1477
        Don't quote values that don't need it.
 
 
1478
        Recursively quote members of a list and return a comma joined list.
 
 
1479
        Multiline is ``False`` for lists.
 
 
1480
        Obey list syntax for empty and single member lists.
 
 
1482
        If ``list_values=False`` then the value is only quoted if it contains
 
 
1483
        a ``\n`` (is multiline).
 
 
1485
        if isinstance(value, (list, tuple)):
 
 
1488
            elif len(value) == 1:
 
 
1489
                return self._quote(value[0], multiline=False) + ','
 
 
1490
            return ', '.join([self._quote(val, multiline=False)
 
 
1492
        if not isinstance(value, StringTypes):
 
 
1496
                raise TypeError, 'Value "%s" is not a string.' % value
 
 
1500
        wspace_plus = ' \r\t\n\v\t\'"'
 
 
1505
        if (not self.list_values and '\n' not in value) or not (multiline and
 
 
1506
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
 
 
1507
            if not self.list_values:
 
 
1508
                # we don't quote if ``list_values=False``
 
 
1510
            # for normal values either single or double quotes will do
 
 
1512
                # will only happen if multiline is off - e.g. '\n' in key
 
 
1513
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
 
 
1515
            elif ((value[0] not in wspace_plus) and
 
 
1516
                    (value[-1] not in wspace_plus) and
 
 
1517
                    (',' not in value)):
 
 
1520
                if ("'" in value) and ('"' in value):
 
 
1521
                    raise ConfigObjError, (
 
 
1522
                        'Value "%s" cannot be safely quoted.' % value)
 
 
1528
            # if value has '\n' or "'" *and* '"', it will need triple quotes
 
 
1529
            if (value.find('"""') != -1) and (value.find("'''") != -1):
 
 
1530
                raise ConfigObjError, (
 
 
1531
                    'Value "%s" cannot be safely quoted.' % value)
 
 
1532
            if value.find('"""') == -1:
 
 
1538
    def _handle_value(self, value):
 
 
1540
        Given a value string, unquote, remove comment,
 
 
1541
        handle lists. (including empty and single member lists)
 
 
1543
        Testing list values.
 
 
1545
        >>> testconfig3 = '''
 
 
1548
        ... c = test1, test2   , test3
 
 
1549
        ... d = test1, test2, test3,
 
 
1551
        >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
 
 
1554
        >>> d['b'] == ['test']
 
 
1556
        >>> d['c'] == ['test1', 'test2', 'test3']
 
 
1558
        >>> d['d'] == ['test1', 'test2', 'test3']
 
 
1561
        Testing with list values off.
 
 
1564
        ...     testconfig3.split('\\n'),
 
 
1565
        ...     raise_errors=True,
 
 
1566
        ...     list_values=False)
 
 
1569
        >>> e['b'] == 'test,'
 
 
1571
        >>> e['c'] == 'test1, test2   , test3'
 
 
1573
        >>> e['d'] == 'test1, test2, test3,'
 
 
1576
        Testing creating from a dictionary.
 
 
1599
        >>> g = ConfigObj(f)
 
 
1603
        Testing we correctly detect badly built list values (4 of them).
 
 
1605
        >>> testconfig4 = '''
 
 
1609
        ... dummy = ,,hello, goodbye
 
 
1612
        ...     ConfigObj(testconfig4.split('\\n'))
 
 
1613
        ... except ConfigObjError, e:
 
 
1617
        Testing we correctly detect badly quoted values (4 of them).
 
 
1619
        >>> testconfig5 = '''
 
 
1620
        ... config = "hello   # comment
 
 
1622
        ... fish = 'goodbye   # comment
 
 
1623
        ... dummy = "hello again
 
 
1626
        ...     ConfigObj(testconfig5.split('\\n'))
 
 
1627
        ... except ConfigObjError, e:
 
 
1631
        # do we look for lists in values ?
 
 
1632
        if not self.list_values:
 
 
1633
            mat = self._nolistvalue.match(value)
 
 
1636
            (value, comment) = mat.groups()
 
 
1637
            # NOTE: we don't unquote here
 
 
1638
            return (value, comment)
 
 
1639
        mat = self._valueexp.match(value)
 
 
1641
            # the value is badly constructed, probably badly quoted,
 
 
1642
            # or an invalid list
 
 
1644
        (list_values, single, empty_list, comment) = mat.groups()
 
 
1645
        if (list_values == '') and (single is None):
 
 
1646
            # change this if you want to accept empty values
 
 
1648
        # NOTE: note there is no error handling from here if the regex
 
 
1649
        # is wrong: then incorrect values will slip through
 
 
1650
        if empty_list is not None:
 
 
1651
            # the single comma - meaning an empty list
 
 
1652
            return ([], comment)
 
 
1653
        if single is not None:
 
 
1654
            single = self._unquote(single)
 
 
1655
        if list_values == '':
 
 
1657
            return (single, comment)
 
 
1658
        the_list = self._listvalueexp.findall(list_values)
 
 
1659
        the_list = [self._unquote(val) for val in the_list]
 
 
1660
        if single is not None:
 
 
1661
            the_list += [single]
 
 
1662
        return (the_list, comment)
 
 
1664
    def _multiline(self, value, infile, cur_index, maxline):
 
 
1666
        Extract the value, where we are in a multiline situation
 
 
1668
        Testing multiline values.
 
 
1671
        ...     'name4': ' another single line value ',
 
 
1672
        ...     'multi section': {
 
 
1673
        ...         'name4': '\\n        Well, this is a\\n        multiline '
 
 
1675
        ...         'name2': '\\n        Well, this is a\\n        multiline '
 
 
1677
        ...         'name3': '\\n        Well, this is a\\n        multiline '
 
 
1679
        ...         'name1': '\\n        Well, this is a\\n        multiline '
 
 
1682
        ...     'name2': ' another single line value ',
 
 
1683
        ...     'name3': ' a single line value ',
 
 
1684
        ...     'name1': ' a single line value ',
 
 
1689
        newvalue = value[3:]
 
 
1690
        single_line = self._triple_quote[quot][0]
 
 
1691
        multi_line = self._triple_quote[quot][1]
 
 
1692
        mat = single_line.match(value)
 
 
1694
            retval = list(mat.groups())
 
 
1695
            retval.append(cur_index)
 
 
1697
        elif newvalue.find(quot) != -1:
 
 
1698
            # somehow the triple quote is missing
 
 
1701
        while cur_index < maxline:
 
 
1704
            line = infile[cur_index]
 
 
1705
            if line.find(quot) == -1:
 
 
1708
                # end of multiline, process it
 
 
1711
            # we've got to the end of the config, oops...
 
 
1713
        mat = multi_line.match(line)
 
 
1715
            # a badly formed line
 
 
1717
        (value, comment) = mat.groups()
 
 
1718
        return (newvalue + value, comment, cur_index)
 
 
1720
    def _handle_configspec(self, configspec):
 
 
1721
        """Parse the configspec."""
 
 
1723
            configspec = ConfigObj(
 
 
1728
        except ConfigObjError, e:
 
 
1729
            # FIXME: Should these errors have a reference
 
 
1730
            # to the already parsed ConfigObj ?
 
 
1731
            raise ConfigspecError('Parsing configspec failed: %s' % e)
 
 
1733
            raise IOError('Reading configspec failed: %s' % e)
 
 
1734
        self._set_configspec_value(configspec, self)
 
 
1736
    def _set_configspec_value(self, configspec, section):
 
 
1737
        """Used to recursively set configspec values."""
 
 
1738
        if '__many__' in configspec.sections:
 
 
1739
            section.configspec['__many__'] = configspec['__many__']
 
 
1740
            if len(configspec.sections) > 1:
 
 
1741
                # FIXME: can we supply any useful information here ?
 
 
1742
                raise RepeatSectionError
 
 
1743
        for entry in configspec.scalars:
 
 
1744
            section.configspec[entry] = configspec[entry]
 
 
1745
        for entry in configspec.sections:
 
 
1746
            if entry == '__many__':
 
 
1748
            if entry not in section:
 
 
1750
            self._set_configspec_value(configspec[entry], section[entry])
 
 
1752
    def _handle_repeat(self, section, configspec):
 
 
1753
        """Dynamically assign configspec for repeated section."""
 
 
1755
            section_keys = configspec.sections
 
 
1756
            scalar_keys = configspec.scalars
 
 
1757
        except AttributeError:
 
 
1758
            section_keys = [entry for entry in configspec 
 
 
1759
                                if isinstance(configspec[entry], dict)]
 
 
1760
            scalar_keys = [entry for entry in configspec 
 
 
1761
                                if not isinstance(configspec[entry], dict)]
 
 
1762
        if '__many__' in section_keys and len(section_keys) > 1:
 
 
1763
            # FIXME: can we supply any useful information here ?
 
 
1764
            raise RepeatSectionError
 
 
1767
        for entry in scalar_keys:
 
 
1768
            val = configspec[entry]
 
 
1769
            scalars[entry] = val
 
 
1770
        for entry in section_keys:
 
 
1771
            val = configspec[entry]
 
 
1772
            if entry == '__many__':
 
 
1773
                scalars[entry] = val
 
 
1775
            sections[entry] = val
 
 
1777
        section.configspec = scalars
 
 
1778
        for entry in sections:
 
 
1779
            if entry not in section:
 
 
1781
            self._handle_repeat(section[entry], sections[entry])
 
 
1783
    def _write_line(self, indent_string, entry, this_entry, comment):
 
 
1784
        """Write an individual line, for the write method"""
 
 
1785
        # NOTE: the calls to self._quote here handles non-StringType values.
 
 
1786
        return '%s%s%s%s%s' % (
 
 
1788
            self._decode_element(self._quote(entry, multiline=False)),
 
 
1789
            self._a_to_u(' = '),
 
 
1790
            self._decode_element(self._quote(this_entry)),
 
 
1791
            self._decode_element(comment))
 
 
1793
    def _write_marker(self, indent_string, depth, entry, comment):
 
 
1794
        """Write a section marker line"""
 
 
1795
        return '%s%s%s%s%s' % (
 
 
1797
            self._a_to_u('[' * depth),
 
 
1798
            self._quote(self._decode_element(entry), multiline=False),
 
 
1799
            self._a_to_u(']' * depth),
 
 
1800
            self._decode_element(comment))
 
 
1802
    def _handle_comment(self, comment):
 
 
1804
        Deal with a comment.
 
 
1806
        >>> filename = a.filename
 
 
1807
        >>> a.filename = None
 
 
1808
        >>> values = a.write()
 
 
1810
        >>> while index < 23:
 
 
1812
        ...     line = values[index-1]
 
 
1813
        ...     assert line.endswith('# comment ' + str(index))
 
 
1814
        >>> a.filename = filename
 
 
1816
        >>> start_comment = ['# Initial Comment', '', '#']
 
 
1817
        >>> end_comment = ['', '#', '# Final Comment']
 
 
1818
        >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
 
 
1819
        >>> nc = ConfigObj(newconfig)
 
 
1820
        >>> nc.initial_comment
 
 
1821
        ['# Initial Comment', '', '#']
 
 
1822
        >>> nc.final_comment
 
 
1823
        ['', '#', '# Final Comment']
 
 
1824
        >>> nc.initial_comment == start_comment
 
 
1826
        >>> nc.final_comment == end_comment
 
 
1831
        if self.indent_type == '\t':
 
 
1832
            start = self._a_to_u('\t')
 
 
1834
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
 
 
1835
        if not comment.startswith('#'):
 
 
1836
            start += _a_to_u('# ')
 
 
1837
        return (start + comment)
 
 
1839
    def _compute_indent_string(self, depth):
 
 
1841
        Compute the indent string, according to current indent_type and depth
 
 
1843
        if self.indent_type == '':
 
 
1844
            # no indentation at all
 
 
1846
        if self.indent_type == '\t':
 
 
1848
        if self.indent_type == ' ':
 
 
1849
            return ' ' * NUM_INDENT_SPACES * depth
 
 
1854
    def write(self, outfile=None, section=None):
 
 
1856
        Write the current ConfigObj as a file
 
 
1858
        tekNico: FIXME: use StringIO instead of real files
 
 
1860
        >>> filename = a.filename
 
 
1861
        >>> a.filename = 'test.ini'
 
 
1863
        >>> a.filename = filename
 
 
1864
        >>> a == ConfigObj('test.ini', raise_errors=True)
 
 
1866
        >>> os.remove('test.ini')
 
 
1867
        >>> b.filename = 'test.ini'
 
 
1869
        >>> b == ConfigObj('test.ini', raise_errors=True)
 
 
1871
        >>> os.remove('test.ini')
 
 
1872
        >>> i.filename = 'test.ini'
 
 
1874
        >>> i == ConfigObj('test.ini', raise_errors=True)
 
 
1876
        >>> os.remove('test.ini')
 
 
1878
        >>> a['DEFAULT'] = {'a' : 'fish'}
 
 
1879
        >>> a['a'] = '%(a)s'
 
 
1881
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
 
 
1883
        if self.indent_type is None:
 
 
1884
            # this can be true if initialised from a dictionary
 
 
1885
            self.indent_type = DEFAULT_INDENT_TYPE
 
 
1888
        cs = self._a_to_u('#')
 
 
1889
        csp = self._a_to_u('# ')
 
 
1891
            int_val = self.interpolation
 
 
1892
            self.interpolation = False
 
 
1894
            for line in self.initial_comment:
 
 
1895
                line = self._decode_element(line)
 
 
1896
                stripped_line = line.strip()
 
 
1897
                if stripped_line and not stripped_line.startswith(cs):
 
 
1901
        indent_string = self._a_to_u(
 
 
1902
            self._compute_indent_string(section.depth))
 
 
1903
        for entry in (section.scalars + section.sections):
 
 
1904
            if entry in section.defaults:
 
 
1905
                # don't write out default values
 
 
1907
            for comment_line in section.comments[entry]:
 
 
1908
                comment_line = self._decode_element(comment_line.lstrip())
 
 
1909
                if comment_line and not comment_line.startswith(cs):
 
 
1910
                    comment_line = csp + comment_line
 
 
1911
                out.append(indent_string + comment_line)
 
 
1912
            this_entry = section[entry]
 
 
1913
            comment = self._handle_comment(section.inline_comments[entry])
 
 
1915
            if isinstance(this_entry, dict):
 
 
1917
                out.append(self._write_marker(
 
 
1922
                out.extend(self.write(section=this_entry))
 
 
1924
                out.append(self._write_line(
 
 
1931
            for line in self.final_comment:
 
 
1932
                line = self._decode_element(line)
 
 
1933
                stripped_line = line.strip()
 
 
1934
                if stripped_line and not stripped_line.startswith(cs):
 
 
1937
            self.interpolation = int_val
 
 
1939
        if section is not self:
 
 
1942
        if (self.filename is None) and (outfile is None):
 
 
1943
            # output a list of lines
 
 
1944
            # might need to encode
 
 
1945
            # NOTE: This will *screw* UTF16, each line will start with the BOM
 
 
1947
                out = [l.encode(self.encoding) for l in out]
 
 
1948
            if (self.BOM and ((self.encoding is None) or
 
 
1949
                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
 
1953
                out[0] = BOM_UTF8 + out[0]
 
 
1956
        # Turn the list to a string, joined with correct newlines
 
 
1957
        output = (self._a_to_u(self.newlines or os.linesep)
 
 
1960
            output = output.encode(self.encoding)
 
 
1961
        if (self.BOM and ((self.encoding is None) or
 
 
1962
            (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
 
1964
            output = BOM_UTF8 + output
 
 
1965
        if outfile is not None:
 
 
1966
            outfile.write(output)
 
 
1968
            h = open(self.filename, 'w')
 
 
1972
    def validate(self, validator, preserve_errors=False, section=None):
 
 
1974
        Test the ConfigObj against a configspec.
 
 
1976
        It uses the ``validator`` object from *validate.py*.
 
 
1978
        To run ``validate`` on the current ConfigObj, call: ::
 
 
1980
            test = config.validate(validator)
 
 
1982
        (Normally having previously passed in the configspec when the ConfigObj
 
 
1983
        was created - you can dynamically assign a dictionary of checks to the
 
 
1984
        ``configspec`` attribute of a section though).
 
 
1986
        It returns ``True`` if everything passes, or a dictionary of
 
 
1987
        pass/fails (True/False). If every member of a subsection passes, it
 
 
1988
        will just have the value ``True``. (It also returns ``False`` if all
 
 
1991
        In addition, it converts the values from strings to their native
 
 
1992
        types if their checks pass (and ``stringify`` is set).
 
 
1994
        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
 
 
1995
        of a marking a fail with a ``False``, it will preserve the actual
 
 
1996
        exception object. This can contain info about the reason for failure.
 
 
1997
        For example the ``VdtValueTooSmallError`` indeicates that the value
 
 
1998
        supplied was too small. If a value (or section) is missing it will
 
 
1999
        still be marked as ``False``.
 
 
2001
        You must have the validate module to use ``preserve_errors=True``.
 
 
2003
        You can then use the ``flatten_errors`` function to turn your nested
 
 
2004
        results dictionary into a flattened list of failures - useful for
 
 
2005
        displaying meaningful error messages.
 
 
2008
        ...     from validate import Validator
 
 
2009
        ... except ImportError:
 
 
2010
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
 
 
2027
        ... '''.split('\\n')
 
 
2028
        ...     configspec = '''
 
 
2029
        ...     test1= integer(30,50)
 
 
2032
        ...     test4=float(6.0)
 
 
2034
        ...         test1=integer(30,50)
 
 
2037
        ...         test4=float(6.0)
 
 
2039
        ...             test1=integer(30,50)
 
 
2042
        ...             test4=float(6.0)
 
 
2043
        ...     '''.split('\\n')
 
 
2044
        ...     val = Validator()
 
 
2045
        ...     c1 = ConfigObj(config, configspec=configspec)
 
 
2046
        ...     test = c1.validate(val)
 
 
2057
        ...             'sub section': {
 
 
2066
        >>> val.check(c1.configspec['test4'], c1['test4'])
 
 
2067
        Traceback (most recent call last):
 
 
2068
        VdtValueTooSmallError: the value "5.0" is too small.
 
 
2070
        >>> val_test_config = '''
 
 
2075
        ...     key2 = 1.1, 3.0, 17, 6.8
 
 
2078
        ...         key2 = True'''.split('\\n')
 
 
2079
        >>> val_test_configspec = '''
 
 
2084
        ...     key2 = float_list(4)
 
 
2086
        ...        key = option(option1, option2)
 
 
2087
        ...        key2 = boolean'''.split('\\n')
 
 
2088
        >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
 
 
2089
        >>> val_test.validate(val)
 
 
2091
        >>> val_test['key'] = 'text not a digit'
 
 
2092
        >>> val_res = val_test.validate(val)
 
 
2093
        >>> val_res == {'key2': True, 'section': True, 'key': False}
 
 
2095
        >>> configspec = '''
 
 
2096
        ...     test1=integer(30,50, default=40)
 
 
2097
        ...     test2=string(default="hello")
 
 
2098
        ...     test3=integer(default=3)
 
 
2099
        ...     test4=float(6.0, default=6.0)
 
 
2101
        ...         test1=integer(30,50, default=40)
 
 
2102
        ...         test2=string(default="hello")
 
 
2103
        ...         test3=integer(default=3)
 
 
2104
        ...         test4=float(6.0, default=6.0)
 
 
2106
        ...             test1=integer(30,50, default=40)
 
 
2107
        ...             test2=string(default="hello")
 
 
2108
        ...             test3=integer(default=3)
 
 
2109
        ...             test4=float(6.0, default=6.0)
 
 
2110
        ...     '''.split('\\n')
 
 
2111
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
 
 
2113
        {'test1': '30', 'section': {'sub section': {}}}
 
 
2114
        >>> default_test.validate(val)
 
 
2116
        >>> default_test == {
 
 
2118
        ...     'test2': 'hello',
 
 
2123
        ...         'test2': 'hello',
 
 
2126
        ...         'sub section': {
 
 
2129
        ...             'test2': 'hello',
 
 
2136
        Now testing with repeated sections : BIG TEST
 
 
2138
        >>> repeated_1 = '''
 
 
2140
        ...     [[__many__]] # spec for a dog
 
 
2141
        ...         fleas = boolean(default=True)
 
 
2142
        ...         tail = option(long, short, default=long)
 
 
2143
        ...         name = string(default=rover)
 
 
2144
        ...         [[[__many__]]]  # spec for a puppy
 
 
2145
        ...             name = string(default="son of rover")
 
 
2146
        ...             age = float(default=0.0)
 
 
2148
        ...     [[__many__]] # spec for a cat
 
 
2149
        ...         fleas = boolean(default=True)
 
 
2150
        ...         tail = option(long, short, default=short)
 
 
2151
        ...         name = string(default=pussy)
 
 
2152
        ...         [[[__many__]]] # spec for a kitten
 
 
2153
        ...             name = string(default="son of pussy")
 
 
2154
        ...             age = float(default=0.0)
 
 
2155
        ...         '''.split('\\n')
 
 
2156
        >>> repeated_2 = '''
 
 
2159
        ...     # blank dogs with puppies
 
 
2160
        ...     # should be filled in by the configspec
 
 
2175
        ...     # blank cats with kittens
 
 
2176
        ...     # should be filled in by the configspec
 
 
2189
        ... '''.split('\\n')
 
 
2190
        >>> repeated_3 = '''
 
 
2201
        ... '''.split('\\n')
 
 
2202
        >>> repeated_4 = '''
 
 
2205
        ...     name = string(default=Michael)
 
 
2206
        ...     age = float(default=0.0)
 
 
2207
        ...     sex = option(m, f, default=m)
 
 
2208
        ... '''.split('\\n')
 
 
2209
        >>> repeated_5 = '''
 
 
2212
        ...     fleas = boolean(default=True)
 
 
2213
        ...     tail = option(long, short, default=short)
 
 
2214
        ...     name = string(default=pussy)
 
 
2215
        ...     [[[description]]]
 
 
2216
        ...         height = float(default=3.3)
 
 
2217
        ...         weight = float(default=6)
 
 
2219
        ...             fur = option(black, grey, brown, "tortoise shell", default=black)
 
 
2220
        ...             condition = integer(0,10, default=5)
 
 
2221
        ... '''.split('\\n')
 
 
2222
        >>> from validate import Validator
 
 
2223
        >>> val= Validator()
 
 
2224
        >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
 
 
2225
        >>> repeater.validate(val)
 
 
2232
        ...             'name': 'rover',
 
 
2233
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
 
2234
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
 
2235
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
 
2240
        ...             'name': 'rover',
 
 
2241
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
 
2242
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
 
2243
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
 
2248
        ...             'name': 'rover',
 
 
2249
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
 
2250
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
 
2251
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
 
2257
        ...             'tail': 'short',
 
 
2258
        ...             'name': 'pussy',
 
 
2259
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
 
2260
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
 
2261
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
 
2265
        ...             'tail': 'short',
 
 
2266
        ...             'name': 'pussy',
 
 
2267
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
 
2268
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
 
2269
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
 
2273
        ...             'tail': 'short',
 
 
2274
        ...             'name': 'pussy',
 
 
2275
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
 
2276
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
 
2277
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
 
2282
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
 
 
2283
        >>> repeater.validate(val)
 
 
2287
        ...         'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
 
2288
        ...         'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
 
2289
        ...         'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
 
2292
        ...         'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
 
2293
        ...         'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
 
2294
        ...         'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
 
2298
        >>> repeater = ConfigObj(configspec=repeated_4)
 
 
2299
        >>> repeater['Michael'] = {}
 
 
2300
        >>> repeater.validate(val)
 
 
2303
        ...     'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
 
 
2306
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
 
 
2308
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
 
2309
        ...     'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
 
 
2312
        >>> repeater.validate(val)
 
 
2315
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
 
2319
        ...             'tail': 'short',
 
 
2320
        ...             'name': 'pussy',
 
 
2321
        ...             'description': {
 
 
2323
        ...                 'height': 3.2999999999999998,
 
 
2324
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
 
2329
        ...             'tail': 'short',
 
 
2330
        ...             'name': 'pussy',
 
 
2331
        ...             'description': {
 
 
2333
        ...                 'height': 3.2999999999999998,
 
 
2334
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
 
2339
        ...             'tail': 'short',
 
 
2340
        ...             'name': 'pussy',
 
 
2341
        ...             'description': {
 
 
2343
        ...                 'height': 3.2999999999999998,
 
 
2344
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
 
2351
        Test that interpolation is preserved for validated string values.
 
 
2352
        Also check that interpolation works in configspecs.
 
 
2354
        >>> t['DEFAULT'] = {}
 
 
2355
        >>> t['DEFAULT']['test'] = 'a'
 
 
2356
        >>> t['test'] = '%(test)s'
 
 
2360
        >>> t.configspec = {'test': 'string'}
 
 
2363
        >>> t.interpolation = False
 
 
2365
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 
 
2367
        ...    'interpolated string  = string(default="fuzzy-%(man)s")',
 
 
2371
        >>> c = ConfigObj(configspec=specs)
 
 
2374
        >>> c['interpolated string']
 
 
2377
        FIXME: Above tests will fail if we couldn't import Validator (the ones
 
 
2378
        that don't raise errors will produce different output and still fail as
 
 
2382
            if self.configspec is None:
 
 
2383
                raise ValueError, 'No configspec supplied.'
 
 
2385
                if VdtMissingValue is None:
 
 
2386
                    raise ImportError('Missing validate module.')
 
 
2389
        spec_section = section.configspec
 
 
2390
        if '__many__' in section.configspec:
 
 
2391
            many = spec_section['__many__']
 
 
2392
            # dynamically assign the configspecs
 
 
2393
            # for the sections below
 
 
2394
            for entry in section.sections:
 
 
2395
                self._handle_repeat(section[entry], many)
 
 
2400
        for entry in spec_section:
 
 
2401
            if entry == '__many__':
 
 
2403
            if (not entry in section.scalars) or (entry in section.defaults):
 
 
2405
                # or entries from defaults
 
 
2410
                val = section[entry]
 
 
2412
                check = validator.check(spec_section[entry],
 
 
2416
            except validator.baseErrorClass, e:
 
 
2417
                if not preserve_errors or isinstance(e, VdtMissingValue):
 
 
2420
                    # preserve the error
 
 
2427
                if self.stringify or missing:
 
 
2428
                    # if we are doing type conversion
 
 
2429
                    # or the value is a supplied default
 
 
2430
                    if not self.stringify:
 
 
2431
                        if isinstance(check, (list, tuple)):
 
 
2433
                            check = [self._str(item) for item in check]
 
 
2434
                        elif missing and check is None:
 
 
2435
                            # convert the None from a default to a ''
 
 
2438
                            check = self._str(check)
 
 
2439
                    if (check != val) or missing:
 
 
2440
                        section[entry] = check
 
 
2441
                if missing and entry not in section.defaults:
 
 
2442
                    section.defaults.append(entry)
 
 
2444
        # FIXME: Will this miss missing sections ?
 
 
2445
        for entry in section.sections:
 
 
2446
            if section is self and entry == 'DEFAULT':
 
 
2448
            check = self.validate(validator, preserve_errors=preserve_errors,
 
 
2449
                section=section[entry])
 
 
2466
class SimpleVal(object):
 
 
2469
    Can be used to check that all members expected are present.
 
 
2471
    To use it, provide a configspec with all your members in (the value given
 
 
2472
    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
 
 
2473
    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
 
 
2474
    members are present, or a dictionary with True/False meaning
 
 
2475
    present/missing. (Whole missing sections will be replaced with ``False``)
 
 
2477
    >>> val = SimpleVal()
 
 
2493
    ... '''.split('\\n')
 
 
2494
    >>> configspec = '''
 
 
2509
    ... '''.split('\\n')
 
 
2510
    >>> o = ConfigObj(config, configspec=configspec)
 
 
2513
    >>> o = ConfigObj(configspec=configspec)
 
 
2519
        self.baseErrorClass = ConfigObjError
 
 
2521
    def check(self, check, member, missing=False):
 
 
2522
        """A dummy check method, always returns the value unchanged."""
 
 
2524
            raise self.baseErrorClass
 
 
2527
# Check / processing functions for options
 
 
2528
def flatten_errors(cfg, res, levels=None, results=None):
 
 
2530
    An example function that will turn a nested dictionary of results
 
 
2531
    (as returned by ``ConfigObj.validate``) into a flat list.
 
 
2533
    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
 
 
2534
    dictionary returned by ``validate``.
 
 
2536
    (This is a recursive function, so you shouldn't use the ``levels`` or
 
 
2537
    ``results`` arguments - they are used by the function.
 
 
2539
    Returns a list of keys that failed. Each member of the list is a tuple :
 
 
2542
        ([list of sections...], key, result)
 
 
2544
    If ``validate`` was called with ``preserve_errors=False`` (the default)
 
 
2545
    then ``result`` will always be ``False``.
 
 
2547
    *list of sections* is a flattened list of sections that the key was found
 
 
2550
    If the section was missing then key will be ``None``.
 
 
2552
    If the value (or section) was missing then ``result`` will be ``False``.
 
 
2554
    If ``validate`` was called with ``preserve_errors=True`` and a value
 
 
2555
    was present, but failed the check, then ``result`` will be the exception
 
 
2556
    object returned. You can use this as a string that describes the failure.
 
 
2558
    For example *The value "3" is of the wrong type*.
 
 
2560
    # FIXME: is the ordering of the output arbitrary ?
 
 
2562
    >>> vtor = validate.Validator()
 
 
2568
    ...     another_option = Probably
 
 
2570
    ...     another_option = True
 
 
2577
    ...     option1 = boolean()
 
 
2578
    ...     option2 = boolean()
 
 
2579
    ...     option3 = boolean(default=Bad_value)
 
 
2581
    ...     option1 = boolean()
 
 
2582
    ...     option2 = boolean()
 
 
2583
    ...     option3 = boolean(default=Bad_value)
 
 
2585
    ...     another_option = boolean()
 
 
2587
    ...     another_option = boolean()
 
 
2590
    ...     value2 = integer
 
 
2591
    ...     value3 = integer(0, 10)
 
 
2592
    ...         [[[section3b-sub]]]
 
 
2595
    ...     another_option = boolean()
 
 
2597
    >>> cs = my_cfg.split('\\n')
 
 
2598
    >>> ini = my_ini.split('\\n')
 
 
2599
    >>> cfg = ConfigObj(ini, configspec=cs)
 
 
2600
    >>> res = cfg.validate(vtor, preserve_errors=True)
 
 
2602
    >>> for entry in flatten_errors(cfg, res):
 
 
2603
    ...     section_list, key, error = entry
 
 
2604
    ...     section_list.insert(0, '[root]')
 
 
2605
    ...     if key is not None:
 
 
2606
    ...        section_list.append(key)
 
 
2608
    ...         section_list.append('[missing]')
 
 
2609
    ...     section_string = ', '.join(section_list)
 
 
2610
    ...     errors.append((section_string, ' = ', error))
 
 
2612
    >>> for entry in errors:
 
 
2613
    ...     print entry[0], entry[1], (entry[2] or 0)
 
 
2615
    [root], option3  =  the value "Bad_value" is of the wrong type.
 
 
2616
    [root], section1, option2  =  0
 
 
2617
    [root], section1, option3  =  the value "Bad_value" is of the wrong type.
 
 
2618
    [root], section2, another_option  =  the value "Probably" is of the wrong type.
 
 
2619
    [root], section3, section3b, section3b-sub, [missing]  =  0
 
 
2620
    [root], section3, section3b, value2  =  the value "a" is of the wrong type.
 
 
2621
    [root], section3, section3b, value3  =  the value "11" is too big.
 
 
2622
    [root], section4, [missing]  =  0
 
 
2631
        results.append((levels[:], None, False))
 
 
2635
    for (key, val) in res.items():
 
 
2638
        if isinstance(cfg.get(key), dict):
 
 
2641
            flatten_errors(cfg[key], val, levels, results)
 
 
2643
        results.append((levels[:], key, val))
 
 
2652
# FIXME: test error code for badly built multiline values
 
 
2653
# FIXME: test handling of StringIO
 
 
2654
# FIXME: test interpolation with writing
 
 
2658
    Dummy function to hold some of the doctests.
 
 
2695
    ...         'keys11': 'val1',
 
 
2696
    ...         'keys13': 'val3',
 
 
2697
    ...         'keys12': 'val2',
 
 
2700
    ...         'section 2 sub 1': {
 
 
2703
    ...     'keys21': 'val1',
 
 
2704
    ...     'keys22': 'val2',
 
 
2705
    ...     'keys23': 'val3',
 
 
2710
    ... 'a' = b # !"$%^&*(),::;'@~#= 33
 
 
2711
    ... "b" = b #= 6, 33
 
 
2712
    ... ''' .split('\\n')
 
 
2713
    >>> t2 = ConfigObj(t)
 
 
2714
    >>> assert t2 == {'a': 'b', 'b': 'b'}
 
 
2715
    >>> t2.inline_comments['b'] = ''
 
 
2717
    >>> assert t2.write() == ['','b = b', '']
 
 
2719
    # Test ``list_values=False`` stuff
 
 
2721
    ...     key1 = no quotes
 
 
2722
    ...     key2 = 'single quotes'
 
 
2723
    ...     key3 = "double quotes"
 
 
2724
    ...     key4 = "list", 'with', several, "quotes"
 
 
2726
    >>> cfg = ConfigObj(c.splitlines(), list_values=False)
 
 
2727
    >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'", 
 
 
2728
    ... 'key3': '"double quotes"', 
 
 
2729
    ... 'key4': '"list", \\'with\\', several, "quotes"'
 
 
2732
    >>> cfg = ConfigObj(list_values=False)
 
 
2733
    >>> cfg['key1'] = 'Multiline\\nValue'
 
 
2734
    >>> cfg['key2'] = '''"Value" with 'quotes' !'''
 
 
2736
    ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
 
 
2737
    >>> cfg.list_values = True
 
 
2738
    >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
 
 
2739
    ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
 
 
2742
    Test flatten_errors:
 
 
2744
    >>> from validate import Validator, VdtValueTooSmallError
 
 
2760
    ... '''.split('\\n')
 
 
2761
    >>> configspec = '''
 
 
2762
    ...     test1= integer(30,50)
 
 
2765
    ...     test4=float(6.0)
 
 
2767
    ...         test1=integer(30,50)
 
 
2770
    ...         test4=float(6.0)
 
 
2772
    ...             test1=integer(30,50)
 
 
2775
    ...             test4=float(6.0)
 
 
2776
    ...     '''.split('\\n')
 
 
2777
    >>> val = Validator()
 
 
2778
    >>> c1 = ConfigObj(config, configspec=configspec)
 
 
2779
    >>> res = c1.validate(val)
 
 
2780
    >>> flatten_errors(c1, res) == [([], 'test4', False), (['section', 
 
 
2781
    ...     'sub section'], 'test4', False), (['section'], 'test4', False)]
 
 
2783
    >>> res = c1.validate(val, preserve_errors=True)
 
 
2784
    >>> check = flatten_errors(c1, res)
 
 
2788
    (['section', 'sub section'], 'test4')
 
 
2790
    (['section'], 'test4')
 
 
2791
    >>> for entry in check:
 
 
2792
    ...     isinstance(entry[2], VdtValueTooSmallError)
 
 
2793
    ...     print str(entry[2])
 
 
2795
    the value "5.0" is too small.
 
 
2797
    the value "5.0" is too small.
 
 
2799
    the value "5.0" is too small.
 
 
2801
    Test unicode handling, BOM, write witha file like object and line endings :
 
 
2803
    ... # initial comment
 
 
2804
    ...     # inital comment 2
 
 
2806
    ... test1 = some value
 
 
2808
    ... test2 = another value    # inline comment
 
 
2809
    ... # section comment
 
 
2810
    ... [section]    # inline comment
 
 
2811
    ...     test = test    # another inline comment
 
 
2815
    ... # final comment2
 
 
2817
    >>> u = u_base.encode('utf_8').splitlines(True)
 
 
2818
    >>> u[0] = BOM_UTF8 + u[0]
 
 
2819
    >>> uc = ConfigObj(u)
 
 
2820
    >>> uc.encoding = None
 
 
2823
    >>> uc == {'test1': 'some value', 'test2': 'another value',
 
 
2824
    ... 'section': {'test': 'test', 'test2': 'test2'}}
 
 
2826
    >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
 
 
2829
    >>> isinstance(uc['test1'], unicode)
 
 
2835
    >>> uc['latin1'] = "This costs lot's of "
 
 
2836
    >>> a_list = uc.write()
 
 
2839
    >>> isinstance(a_list[0], str)
 
 
2841
    >>> a_list[0].startswith(BOM_UTF8)
 
 
2843
    >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
 
 
2844
    >>> uc = ConfigObj(u)
 
 
2847
    >>> uc.newlines = '\\r'
 
 
2848
    >>> from cStringIO import StringIO
 
 
2849
    >>> file_like = StringIO()
 
 
2850
    >>> uc.write(file_like)
 
 
2851
    >>> file_like.seek(0)
 
 
2852
    >>> uc2 = ConfigObj(file_like)
 
 
2855
    >>> uc2.filename is None
 
 
2857
    >>> uc2.newlines == '\\r'
 
 
2861
if __name__ == '__main__':
 
 
2862
    # run the code tests in doctest format
 
 
2865
    key1= val    # comment 1
 
 
2866
    key2= val    # comment 2
 
 
2869
    key1= val    # comment 5
 
 
2870
    key2= val    # comment 6
 
 
2873
    key1= val    # comment 9
 
 
2874
    key2= val    # comment 10
 
 
2876
        [[lev2ba]]    # comment 12
 
 
2877
        key1= val    # comment 13
 
 
2879
        [[lev2bb]]    # comment 15
 
 
2880
        key1= val    # comment 16
 
 
2882
    [lev1c]    # comment 18
 
 
2884
        [[lev2c]]    # comment 20
 
 
2886
            [[[lev3c]]]    # comment 22
 
 
2887
            key1 = val    # comment 23"""
 
 
2893
                        ["section 1"] # comment
 
 
2902
                            [['section 2 sub 1']]
 
 
2907
    name1 = """ a single line value """ # comment
 
 
2908
    name2 = \''' another single line value \''' # comment
 
 
2909
    name3 = """ a single line value """
 
 
2910
    name4 = \''' another single line value \'''
 
 
2927
        \'''  # I guess this is a comment too
 
 
2931
    m = sys.modules.get('__main__')
 
 
2932
    globs = m.__dict__.copy()
 
 
2933
    a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
 
 
2934
    b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
 
 
2935
    i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
 
 
2937
        'INTP_VER': INTP_VER,
 
 
2942
    doctest.testmod(m, globs=globs)
 
 
2953
    Better support for configuration from multiple files, including tracking
 
 
2954
    *where* the original file came from and writing changes to the correct
 
 
2958
    Make ``newline`` an option (as well as an attribute) ?
 
 
2960
    ``UTF16`` encoded files, when returned as a list of lines, will have the
 
 
2961
    BOM at the start of every line. Should this be removed from all but the
 
 
2964
    Option to set warning type for unicode decode ? (Defaults to strict).
 
 
2966
    A method to optionally remove uniform indentation from multiline values.
 
 
2967
    (do as an example of using ``walk`` - along with string-escape)
 
 
2969
    Should the results dictionary from validate be an ordered dictionary if
 
 
2970
    `odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
 
 
2972
    Implement a better ``__repr__`` ? (``ConfigObj({})``)
 
 
2974
    Implement some of the sequence methods (which include slicing) from the
 
 
2977
    INCOMPATIBLE CHANGES
 
 
2978
    ====================
 
 
2980
    (I have removed a lot of needless complications - this list is probably not
 
 
2981
    conclusive, many option/attribute/method names have changed)
 
 
2985
    The only valid divider is '='
 
 
2987
    We've removed line continuations with '\'
 
 
2989
    No recursive lists in values
 
 
2993
    No distinction between flatfiles and non flatfiles
 
 
2995
    Change in list syntax - use commas to indicate list, not parentheses
 
 
2996
    (square brackets and parentheses are no longer recognised as lists)
 
 
2998
    ';' is no longer valid for comments and no multiline comments
 
 
3002
    We don't allow empty values - have to use '' or ""
 
 
3004
    In ConfigObj 3 - setting a non-flatfile member to ``None`` would
 
 
3005
    initialise it as an empty section.
 
 
3007
    The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
 
 
3008
    replaced by triple quote, multiple line values.
 
 
3010
    The ``newline``, ``force_return``, and ``default`` options have gone
 
 
3012
    The ``encoding`` and ``backup_encoding`` methods have gone - replaced
 
 
3013
    with the ``encode`` and ``decode`` methods.
 
 
3015
    ``fileerror`` and ``createempty`` options have become ``file_error`` and
 
 
3018
    Partial configspecs (for specifying the order members should be written
 
 
3019
    out and which should be present) have gone. The configspec is no longer
 
 
3020
    used to specify order for the ``write`` method.
 
 
3022
    Exceeding the maximum depth of recursion in string interpolation now
 
 
3023
    raises an error ``InterpolationDepthError``.
 
 
3025
    Specifying a value for interpolation which doesn't exist now raises an
 
 
3026
    error ``MissingInterpolationOption`` (instead of merely being ignored).
 
 
3028
    The ``writein`` method has been removed.
 
 
3030
    The comments attribute is now a list (``inline_comments`` equates to the
 
 
3031
    old comments attribute)
 
 
3036
    ``validate`` doesn't report *extra* values or sections.
 
 
3038
    You can't have a keyword with the same name as a section (in the same
 
 
3039
    section). They are both dictionary keys - so they would overlap.
 
 
3041
    ConfigObj doesn't quote and unquote values if ``list_values=False``.
 
 
3042
    This means that leading or trailing whitespace in values will be lost when
 
 
3043
    writing. (Unless you manually quote).
 
 
3045
    Interpolation checks first the 'DEFAULT' subsection of the current
 
 
3046
    section, next it checks the 'DEFAULT' section of the parent section,
 
 
3047
    last it checks the 'DEFAULT' section of the main section.
 
 
3049
    Logically a 'DEFAULT' section should apply to all subsections of the *same
 
 
3050
    parent* - this means that checking the 'DEFAULT' subsection in the
 
 
3051
    *current section* is not necessarily logical ?
 
 
3053
    In order to simplify unicode support (which is possibly of limited value
 
 
3054
    in a config file) I have removed automatic support and added the
 
 
3055
    ``encode`` and ``decode methods, which can be used to transform keys and
 
 
3056
    entries. Because the regex looks for specific values on inital parsing
 
 
3057
    (i.e. the quotes and the equals signs) it can only read ascii compatible
 
 
3058
    encodings. For unicode use ``UTF8``, which is ASCII compatible.
 
 
3060
    Does it matter that we don't support the ':' divider, which is supported
 
 
3061
    by ``ConfigParser`` ?
 
 
3063
    The regular expression correctly removes the value -
 
 
3064
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
 
 
3065
    back quotes (called from ``_handle_value``). What should we do ??
 
 
3066
    (*ought* to raise exception because it's an invalid value if lists are
 
 
3067
    off *sigh*. This is not what you want if you want to do your own list
 
 
3068
    processing - would be *better* in this case not to unquote.)
 
 
3070
    String interpolation and validation don't play well together. When
 
 
3071
    validation changes type it sets the value. This will correctly fetch the
 
 
3072
    value using interpolation - but then overwrite the interpolation reference.
 
 
3073
    If the value is unchanged by validation (it's a string) - but other types
 
 
3080
    List values allow you to specify multiple values for a keyword. This
 
 
3081
    maps to a list as the resulting Python object when parsed.
 
 
3083
    The syntax for lists is easy. A list is a comma separated set of values.
 
 
3084
    If these values contain quotes, the hash mark, or commas, then the values
 
 
3085
    can be surrounded by quotes. e.g. : ::
 
 
3087
        keyword = value1, 'value 2', "value 3"
 
 
3089
    If a value needs to be a list, but only has one member, then you indicate
 
 
3090
    this with a trailing comma. e.g. : ::
 
 
3092
        keyword = "single value",
 
 
3094
    If a value needs to be a list, but it has no members, then you indicate
 
 
3095
    this with a single comma. e.g. : ::
 
 
3097
        keyword = ,     # an empty list
 
 
3099
    Using triple quotes it will be possible for single values to contain
 
 
3100
    newlines and *both* single quotes and double quotes. Triple quotes aren't
 
 
3101
    allowed in list values. This means that the members of list values can't
 
 
3102
    contain carriage returns (or line feeds :-) or both quote values.
 
 
3110
    Removed ``BOM_UTF8`` from ``__all__``.
 
 
3112
    The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
 
 
3113
    *only* ``True`` for the ``UTF16`` encoding.
 
 
3115
    File like objects no longer need a ``seek`` attribute.
 
 
3117
    ConfigObj no longer keeps a reference to file like objects. Instead the
 
 
3118
    ``write`` method takes a file like object as an optional argument. (Which
 
 
3119
    will be used in preference of the ``filename`` attribute if htat exists as
 
 
3122
    Full unicode support added. New options/attributes ``encoding``,
 
 
3123
    ``default_encoding``.
 
 
3125
    utf16 files decoded to unicode.
 
 
3127
    If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
 
 
3128
    written out at the start of the file. (It will normally only be ``True`` if
 
 
3129
    the utf8 BOM was found when the file was read.)
 
 
3131
    File paths are *not* converted to absolute paths, relative paths will
 
 
3132
    remain relative as the ``filename`` attribute.
 
 
3134
    Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
 
 
3140
    Added ``True``, ``False``, and ``enumerate`` if they are not defined.
 
 
3141
    (``True`` and ``False`` are needed for *early* versions of Python 2.2,
 
 
3142
    ``enumerate`` is needed for all versions ofPython 2.2)
 
 
3144
    Deprecated ``istrue``, replaced it with ``as_bool``.
 
 
3146
    Added ``as_int`` and ``as_float``.
 
 
3148
    utf8 and utf16 BOM handled in an endian agnostic way.
 
 
3153
    Validation no longer done on the 'DEFAULT' section (only in the root
 
 
3154
    level). This allows interpolation in configspecs.
 
 
3156
    Change in validation syntax implemented in validate 0.2.1
 
 
3163
    Added ``merge``, a recursive update.
 
 
3165
    Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
 
 
3168
    Thanks to Matthew Brett for suggestions and helping me iron out bugs.
 
 
3170
    Fixed bug where a config file is *all* comment, the comment will now be
 
 
3171
    ``initial_comment`` rather than ``final_comment``.
 
 
3176
    Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
 
 
3181
    Fixed bug in ``Section.walk`` when transforming names as well as values.
 
 
3183
    Added the ``istrue`` method. (Fetches the boolean equivalent of a string
 
 
3186
    Fixed ``list_values=False`` - they are now only quoted/unquoted if they
 
 
3187
    are multiline values.
 
 
3189
    List values are written as ``item, item`` rather than ``item,item``.
 
 
3196
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
 
 
3204
    Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
 
 
3205
    a reference to the new section.
 
 
3210
    Removed ``PositionError``.
 
 
3212
    Allowed quotes around keys as documented.
 
 
3214
    Fixed bug with commas in comments. (matched as a list value)
 
 
3221
    Fixed bug in initialising ConfigObj from a ConfigObj.
 
 
3223
    Changed the mailing list address.
 
 
3230
    Fixed bug in ``Section.__delitem__`` oops.
 
 
3235
    Interpolation is switched off before writing out files.
 
 
3237
    Fixed bug in handling ``StringIO`` instances. (Thanks to report from
 
 
3238
    "Gustavo Niemeyer" <gustavo@niemeyer.net>)
 
 
3240
    Moved the doctests from the ``__init__`` method to a separate function.
 
 
3241
    (For the sake of IDE calltips).
 
 
3248
    String values unchanged by validation *aren't* reset. This preserves
 
 
3249
    interpolation in string values.
 
 
3254
    None from a default is turned to '' if stringify is off - because setting 
 
 
3255
    a value to None raises an error.
 
 
3264
    Actually added the RepeatSectionError class ;-)
 
 
3269
    If ``stringify`` is off - list values are preserved by the ``validate``
 
 
3277
    Fixed ``simpleVal``.
 
 
3279
    Added ``RepeatSectionError`` error if you have additional sections in a
 
 
3280
    section with a ``__many__`` (repeated) section.
 
 
3284
    Reworked the ConfigObj._parse, _handle_error and _multiline methods:
 
 
3285
    mutated the self._infile, self._index and self._maxline attributes into
 
 
3286
    local variables and method parameters
 
 
3288
    Reshaped the ConfigObj._multiline method to better reflect its semantics
 
 
3290
    Changed the "default_test" test in ConfigObj.validate to check the fix for
 
 
3291
    the bug in validate.Validator.check
 
 
3298
    Updated comments at top
 
 
3305
    Implemented repeated sections.
 
 
3309
    Added test for interpreter version: raises RuntimeError if earlier than
 
 
3317
    Implemented default values in configspecs.
 
 
3321
    Fixed naked except: clause in validate that was silencing the fact
 
 
3322
    that Python2.2 does not have dict.pop
 
 
3329
    Bug fix causing error if file didn't exist.
 
 
3336
    Adjusted doctests for Python 2.2.3 compatibility
 
 
3343
    Added the inline_comments attribute
 
 
3345
    We now preserve and rewrite all comments in the config file
 
 
3347
    configspec is now a section attribute
 
 
3349
    The validate method changes values in place
 
 
3351
    Added InterpolationError
 
 
3353
    The errors now have line number, line, and message attributes. This
 
 
3354
    simplifies error handling
 
 
3363
    Fixed bug in Section.pop (now doesn't raise KeyError if a default value
 
 
3366
    Replaced ``basestring`` with ``types.StringTypes``
 
 
3368
    Removed the ``writein`` method
 
 
3377
    Indentation in config file is not significant anymore, subsections are
 
 
3378
    designated by repeating square brackets
 
 
3380
    Adapted all tests and docs to the new format
 
 
3394
    Reformatted final docstring in ReST format, indented it for easier folding
 
 
3396
    Code tests converted to doctest format, and scattered them around
 
 
3397
    in various docstrings
 
 
3399
    Walk method rewritten using scalars and sections attributes
 
 
3406
    Changed Validator and SimpleVal "test" methods to "check"
 
 
3413
    Changed Section.sequence to Section.scalars and Section.sections
 
 
3415
    Added Section.configspec
 
 
3417
    Sections in the root section now have no extra indentation
 
 
3419
    Comments now better supported in Section and preserved by ConfigObj
 
 
3421
    Comments also written out
 
 
3423
    Implemented initial_comment and final_comment
 
 
3425
    A scalar value after a section will now raise an error
 
 
3430
    Fixed a couple of bugs
 
 
3432
    Can now pass a tuple instead of a list
 
 
3434
    Simplified dict and walk methods
 
 
3436
    Added __str__ to Section
 
 
3448
    The stringify option implemented. On by default.
 
 
3453
    Renamed private attributes with a single underscore prefix.
 
 
3455
    Changes to interpolation - exceeding recursion depth, or specifying a
 
 
3456
    missing value, now raise errors.
 
 
3458
    Changes for Python 2.2 compatibility. (changed boolean tests - removed
 
 
3459
    ``is True`` and ``is False``)
 
 
3461
    Added test for duplicate section and member (and fixed bug)
 
 
3475
    Now properly handles values including comments and lists.
 
 
3477
    Better error handling.
 
 
3479
    String interpolation.
 
 
3481
    Some options implemented.
 
 
3483
    You can pass a Section a dictionary to initialise it.
 
 
3485
    Setting a Section member to a dictionary will create a Section instance.
 
 
3492
    Experimental reader.
 
 
3494
    A reasonably elegant implementation - a basic reader in 160 lines of code.
 
 
3496
    *A programming language is a medium of expression.* - Paul Graham