~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/util/configobj/configobj.py

Don't encode unicode messages to UTF-8 in mutter() (the stream writer does it).

Use a codec wrapped log file in tests to match the real environment.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
#         nico AT tekNico DOT net
6
6
 
7
7
# ConfigObj 4
8
 
# http://www.voidspace.org.uk/python/configobj.html
9
8
 
10
9
# Released subject to the BSD License
11
 
# Please see http://www.voidspace.org.uk/python/license.shtml
 
10
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
12
11
 
13
12
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14
13
# For information about bugfixes, updates and support, please join the
16
15
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17
16
# Comments, suggestions and bug reports welcome.
18
17
 
19
 
from __future__ import generators
20
 
 
21
18
"""
22
19
    >>> z = ConfigObj()
23
20
    >>> z['a'] = 'a'
40
37
 
41
38
import os, re
42
39
from types import StringTypes
43
 
from warnings import warn
44
 
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
45
 
 
46
 
# A dictionary mapping BOM to
47
 
# the encoding to decode with, and what to set the
48
 
# encoding attribute to.
49
 
BOMS = {
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'),
54
 
    }
55
 
# All legal variants of the BOM codecs.
56
 
# TODO: the list of aliases is not meant to be exhaustive, is there a
57
 
#   better way ?
58
 
BOM_LIST = {
59
 
    'utf_16': 'utf_16',
60
 
    'u16': 'utf_16',
61
 
    'utf16': 'utf_16',
62
 
    'utf-16': 'utf_16',
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',
69
 
    'utf_8': 'utf_8',
70
 
    'u8': 'utf_8',
71
 
    'utf': 'utf_8',
72
 
    'utf8': 'utf_8',
73
 
    'utf-8': 'utf_8',
74
 
    }
75
 
 
76
 
# Map of encodings to the BOM to write.
77
 
BOM_SET = {
78
 
    'utf_8': BOM_UTF8,
79
 
    'utf_16': BOM_UTF16,
80
 
    'utf16_be': BOM_UTF16_BE,
81
 
    'utf16_le': BOM_UTF16_LE,
82
 
    None: BOM_UTF8
83
 
    }
84
 
 
85
 
try:
86
 
    from validate import VdtMissingValue
87
 
except ImportError:
88
 
    VdtMissingValue = None
89
 
 
90
 
try:
91
 
    enumerate
92
 
except NameError:
93
 
    def enumerate(obj):
94
 
        """enumerate for Python 2.2."""
95
 
        i = -1
96
 
        for item in obj:
97
 
            i += 1
98
 
            yield i, item
99
 
 
100
 
try:
101
 
    True, False
102
 
except NameError:
103
 
    True, False = 1, 0
104
 
 
105
 
 
106
 
__version__ = '4.2.0beta2'
107
 
 
108
 
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
 
40
 
 
41
# the UTF8 BOM - from codecs module
 
42
BOM_UTF8 = '\xef\xbb\xbf'
 
43
 
 
44
__version__ = '4.0.0'
 
45
 
 
46
__revision__ = '$Id: configobj.py 135 2005-10-12 14:52:28Z fuzzyman $'
109
47
 
110
48
__docformat__ = "restructuredtext en"
111
49
 
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
116
50
__all__ = (
117
51
    '__version__',
 
52
    'BOM_UTF8',
118
53
    'DEFAULT_INDENT_TYPE',
119
54
    'NUM_INDENT_SPACES',
120
55
    'MAX_INTERPOL_DEPTH',
130
65
    'MissingInterpolationOption',
131
66
    'RepeatSectionError',
132
67
    '__docformat__',
133
 
    'flatten_errors',
134
68
)
135
69
 
136
70
DEFAULT_INDENT_TYPE = ' '
147
81
    'stringify': True,
148
82
    # option may be set to one of ('', ' ', '\t')
149
83
    'indent_type': None,
150
 
    'encoding': None,
151
 
    'default_encoding': None,
152
84
}
153
85
 
154
86
class ConfigObjError(SyntaxError):
264
196
 
265
197
    def __init__(self, parent, depth, main, indict=None, name=None):
266
198
        """
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
 
199
        parent is the section above
 
200
        depth is the depth level of this section
 
201
        main is the main ConfigObj
 
202
        indict is a dictionary to initialise the section with
271
203
        """
272
204
        if indict is None:
273
205
            indict = {}
352
284
        """
353
285
        if not isinstance(key, StringTypes):
354
286
            raise ValueError, 'The key "%s" is not a string.' % key
 
287
##        if self.depth is None:
 
288
##            self.depth = 0
355
289
        # add the comment
356
 
        if key not in self.comments:
 
290
        if not self.comments.has_key(key):
357
291
            self.comments[key] = []
358
292
            self.inline_comments[key] = ''
359
293
        # remove the entry from defaults
361
295
            self.defaults.remove(key)
362
296
        #
363
297
        if isinstance(value, Section):
364
 
            if key not in self:
 
298
            if not self.has_key(key):
365
299
                self.sections.append(key)
366
300
            dict.__setitem__(self, key, value)
367
301
        elif isinstance(value, dict):
368
302
            # First create the new depth level,
369
303
            # then create the section
370
 
            if key not in self:
 
304
            if not self.has_key(key):
371
305
                self.sections.append(key)
372
306
            new_depth = self.depth + 1
373
307
            dict.__setitem__(
380
314
                    indict=value,
381
315
                    name=key))
382
316
        else:
383
 
            if key not in self:
 
317
            if not self.has_key(key):
384
318
                self.scalars.append(key)
385
319
            if not self.main.stringify:
386
320
                if isinstance(value, StringTypes):
412
346
            return default
413
347
 
414
348
    def update(self, indict):
415
 
        """
416
 
        A version of update that uses our ``__setitem__``.
417
 
        """
 
349
        """A version of update that uses our ``__setitem__``."""
418
350
        for entry in indict:
419
351
            self[entry] = indict[entry]
420
352
 
421
 
 
422
353
    def pop(self, key, *args):
423
354
        """ """
424
355
        val = dict.pop(self, key, *args)
525
456
            newdict[entry] = this_entry
526
457
        return newdict
527
458
 
528
 
    def merge(self, indict):
529
 
        """
530
 
        A recursive update - useful for merging config files.
531
 
        
532
 
        >>> a = '''[section1]
533
 
        ...     option1 = True
534
 
        ...     [[subsection]]
535
 
        ...     more_options = False
536
 
        ...     # end of file'''.splitlines()
537
 
        >>> b = '''# File is user.ini
538
 
        ...     [section1]
539
 
        ...     option1 = False
540
 
        ...     # end of file'''.splitlines()
541
 
        >>> c1 = ConfigObj(b)
542
 
        >>> c2 = ConfigObj(a)
543
 
        >>> c2.merge(c1)
544
 
        >>> c2
545
 
        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
546
 
        """
547
 
        for key, val in indict.items():
548
 
            if (key in self and isinstance(self[key], dict) and
549
 
                                isinstance(val, dict)):
550
 
                self[key].merge(val)
551
 
            else:   
552
 
                self[key] = val
553
 
 
554
459
    def rename(self, oldkey, newkey):
555
460
        """
556
461
        Change a keyname to another, without changing position in sequence.
602
507
        return value when called on the whole subsection has to be discarded.
603
508
        
604
509
        See  the encode and decode methods for examples, including functions.
605
 
        
606
 
        .. caution::
607
 
        
608
 
            You can use ``walk`` to transform the names of members of a section
609
 
            but you mustn't add or delete members.
610
 
        
611
 
        >>> config = '''[XXXXsection]
612
 
        ... XXXXkey = XXXXvalue'''.splitlines()
613
 
        >>> cfg = ConfigObj(config)
614
 
        >>> cfg
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)):
621
 
        ...         pass
622
 
        ...     else:
623
 
        ...         val = val.replace('XXXX', 'CLIENT1')
624
 
        ...         section[newkey] = val
625
 
        >>> cfg.walk(transform, call_on_sections=True)
626
 
        {'CLIENT1section': {'CLIENT1key': None}}
627
 
        >>> cfg
628
 
        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
629
510
        """
630
511
        out = {}
631
512
        # scalars first
632
 
        for i in range(len(self.scalars)):
633
 
            entry = self.scalars[i]
 
513
        for entry in self.scalars[:]:
634
514
            try:
635
 
                val = function(self, entry, **keywargs)
636
 
                # bound again in case name has changed
637
 
                entry = self.scalars[i]
638
 
                out[entry] = val
 
515
                out[entry] = function(self, entry, **keywargs)
639
516
            except Exception:
640
517
                if raise_errors:
641
518
                    raise
642
519
                else:
643
 
                    entry = self.scalars[i]
644
520
                    out[entry] = False
645
521
        # then sections
646
 
        for i in range(len(self.sections)):
647
 
            entry = self.sections[i]
 
522
        for entry in self.sections[:]:
648
523
            if call_on_sections:
649
524
                try:
650
525
                    function(self, entry, **keywargs)
652
527
                    if raise_errors:
653
528
                        raise
654
529
                    else:
655
 
                        entry = self.sections[i]
656
530
                        out[entry] = False
657
 
                # bound again in case name has changed
658
 
                entry = self.sections[i]
659
531
            # previous result is discarded
660
532
            out[entry] = self[entry].walk(
661
533
                function,
730
602
            section[newkey] = newval
731
603
        self.walk(encode, call_on_sections=True)
732
604
 
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)
738
 
 
739
 
    def as_bool(self, key):
740
 
        """
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.
744
 
        
745
 
        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
746
 
        ``True``.
747
 
        
748
 
        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
749
 
        ``False``.
750
 
        
751
 
        ``as_bool`` is not case sensitive.
752
 
        
753
 
        Any other input will raise a ``ValueError``.
754
 
        
755
 
        >>> a = ConfigObj()
756
 
        >>> a['a'] = 'fish'
757
 
        >>> a.as_bool('a')
758
 
        Traceback (most recent call last):
759
 
        ValueError: Value "fish" is neither True nor False
760
 
        >>> a['b'] = 'True'
761
 
        >>> a.as_bool('b')
762
 
        1
763
 
        >>> a['b'] = 'off'
764
 
        >>> a.as_bool('b')
765
 
        0
766
 
        """
767
 
        val = self[key]
768
 
        if val == True:
769
 
            return True
770
 
        elif val == False:
771
 
            return False
772
 
        else:
773
 
            try:
774
 
                if not isinstance(val, StringTypes):
775
 
                    raise KeyError
776
 
                else:
777
 
                    return self.main._bools[val.lower()]
778
 
            except KeyError:
779
 
                raise ValueError('Value "%s" is neither True nor False' % val)
780
 
 
781
 
    def as_int(self, key):
782
 
        """
783
 
        A convenience method which coerces the specified value to an integer.
784
 
        
785
 
        If the value is an invalid literal for ``int``, a ``ValueError`` will
786
 
        be raised.
787
 
        
788
 
        >>> a = ConfigObj()
789
 
        >>> a['a'] = 'fish'
790
 
        >>> a.as_int('a')
791
 
        Traceback (most recent call last):
792
 
        ValueError: invalid literal for int(): fish
793
 
        >>> a['b'] = '1'
794
 
        >>> a.as_int('b')
795
 
        1
796
 
        >>> a['b'] = '3.2'
797
 
        >>> a.as_int('b')
798
 
        Traceback (most recent call last):
799
 
        ValueError: invalid literal for int(): 3.2
800
 
        """
801
 
        return int(self[key])
802
 
 
803
 
    def as_float(self, key):
804
 
        """
805
 
        A convenience method which coerces the specified value to a float.
806
 
        
807
 
        If the value is an invalid literal for ``float``, a ``ValueError`` will
808
 
        be raised.
809
 
        
810
 
        >>> a = ConfigObj()
811
 
        >>> a['a'] = 'fish'
812
 
        >>> a.as_float('a')
813
 
        Traceback (most recent call last):
814
 
        ValueError: invalid literal for float(): fish
815
 
        >>> a['b'] = '1'
816
 
        >>> a.as_float('b')
817
 
        1.0
818
 
        >>> a['b'] = '3.2'
819
 
        >>> a.as_float('b')
820
 
        3.2000000000000002
821
 
        """
822
 
        return float(self[key])
823
 
    
824
 
 
825
605
class ConfigObj(Section):
826
606
    """
827
607
    An object to read, create, and write config files.
942
722
        '"""': (_single_line_double, _multi_line_double),
943
723
    }
944
724
 
945
 
    # Used by the ``istrue`` Section method
946
 
    _bools = {
947
 
        'yes': True, 'no': False,
948
 
        'on': True, 'off': False,
949
 
        '1': True, '0': False,
950
 
        'true': True, 'false': False,
951
 
        }
952
 
 
953
725
    def __init__(self, infile=None, options=None, **kwargs):
954
726
        """
955
727
        Parse or create a config file object.
969
741
        for entry in options.keys():
970
742
            if entry not in defaults.keys():
971
743
                raise TypeError, 'Unrecognised option "%s".' % entry
972
 
        # TODO: check the values too.
973
 
        #
974
 
        # Add any explicit options to the defaults
 
744
        # TODO: check the values too
 
745
        # add the explicit options to the defaults
975
746
        defaults.update(options)
976
747
        #
977
748
        # initialise a few variables
978
 
        self.filename = None
979
749
        self._errors = []
980
750
        self.raise_errors = defaults['raise_errors']
981
751
        self.interpolation = defaults['interpolation']
984
754
        self.file_error = defaults['file_error']
985
755
        self.stringify = defaults['stringify']
986
756
        self.indent_type = defaults['indent_type']
987
 
        self.encoding = defaults['encoding']
988
 
        self.default_encoding = defaults['default_encoding']
989
 
        self.BOM = False
990
 
        self.newlines = None
 
757
        # used by the write method
 
758
        self.BOM = None
991
759
        #
992
760
        self.initial_comment = []
993
761
        self.final_comment = []
994
762
        #
995
763
        if isinstance(infile, StringTypes):
996
 
            self.filename = infile
997
 
            if os.path.isfile(infile):
998
 
                infile = open(infile).read() or []
 
764
            self.filename = os.path.abspath(infile)
 
765
            if os.path.isfile(self.filename):
 
766
                infile = open(self.filename).readlines()
999
767
            elif self.file_error:
1000
768
                # raise an error if the file doesn't exist
1001
769
                raise IOError, 'Config file not found: "%s".' % self.filename
1004
772
                if self.create_empty:
1005
773
                    # this is a good test that the filename specified
1006
774
                    # isn't impossible - like on a non existent device
1007
 
                    h = open(infile, 'w')
 
775
                    h = open(self.filename)
1008
776
                    h.write('')
1009
777
                    h.close()
1010
778
                infile = []
1011
779
        elif isinstance(infile, (list, tuple)):
1012
 
            infile = list(infile)
 
780
            self.filename = None
1013
781
        elif isinstance(infile, dict):
1014
782
            # initialise self
1015
783
            # the Section class handles creating subsections
1018
786
                infile = infile.dict()
1019
787
            for entry in infile:
1020
788
                self[entry] = infile[entry]
 
789
            self.filename = None
1021
790
            del self._errors
1022
791
            if defaults['configspec'] is not None:
1023
792
                self._handle_configspec(defaults['configspec'])
1024
793
            else:
1025
794
                self.configspec = None
1026
795
            return
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
 
796
        elif hasattr(infile, 'seek'):
 
797
            # this supports StringIO instances and even file objects
 
798
            self.filename = infile
 
799
            infile.seek(0)
 
800
            infile = infile.readlines()
 
801
            self.filename.seek(0)
1032
802
        else:
1033
803
            raise TypeError, ('infile must be a filename,'
1034
 
                ' file like object, or list of lines.')
1035
 
        #
1036
 
        if infile:
1037
 
            # don't do it for the empty ConfigObj
1038
 
            infile = self._handle_bom(infile)
1039
 
            # infile is now *always* a list
1040
 
            #
1041
 
            # Set the newlines attribute (first line ending it finds)
1042
 
            # and strip trailing '\n' or '\r' from lines
1043
 
            for line in infile:
1044
 
                if (not line) or (line[-1] not in '\r\n'):
1045
 
                    continue
1046
 
                for end in ('\r\n', '\n', '\r'):
1047
 
                    if line.endswith(end):
1048
 
                        self.newlines = end
1049
 
                        break
1050
 
                break
1051
 
            infile = [line.rstrip('\r\n') for line in infile]
 
804
                ' StringIO instance, or a file as a list.')
 
805
        #
 
806
        # strip trailing '\n' from lines
 
807
        infile = [line.rstrip('\n') for line in infile]
 
808
        #
 
809
        # remove the UTF8 BOM if it is there
 
810
        # FIXME: support other BOM
 
811
        if infile and infile[0].startswith(BOM_UTF8):
 
812
            infile[0] = infile[0][3:]
 
813
            self.BOM = BOM_UTF8
 
814
        else:
 
815
            self.BOM = None
1052
816
        #
1053
817
        self._parse(infile)
1054
818
        # if we had any errors, now is the time to raise them
1068
832
        else:
1069
833
            self._handle_configspec(defaults['configspec'])
1070
834
 
1071
 
    def _handle_bom(self, infile):
1072
 
        """
1073
 
        Handle any BOM, and decode if necessary.
1074
 
        
1075
 
        If an encoding is specified, that *must* be used - but the BOM should
1076
 
        still be removed (and the BOM attribute set).
1077
 
        
1078
 
        (If the encoding is wrongly specified, then a BOM for an alternative
1079
 
        encoding won't be discovered or removed.)
1080
 
        
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
1083
 
        unicode.
1084
 
        
1085
 
        NOTE: This method must not be called with an empty ``infile``.
1086
 
        
1087
 
        Specifying the *wrong* encoding is likely to cause a
1088
 
        ``UnicodeDecodeError``.
1089
 
        
1090
 
        ``infile`` must always be returned as a list of lines, but may be
1091
 
        passed in as a single string.
1092
 
        """
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
1097
 
            # just decode
1098
 
            return self._decode(infile, self.encoding)
1099
 
        #
1100
 
        if isinstance(infile, (list, tuple)):
1101
 
            line = infile[0]
1102
 
        else:
1103
 
            line = infile
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()]
1110
 
            if enc == 'utf_16':
1111
 
                # For UTF16 we try big endian and little endian
1112
 
                for BOM, (encoding, final_encoding) in BOMS.items():
1113
 
                    if not final_encoding:
1114
 
                        # skip UTF8
1115
 
                        continue
1116
 
                    if infile.startswith(BOM):
1117
 
                        ### BOM discovered
1118
 
                        ##self.BOM = True
1119
 
                        # Don't need to remove BOM
1120
 
                        return self._decode(infile, encoding)
1121
 
                #
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)
1125
 
            #
1126
 
            # Must be UTF8
1127
 
            BOM = BOM_SET[enc]
1128
 
            if not line.startswith(BOM):
1129
 
                return self._decode(infile, self.encoding)
1130
 
            #
1131
 
            newline = line[len(BOM):]
1132
 
            #
1133
 
            # BOM removed
1134
 
            if isinstance(infile, (list, tuple)):
1135
 
                infile[0] = newline
1136
 
            else:
1137
 
                infile = newline
1138
 
            self.BOM = True
1139
 
            return self._decode(infile, self.encoding)
1140
 
        #
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):
1144
 
                continue
1145
 
            else:
1146
 
                # BOM discovered
1147
 
                self.encoding = final_encoding
1148
 
                if not final_encoding:
1149
 
                    self.BOM = True
1150
 
                    # UTF8
1151
 
                    # remove BOM
1152
 
                    newline = line[len(BOM):]
1153
 
                    if isinstance(infile, (list, tuple)):
1154
 
                        infile[0] = newline
1155
 
                    else:
1156
 
                        infile = newline
1157
 
                    # UTF8 - don't decode
1158
 
                    if isinstance(infile, StringTypes):
1159
 
                        return infile.splitlines(True)
1160
 
                    else:
1161
 
                        return infile
1162
 
                # UTF16 - have to decode
1163
 
                return self._decode(infile, encoding)
1164
 
        #
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)
1169
 
        else:
1170
 
            return infile
1171
 
 
1172
 
    def _a_to_u(self, string):
1173
 
        """Decode ascii strings to unicode if a self.encoding is specified."""
1174
 
        if not self.encoding:
1175
 
            return string
1176
 
        else:
1177
 
            return string.decode('ascii')
1178
 
 
1179
 
    def _decode(self, infile, encoding):
1180
 
        """
1181
 
        Decode infile to unicode. Using the specified encoding.
1182
 
        
1183
 
        if is a string, it also needs converting to a list.
1184
 
        """
1185
 
        if isinstance(infile, StringTypes):
1186
 
            # can't be unicode
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)
1195
 
        return infile
1196
 
 
1197
 
    def _decode_element(self, line):
1198
 
        """Decode element to unicode if necessary."""
1199
 
        if not self.encoding:
1200
 
            return line
1201
 
        if isinstance(line, str) and self.default_encoding:
1202
 
            return line.decode(self.default_encoding)
1203
 
        return line
1204
 
 
1205
 
    def _str(self, value):
1206
 
        """
1207
 
        Used by ``stringify`` within validate, to turn non-string values
1208
 
        into strings.
1209
 
        """
1210
 
        if not isinstance(value, StringTypes):
1211
 
            return str(value)
1212
 
        else:
1213
 
            return value
1214
 
 
1215
835
    def _parse(self, infile):
1216
836
        """
1217
837
        Actually parse the config file
1344
964
                        NestingError, infile, cur_index)
1345
965
                #
1346
966
                sect_name = self._unquote(sect_name)
1347
 
                if sect_name in parent:
 
967
                if parent.has_key(sect_name):
1348
968
##                    print >> sys.stderr, sect_name
1349
969
                    self._handle_error(
1350
970
                        'Duplicate section name at line %s.',
1394
1014
                #
1395
1015
##                print >> sys.stderr, sline
1396
1016
                key = self._unquote(key)
1397
 
                if key in this_section:
 
1017
                if this_section.has_key(key):
1398
1018
                    self._handle_error(
1399
1019
                        'Duplicate keyword name at line %s.',
1400
1020
                        DuplicateError, infile, cur_index)
1420
1040
            # no indentation used, set the type accordingly
1421
1041
            self.indent_type = ''
1422
1042
        # preserve the final comment
1423
 
        if not self and not self.initial_comment:
1424
 
            self.initial_comment = comment_list
1425
 
        else:
1426
 
            self.final_comment = comment_list
 
1043
        self.final_comment = comment_list
1427
1044
 
1428
1045
    def _match_depth(self, sect, depth):
1429
1046
        """
1478
1095
        Recursively quote members of a list and return a comma joined list.
1479
1096
        Multiline is ``False`` for lists.
1480
1097
        Obey list syntax for empty and single member lists.
1481
 
        
1482
 
        If ``list_values=False`` then the value is only quoted if it contains
1483
 
        a ``\n`` (is multiline).
1484
1098
        """
1485
1099
        if isinstance(value, (list, tuple)):
1486
1100
            if not value:
1487
1101
                return ','
1488
1102
            elif len(value) == 1:
1489
1103
                return self._quote(value[0], multiline=False) + ','
1490
 
            return ', '.join([self._quote(val, multiline=False)
 
1104
            return ','.join([self._quote(val, multiline=False)
1491
1105
                for val in value])
1492
1106
        if not isinstance(value, StringTypes):
1493
1107
            if self.stringify:
1502
1116
        tdquot = "'''%s'''"
1503
1117
        if not value:
1504
1118
            return '""'
1505
 
        if (not self.list_values and '\n' not in value) or not (multiline and
 
1119
        if not (multiline and
1506
1120
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
1507
 
            if not self.list_values:
1508
 
                # we don't quote if ``list_values=False``
1509
 
                quot = noquot
1510
1121
            # for normal values either single or double quotes will do
1511
 
            elif '\n' in value:
1512
 
                # will only happen if multiline is off - e.g. '\n' in key
 
1122
            if '\n' in value:
1513
1123
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1514
1124
                    value)
1515
 
            elif ((value[0] not in wspace_plus) and
 
1125
            if ((value[0] not in wspace_plus) and
1516
1126
                    (value[-1] not in wspace_plus) and
1517
1127
                    (',' not in value)):
1518
1128
                quot = noquot
1634
1244
            if mat is None:
1635
1245
                raise SyntaxError
1636
1246
            (value, comment) = mat.groups()
1637
 
            # NOTE: we don't unquote here
1638
 
            return (value, comment)
 
1247
            # FIXME: unquoting here can be a source of error
 
1248
            return (self._unquote(value), comment)
1639
1249
        mat = self._valueexp.match(value)
1640
1250
        if mat is None:
1641
1251
            # the value is badly constructed, probably badly quoted,
1745
1355
        for entry in configspec.sections:
1746
1356
            if entry == '__many__':
1747
1357
                continue
1748
 
            if entry not in section:
 
1358
            if not section.has_key(entry):
1749
1359
                section[entry] = {}
1750
1360
            self._set_configspec_value(configspec[entry], section[entry])
1751
1361
 
1776
1386
        #
1777
1387
        section.configspec = scalars
1778
1388
        for entry in sections:
1779
 
            if entry not in section:
 
1389
            if not section.has_key(entry):
1780
1390
                section[entry] = {}
1781
1391
            self._handle_repeat(section[entry], sections[entry])
1782
1392
 
1783
1393
    def _write_line(self, indent_string, entry, this_entry, comment):
1784
1394
        """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' % (
 
1395
        return '%s%s = %s%s' % (
1787
1396
            indent_string,
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))
 
1397
            self._quote(entry, multiline=False),
 
1398
            self._quote(this_entry),
 
1399
            comment)
1792
1400
 
1793
1401
    def _write_marker(self, indent_string, depth, entry, comment):
1794
1402
        """Write a section marker line"""
1795
1403
        return '%s%s%s%s%s' % (
1796
1404
            indent_string,
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))
 
1405
            '[' * depth,
 
1406
            self._quote(entry, multiline=False),
 
1407
            ']' * depth,
 
1408
            comment)
1801
1409
 
1802
1410
    def _handle_comment(self, comment):
1803
1411
        """
1829
1437
        if not comment:
1830
1438
            return ''
1831
1439
        if self.indent_type == '\t':
1832
 
            start = self._a_to_u('\t')
 
1440
            start = '\t'
1833
1441
        else:
1834
 
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
 
1442
            start = ' ' * NUM_INDENT_SPACES
1835
1443
        if not comment.startswith('#'):
1836
 
            start += _a_to_u('# ')
 
1444
            start += '# '
1837
1445
        return (start + comment)
1838
1446
 
1839
1447
    def _compute_indent_string(self, depth):
1851
1459
 
1852
1460
    # Public methods
1853
1461
 
1854
 
    def write(self, outfile=None, section=None):
 
1462
    def write(self, section=None):
1855
1463
        """
1856
1464
        Write the current ConfigObj as a file
1857
1465
        
1880
1488
        >>> a.write()
1881
1489
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
1882
1490
        """
 
1491
        int_val = 'test'
1883
1492
        if self.indent_type is None:
1884
1493
            # this can be true if initialised from a dictionary
1885
1494
            self.indent_type = DEFAULT_INDENT_TYPE
1886
1495
        #
1887
1496
        out = []
1888
 
        cs = self._a_to_u('#')
1889
 
        csp = self._a_to_u('# ')
 
1497
        return_list = True
1890
1498
        if section is None:
1891
1499
            int_val = self.interpolation
1892
1500
            self.interpolation = False
1893
1501
            section = self
 
1502
            return_list = False
1894
1503
            for line in self.initial_comment:
1895
 
                line = self._decode_element(line)
1896
1504
                stripped_line = line.strip()
1897
 
                if stripped_line and not stripped_line.startswith(cs):
1898
 
                    line = csp + line
 
1505
                if stripped_line and not stripped_line.startswith('#'):
 
1506
                    line = '# ' + line
1899
1507
                out.append(line)
1900
1508
        #
1901
 
        indent_string = self._a_to_u(
1902
 
            self._compute_indent_string(section.depth))
 
1509
        indent_string = self._compute_indent_string(section.depth)
1903
1510
        for entry in (section.scalars + section.sections):
1904
1511
            if entry in section.defaults:
1905
1512
                # don't write out default values
1906
1513
                continue
1907
1514
            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
 
1515
                comment_line = comment_line.lstrip()
 
1516
                if comment_line and not comment_line.startswith('#'):
 
1517
                    comment_line = '#' + comment_line
1911
1518
                out.append(indent_string + comment_line)
1912
1519
            this_entry = section[entry]
1913
1520
            comment = self._handle_comment(section.inline_comments[entry])
1919
1526
                    this_entry.depth,
1920
1527
                    entry,
1921
1528
                    comment))
1922
 
                out.extend(self.write(section=this_entry))
 
1529
                out.extend(self.write(this_entry))
1923
1530
            else:
1924
1531
                out.append(self._write_line(
1925
1532
                    indent_string,
1927
1534
                    this_entry,
1928
1535
                    comment))
1929
1536
        #
1930
 
        if section is self:
 
1537
        if not return_list:
1931
1538
            for line in self.final_comment:
1932
 
                line = self._decode_element(line)
1933
1539
                stripped_line = line.strip()
1934
 
                if stripped_line and not stripped_line.startswith(cs):
1935
 
                    line = csp + line
 
1540
                if stripped_line and not stripped_line.startswith('#'):
 
1541
                    line = '# ' + line
1936
1542
                out.append(line)
 
1543
        #
 
1544
        if int_val != 'test':
1937
1545
            self.interpolation = int_val
1938
1546
        #
1939
 
        if section is not self:
1940
 
            return out
1941
 
        #
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
1946
 
            if self.encoding:
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'))):
1950
 
                # Add the UTF8 BOM
1951
 
                if not out:
1952
 
                    out.append('')
1953
 
                out[0] = BOM_UTF8 + out[0]
1954
 
            return out
1955
 
        #
1956
 
        # Turn the list to a string, joined with correct newlines
1957
 
        output = (self._a_to_u(self.newlines or os.linesep)
1958
 
            ).join(out)
1959
 
        if self.encoding:
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'))):
1963
 
            # Add the UTF8 BOM
1964
 
            output = BOM_UTF8 + output
1965
 
        if outfile is not None:
1966
 
            outfile.write(output)
1967
 
        else:
 
1547
        if (return_list) or (self.filename is None):
 
1548
            return out
 
1549
        #
 
1550
        if isinstance(self.filename, StringTypes):
1968
1551
            h = open(self.filename, 'w')
1969
 
            h.write(output)
 
1552
            h.write(self.BOM or '')
 
1553
            h.write('\n'.join(out))
1970
1554
            h.close()
 
1555
        else:
 
1556
            self.filename.seek(0)
 
1557
            self.filename.write(self.BOM or '')
 
1558
            self.filename.write('\n'.join(out))
 
1559
            # if we have a stored file object (or StringIO)
 
1560
            # we *don't* close it
1971
1561
 
1972
 
    def validate(self, validator, preserve_errors=False, section=None):
 
1562
    def validate(self, validator, section=None):
1973
1563
        """
1974
1564
        Test the ConfigObj against a configspec.
1975
1565
        
1991
1581
        In addition, it converts the values from strings to their native
1992
1582
        types if their checks pass (and ``stringify`` is set).
1993
1583
        
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``.
2000
 
        
2001
 
        You must have the validate module to use ``preserve_errors=True``.
2002
 
        
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.
2006
 
        
2007
1584
        >>> try:
2008
1585
        ...     from validate import Validator
2009
1586
        ... except ImportError:
2010
 
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
 
1587
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
2011
1588
        ... else:
2012
1589
        ...     config = '''
2013
1590
        ...     test1=40
2026
1603
        ...             test4=5.0
2027
1604
        ... '''.split('\\n')
2028
1605
        ...     configspec = '''
2029
 
        ...     test1= integer(30,50)
2030
 
        ...     test2= string
2031
 
        ...     test3=integer
2032
 
        ...     test4=float(6.0)
 
1606
        ...     test1='integer(30,50)'
 
1607
        ...     test2='string'
 
1608
        ...     test3='integer'
 
1609
        ...     test4='float(6.0)'
2033
1610
        ...     [section ]
2034
 
        ...         test1=integer(30,50)
2035
 
        ...         test2=string
2036
 
        ...         test3=integer
2037
 
        ...         test4=float(6.0)
 
1611
        ...         test1='integer(30,50)'
 
1612
        ...         test2='string'
 
1613
        ...         test3='integer'
 
1614
        ...         test4='float(6.0)'
2038
1615
        ...         [[sub section]]
2039
 
        ...             test1=integer(30,50)
2040
 
        ...             test2=string
2041
 
        ...             test3=integer
2042
 
        ...             test4=float(6.0)
 
1616
        ...             test1='integer(30,50)'
 
1617
        ...             test2='string'
 
1618
        ...             test3='integer'
 
1619
        ...             test4='float(6.0)'
2043
1620
        ...     '''.split('\\n')
2044
1621
        ...     val = Validator()
2045
1622
        ...     c1 = ConfigObj(config, configspec=configspec)
2093
1670
        >>> val_res == {'key2': True, 'section': True, 'key': False}
2094
1671
        1
2095
1672
        >>> 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)
 
1673
        ...     test1='integer(30,50, default=40)'
 
1674
        ...     test2='string(default="hello")'
 
1675
        ...     test3='integer(default=3)'
 
1676
        ...     test4='float(6.0, default=6.0)'
2100
1677
        ...     [section ]
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)
 
1678
        ...         test1='integer(30,50, default=40)'
 
1679
        ...         test2='string(default="hello")'
 
1680
        ...         test3='integer(default=3)'
 
1681
        ...         test4='float(6.0, default=6.0)'
2105
1682
        ...         [[sub section]]
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)
 
1683
        ...             test1='integer(30,50, default=40)'
 
1684
        ...             test2='string(default="hello")'
 
1685
        ...             test3='integer(default=3)'
 
1686
        ...             test4='float(6.0, default=6.0)'
2110
1687
        ...     '''.split('\\n')
2111
1688
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2112
1689
        >>> default_test
2349
1926
        1
2350
1927
        
2351
1928
        Test that interpolation is preserved for validated string values.
2352
 
        Also check that interpolation works in configspecs.
2353
1929
        >>> t = ConfigObj()
2354
1930
        >>> t['DEFAULT'] = {}
2355
1931
        >>> t['DEFAULT']['test'] = 'a'
2363
1939
        >>> t.interpolation = False
2364
1940
        >>> t
2365
1941
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2366
 
        >>> specs = [
2367
 
        ...    'interpolated string  = string(default="fuzzy-%(man)s")',
2368
 
        ...    '[DEFAULT]',
2369
 
        ...    'man = wuzzy',
2370
 
        ...    ]
2371
 
        >>> c = ConfigObj(configspec=specs)
2372
 
        >>> c.validate(v)
2373
 
        1
2374
 
        >>> c['interpolated string']
2375
 
        'fuzzy-wuzzy'
2376
1942
        
2377
1943
        FIXME: Above tests will fail if we couldn't import Validator (the ones
2378
1944
        that don't raise errors will produce different output and still fail as
2381
1947
        if section is None:
2382
1948
            if self.configspec is None:
2383
1949
                raise ValueError, 'No configspec supplied.'
2384
 
            if preserve_errors:
2385
 
                if VdtMissingValue is None:
2386
 
                    raise ImportError('Missing validate module.')
2387
1950
            section = self
2388
1951
        #
2389
1952
        spec_section = section.configspec
2411
1974
            try:
2412
1975
                check = validator.check(spec_section[entry],
2413
1976
                                        val,
2414
 
                                        missing=missing
2415
 
                                        )
2416
 
            except validator.baseErrorClass, e:
2417
 
                if not preserve_errors or isinstance(e, VdtMissingValue):
2418
 
                    out[entry] = False
2419
 
                else:
2420
 
                    # preserve the error
2421
 
                    out[entry] = e
2422
 
                    ret_false = False
 
1977
                                        missing=missing)
 
1978
            except validator.baseErrorClass:
 
1979
                out[entry] = False
2423
1980
                ret_true = False
 
1981
            # MIKE: we want to raise all other exceptions, not just print ?
 
1982
##            except Exception, err:
 
1983
##                print err
2424
1984
            else:
2425
1985
                ret_false = False
2426
1986
                out[entry] = True
2430
1990
                    if not self.stringify:
2431
1991
                        if isinstance(check, (list, tuple)):
2432
1992
                            # preserve lists
2433
 
                            check = [self._str(item) for item in check]
 
1993
                            check = [str(item) for item in check]
2434
1994
                        elif missing and check is None:
2435
1995
                            # convert the None from a default to a ''
2436
1996
                            check = ''
2437
1997
                        else:
2438
 
                            check = self._str(check)
 
1998
                            check = str(check)
2439
1999
                    if (check != val) or missing:
2440
2000
                        section[entry] = check
2441
2001
                if missing and entry not in section.defaults:
2442
2002
                    section.defaults.append(entry)
2443
2003
        #
2444
 
        # FIXME: Will this miss missing sections ?
2445
2004
        for entry in section.sections:
2446
 
            if section is self and entry == 'DEFAULT':
2447
 
                continue
2448
 
            check = self.validate(validator, preserve_errors=preserve_errors,
2449
 
                section=section[entry])
 
2005
            check = self.validate(validator, section[entry])
2450
2006
            out[entry] = check
2451
2007
            if check == False:
2452
2008
                ret_true = False
2524
2080
            raise self.baseErrorClass
2525
2081
        return member
2526
2082
 
2527
 
# Check / processing functions for options
2528
 
def flatten_errors(cfg, res, levels=None, results=None):
2529
 
    """
2530
 
    An example function that will turn a nested dictionary of results
2531
 
    (as returned by ``ConfigObj.validate``) into a flat list.
2532
 
    
2533
 
    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2534
 
    dictionary returned by ``validate``.
2535
 
    
2536
 
    (This is a recursive function, so you shouldn't use the ``levels`` or
2537
 
    ``results`` arguments - they are used by the function.
2538
 
    
2539
 
    Returns a list of keys that failed. Each member of the list is a tuple :
2540
 
    ::
2541
 
    
2542
 
        ([list of sections...], key, result)
2543
 
    
2544
 
    If ``validate`` was called with ``preserve_errors=False`` (the default)
2545
 
    then ``result`` will always be ``False``.
2546
 
 
2547
 
    *list of sections* is a flattened list of sections that the key was found
2548
 
    in.
2549
 
    
2550
 
    If the section was missing then key will be ``None``.
2551
 
    
2552
 
    If the value (or section) was missing then ``result`` will be ``False``.
2553
 
    
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.
2557
 
    
2558
 
    For example *The value "3" is of the wrong type*.
2559
 
    
2560
 
    # FIXME: is the ordering of the output arbitrary ?
2561
 
    >>> import validate
2562
 
    >>> vtor = validate.Validator()
2563
 
    >>> my_ini = '''
2564
 
    ...     option1 = True
2565
 
    ...     [section1]
2566
 
    ...     option1 = True
2567
 
    ...     [section2]
2568
 
    ...     another_option = Probably
2569
 
    ...     [section3]
2570
 
    ...     another_option = True
2571
 
    ...     [[section3b]]
2572
 
    ...     value = 3
2573
 
    ...     value2 = a
2574
 
    ...     value3 = 11
2575
 
    ...     '''
2576
 
    >>> my_cfg = '''
2577
 
    ...     option1 = boolean()
2578
 
    ...     option2 = boolean()
2579
 
    ...     option3 = boolean(default=Bad_value)
2580
 
    ...     [section1]
2581
 
    ...     option1 = boolean()
2582
 
    ...     option2 = boolean()
2583
 
    ...     option3 = boolean(default=Bad_value)
2584
 
    ...     [section2]
2585
 
    ...     another_option = boolean()
2586
 
    ...     [section3]
2587
 
    ...     another_option = boolean()
2588
 
    ...     [[section3b]]
2589
 
    ...     value = integer
2590
 
    ...     value2 = integer
2591
 
    ...     value3 = integer(0, 10)
2592
 
    ...         [[[section3b-sub]]]
2593
 
    ...         value = string
2594
 
    ...     [section4]
2595
 
    ...     another_option = boolean()
2596
 
    ...     '''
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)
2601
 
    >>> errors = []
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)
2607
 
    ...     else:
2608
 
    ...         section_list.append('[missing]')
2609
 
    ...     section_string = ', '.join(section_list)
2610
 
    ...     errors.append((section_string, ' = ', error))
2611
 
    >>> errors.sort()
2612
 
    >>> for entry in errors:
2613
 
    ...     print entry[0], entry[1], (entry[2] or 0)
2614
 
    [root], option2  =  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
2623
 
    """
2624
 
    if levels is None:
2625
 
        # first time called
2626
 
        levels = []
2627
 
        results = []
2628
 
    if res is True:
2629
 
        return results
2630
 
    if res is False:
2631
 
        results.append((levels[:], None, False))
2632
 
        if levels:
2633
 
            levels.pop()
2634
 
        return results
2635
 
    for (key, val) in res.items():
2636
 
        if val == True:
2637
 
            continue
2638
 
        if isinstance(cfg.get(key), dict):
2639
 
            # Go down one level
2640
 
            levels.append(key)
2641
 
            flatten_errors(cfg[key], val, levels, results)
2642
 
            continue
2643
 
        results.append((levels[:], key, val))
2644
 
    #
2645
 
    # Go up one level
2646
 
    if levels:
2647
 
        levels.pop()
2648
 
    #
2649
 
    return results
2650
 
 
2651
 
 
2652
2083
# FIXME: test error code for badly built multiline values
2653
2084
# FIXME: test handling of StringIO
2654
2085
# FIXME: test interpolation with writing
2715
2146
    >>> t2.inline_comments['b'] = ''
2716
2147
    >>> del t2['a']
2717
2148
    >>> assert t2.write() == ['','b = b', '']
2718
 
    
2719
 
    # Test ``list_values=False`` stuff
2720
 
    >>> c = '''
2721
 
    ...     key1 = no quotes
2722
 
    ...     key2 = 'single quotes'
2723
 
    ...     key3 = "double quotes"
2724
 
    ...     key4 = "list", 'with', several, "quotes"
2725
 
    ...     '''
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"'
2730
 
    ... }
2731
 
    1
2732
 
    >>> cfg = ConfigObj(list_values=False)
2733
 
    >>> cfg['key1'] = 'Multiline\\nValue'
2734
 
    >>> cfg['key2'] = '''"Value" with 'quotes' !'''
2735
 
    >>> cfg.write()
2736
 
    ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2737
 
    >>> cfg.list_values = True
2738
 
    >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2739
 
    ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2740
 
    1
2741
 
    
2742
 
    Test flatten_errors:
2743
 
    
2744
 
    >>> from validate import Validator, VdtValueTooSmallError
2745
 
    >>> config = '''
2746
 
    ...     test1=40
2747
 
    ...     test2=hello
2748
 
    ...     test3=3
2749
 
    ...     test4=5.0
2750
 
    ...     [section]
2751
 
    ...         test1=40
2752
 
    ...         test2=hello
2753
 
    ...         test3=3
2754
 
    ...         test4=5.0
2755
 
    ...         [[sub section]]
2756
 
    ...             test1=40
2757
 
    ...             test2=hello
2758
 
    ...             test3=3
2759
 
    ...             test4=5.0
2760
 
    ... '''.split('\\n')
2761
 
    >>> configspec = '''
2762
 
    ...     test1= integer(30,50)
2763
 
    ...     test2= string
2764
 
    ...     test3=integer
2765
 
    ...     test4=float(6.0)
2766
 
    ...     [section ]
2767
 
    ...         test1=integer(30,50)
2768
 
    ...         test2=string
2769
 
    ...         test3=integer
2770
 
    ...         test4=float(6.0)
2771
 
    ...         [[sub section]]
2772
 
    ...             test1=integer(30,50)
2773
 
    ...             test2=string
2774
 
    ...             test3=integer
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)]
2782
 
    True
2783
 
    >>> res = c1.validate(val, preserve_errors=True)
2784
 
    >>> check = flatten_errors(c1, res)
2785
 
    >>> check[0][:2]
2786
 
    ([], 'test4')
2787
 
    >>> check[1][:2]
2788
 
    (['section', 'sub section'], 'test4')
2789
 
    >>> check[2][:2]
2790
 
    (['section'], 'test4')
2791
 
    >>> for entry in check:
2792
 
    ...     isinstance(entry[2], VdtValueTooSmallError)
2793
 
    ...     print str(entry[2])
2794
 
    True
2795
 
    the value "5.0" is too small.
2796
 
    True
2797
 
    the value "5.0" is too small.
2798
 
    True
2799
 
    the value "5.0" is too small.
2800
 
    
2801
 
    Test unicode handling, BOM, write witha file like object and line endings :
2802
 
    >>> u_base = '''
2803
 
    ... # initial comment
2804
 
    ...     # inital comment 2
2805
 
    ... 
2806
 
    ... test1 = some value
2807
 
    ... # comment
2808
 
    ... test2 = another value    # inline comment
2809
 
    ... # section comment
2810
 
    ... [section]    # inline comment
2811
 
    ...     test = test    # another inline comment
2812
 
    ...     test2 = test2
2813
 
    ... 
2814
 
    ... # final comment
2815
 
    ... # final comment2
2816
 
    ... '''
2817
 
    >>> u = u_base.encode('utf_8').splitlines(True)
2818
 
    >>> u[0] = BOM_UTF8 + u[0]
2819
 
    >>> uc = ConfigObj(u)
2820
 
    >>> uc.encoding = None
2821
 
    >>> uc.BOM == True
2822
 
    1
2823
 
    >>> uc == {'test1': 'some value', 'test2': 'another value',
2824
 
    ... 'section': {'test': 'test', 'test2': 'test2'}}
2825
 
    1
2826
 
    >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2827
 
    >>> uc.BOM
2828
 
    1
2829
 
    >>> isinstance(uc['test1'], unicode)
2830
 
    1
2831
 
    >>> uc.encoding
2832
 
    'utf_8'
2833
 
    >>> uc.newlines
2834
 
    '\\n'
2835
 
    >>> uc['latin1'] = "This costs lot's of "
2836
 
    >>> a_list = uc.write()
2837
 
    >>> len(a_list)
2838
 
    15
2839
 
    >>> isinstance(a_list[0], str)
2840
 
    1
2841
 
    >>> a_list[0].startswith(BOM_UTF8)
2842
 
    1
2843
 
    >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2844
 
    >>> uc = ConfigObj(u)
2845
 
    >>> uc.newlines
2846
 
    '\\r\\n'
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)
2853
 
    >>> uc2 == uc
2854
 
    1
2855
 
    >>> uc2.filename is None
2856
 
    1
2857
 
    >>> uc2.newlines == '\\r'
2858
 
    1
2859
2149
    """
2860
2150
 
2861
2151
if __name__ == '__main__':
2945
2235
    BUGS
2946
2236
    ====
2947
2237
    
2948
 
    None known.
 
2238
    With list values off, ConfigObj can incorrectly unquote values. (This makes
 
2239
    it impossible to use listquote to handle your list values for you - for
 
2240
    nested lists. Not handling quotes at all would be better for this)
2949
2241
    
2950
2242
    TODO
2951
2243
    ====
2952
2244
    
2953
 
    Better support for configuration from multiple files, including tracking
2954
 
    *where* the original file came from and writing changes to the correct
2955
 
    file.
2956
 
    
2957
 
    
2958
 
    Make ``newline`` an option (as well as an attribute) ?
2959
 
    
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
2962
 
    first line ?
2963
 
    
2964
 
    Option to set warning type for unicode decode ? (Defaults to strict).
2965
 
    
2966
2245
    A method to optionally remove uniform indentation from multiline values.
2967
2246
    (do as an example of using ``walk`` - along with string-escape)
2968
2247
    
2969
 
    Should the results dictionary from validate be an ordered dictionary if
2970
 
    `odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2971
 
    
2972
 
    Implement a better ``__repr__`` ? (``ConfigObj({})``)
2973
 
    
2974
 
    Implement some of the sequence methods (which include slicing) from the
2975
 
    newer ``odict`` ?
2976
 
    
2977
2248
    INCOMPATIBLE CHANGES
2978
2249
    ====================
2979
2250
    
3033
2304
    ISSUES
3034
2305
    ======
3035
2306
    
3036
 
    ``validate`` doesn't report *extra* values or sections.
3037
 
    
3038
2307
    You can't have a keyword with the same name as a section (in the same
3039
2308
    section). They are both dictionary keys - so they would overlap.
3040
2309
    
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).
3044
 
    
3045
2310
    Interpolation checks first the 'DEFAULT' subsection of the current
3046
2311
    section, next it checks the 'DEFAULT' section of the parent section,
3047
2312
    last it checks the 'DEFAULT' section of the main section.
3060
2325
    Does it matter that we don't support the ':' divider, which is supported
3061
2326
    by ``ConfigParser`` ?
3062
2327
    
 
2328
    Following error with "list_values=False" : ::
 
2329
    
 
2330
        >>> a = ["a='hello', 'goodbye'"]
 
2331
        >>>
 
2332
        >>> c(a, list_values=False)
 
2333
        {'a': "hello', 'goodbye"}
 
2334
    
3063
2335
    The regular expression correctly removes the value -
3064
2336
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
3065
2337
    back quotes (called from ``_handle_value``). What should we do ??
3073
2345
    If the value is unchanged by validation (it's a string) - but other types
3074
2346
    will be.
3075
2347
    
3076
 
    
3077
2348
    List Value Syntax
3078
2349
    =================
3079
2350
    
3104
2375
    CHANGELOG
3105
2376
    =========
3106
2377
    
3107
 
    2006/02/04
3108
 
    ----------
3109
 
    
3110
 
    Removed ``BOM_UTF8`` from ``__all__``.
3111
 
    
3112
 
    The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3113
 
    *only* ``True`` for the ``UTF16`` encoding.
3114
 
    
3115
 
    File like objects no longer need a ``seek`` attribute.
3116
 
    
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
3120
 
    well.)
3121
 
    
3122
 
    Full unicode support added. New options/attributes ``encoding``,
3123
 
    ``default_encoding``.
3124
 
    
3125
 
    utf16 files decoded to unicode.
3126
 
    
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.)
3130
 
    
3131
 
    File paths are *not* converted to absolute paths, relative paths will
3132
 
    remain relative as the ``filename`` attribute.
3133
 
    
3134
 
    Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3135
 
    a list of lines.
3136
 
    
3137
 
    2006/01/31
3138
 
    ----------
3139
 
    
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)
3143
 
    
3144
 
    Deprecated ``istrue``, replaced it with ``as_bool``.
3145
 
    
3146
 
    Added ``as_int`` and ``as_float``.
3147
 
    
3148
 
    utf8 and utf16 BOM handled in an endian agnostic way.
3149
 
    
3150
 
    2005/12/14
3151
 
    ----------
3152
 
    
3153
 
    Validation no longer done on the 'DEFAULT' section (only in the root
3154
 
    level). This allows interpolation in configspecs.
3155
 
    
3156
 
    Change in validation syntax implemented in validate 0.2.1
3157
 
    
3158
 
    4.1.0
3159
 
    
3160
 
    2005/12/10
3161
 
    ----------
3162
 
    
3163
 
    Added ``merge``, a recursive update.
3164
 
    
3165
 
    Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3166
 
    example function.
3167
 
    
3168
 
    Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3169
 
    
3170
 
    Fixed bug where a config file is *all* comment, the comment will now be
3171
 
    ``initial_comment`` rather than ``final_comment``.
3172
 
    
3173
 
    2005/12/02
3174
 
    ----------
3175
 
    
3176
 
    Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3177
 
    
3178
 
    2005/11/04
3179
 
    ----------
3180
 
    
3181
 
    Fixed bug in ``Section.walk`` when transforming names as well as values.
3182
 
    
3183
 
    Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3184
 
    value).
3185
 
    
3186
 
    Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3187
 
    are multiline values.
3188
 
    
3189
 
    List values are written as ``item, item`` rather than ``item,item``.
3190
 
    
3191
 
    4.0.1
3192
 
    
3193
2378
    2005/10/09
3194
2379
    ----------
3195
2380
    
3196
2381
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3197
2382
    ``interpolation``).
3198
 
 
3199
 
    4.0.0 Final
3200
2383
    
3201
2384
    2005/09/16
3202
2385
    ----------
3227
2410
    2005/09/03
3228
2411
    ----------
3229
2412
    
3230
 
    Fixed bug in ``Section.__delitem__`` oops.
 
2413
    Fixed bug in ``Section__delitem__`` oops.
3231
2414
    
3232
2415
    2005/08/28
3233
2416
    ----------