~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2006-02-22 04:29:54 UTC
  • mfrom: (1566 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1569.
  • Revision ID: mbp@sourcefrog.net-20060222042954-60333f08dd56a646
[merge] from bzr.dev before integration
Fix undefined ordering in sign_my_revisions breaking tests

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
8
9
 
9
10
# Released subject to the BSD License
10
 
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
 
11
# Please see http://www.voidspace.org.uk/python/license.shtml
11
12
 
12
13
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
13
14
# For information about bugfixes, updates and support, please join the
15
16
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
16
17
# Comments, suggestions and bug reports welcome.
17
18
 
 
19
from __future__ import generators
 
20
 
18
21
"""
19
22
    >>> z = ConfigObj()
20
23
    >>> z['a'] = 'a'
37
40
 
38
41
import os, re
39
42
from types import StringTypes
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 $'
 
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 $'
47
109
 
48
110
__docformat__ = "restructuredtext en"
49
111
 
 
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
50
116
__all__ = (
51
117
    '__version__',
52
 
    'BOM_UTF8',
53
118
    'DEFAULT_INDENT_TYPE',
54
119
    'NUM_INDENT_SPACES',
55
120
    'MAX_INTERPOL_DEPTH',
65
130
    'MissingInterpolationOption',
66
131
    'RepeatSectionError',
67
132
    '__docformat__',
 
133
    'flatten_errors',
68
134
)
69
135
 
70
136
DEFAULT_INDENT_TYPE = ' '
81
147
    'stringify': True,
82
148
    # option may be set to one of ('', ' ', '\t')
83
149
    'indent_type': None,
 
150
    'encoding': None,
 
151
    'default_encoding': None,
84
152
}
85
153
 
86
154
class ConfigObjError(SyntaxError):
196
264
 
197
265
    def __init__(self, parent, depth, main, indict=None, name=None):
198
266
        """
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
 
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
203
271
        """
204
272
        if indict is None:
205
273
            indict = {}
284
352
        """
285
353
        if not isinstance(key, StringTypes):
286
354
            raise ValueError, 'The key "%s" is not a string.' % key
287
 
##        if self.depth is None:
288
 
##            self.depth = 0
289
355
        # add the comment
290
356
        if not self.comments.has_key(key):
291
357
            self.comments[key] = []
346
412
            return default
347
413
 
348
414
    def update(self, indict):
349
 
        """A version of update that uses our ``__setitem__``."""
 
415
        """
 
416
        A version of update that uses our ``__setitem__``.
 
417
        """
350
418
        for entry in indict:
351
419
            self[entry] = indict[entry]
352
420
 
 
421
 
353
422
    def pop(self, key, *args):
354
423
        """ """
355
424
        val = dict.pop(self, key, *args)
456
525
            newdict[entry] = this_entry
457
526
        return newdict
458
527
 
 
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
 
459
554
    def rename(self, oldkey, newkey):
460
555
        """
461
556
        Change a keyname to another, without changing position in sequence.
507
602
        return value when called on the whole subsection has to be discarded.
508
603
        
509
604
        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'}}
510
629
        """
511
630
        out = {}
512
631
        # scalars first
513
 
        for entry in self.scalars[:]:
 
632
        for i in range(len(self.scalars)):
 
633
            entry = self.scalars[i]
514
634
            try:
515
 
                out[entry] = function(self, entry, **keywargs)
 
635
                val = function(self, entry, **keywargs)
 
636
                # bound again in case name has changed
 
637
                entry = self.scalars[i]
 
638
                out[entry] = val
516
639
            except Exception:
517
640
                if raise_errors:
518
641
                    raise
519
642
                else:
 
643
                    entry = self.scalars[i]
520
644
                    out[entry] = False
521
645
        # then sections
522
 
        for entry in self.sections[:]:
 
646
        for i in range(len(self.sections)):
 
647
            entry = self.sections[i]
523
648
            if call_on_sections:
524
649
                try:
525
650
                    function(self, entry, **keywargs)
527
652
                    if raise_errors:
528
653
                        raise
529
654
                    else:
 
655
                        entry = self.sections[i]
530
656
                        out[entry] = False
 
657
                # bound again in case name has changed
 
658
                entry = self.sections[i]
531
659
            # previous result is discarded
532
660
            out[entry] = self[entry].walk(
533
661
                function,
602
730
            section[newkey] = newval
603
731
        self.walk(encode, call_on_sections=True)
604
732
 
 
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
 
605
825
class ConfigObj(Section):
606
826
    """
607
827
    An object to read, create, and write config files.
722
942
        '"""': (_single_line_double, _multi_line_double),
723
943
    }
724
944
 
 
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
 
725
953
    def __init__(self, infile=None, options=None, **kwargs):
726
954
        """
727
955
        Parse or create a config file object.
741
969
        for entry in options.keys():
742
970
            if entry not in defaults.keys():
743
971
                raise TypeError, 'Unrecognised option "%s".' % entry
744
 
        # TODO: check the values too
745
 
        # add the explicit options to the defaults
 
972
        # TODO: check the values too.
 
973
        #
 
974
        # Add any explicit options to the defaults
746
975
        defaults.update(options)
747
976
        #
748
977
        # initialise a few variables
 
978
        self.filename = None
749
979
        self._errors = []
750
980
        self.raise_errors = defaults['raise_errors']
751
981
        self.interpolation = defaults['interpolation']
754
984
        self.file_error = defaults['file_error']
755
985
        self.stringify = defaults['stringify']
756
986
        self.indent_type = defaults['indent_type']
757
 
        # used by the write method
758
 
        self.BOM = None
 
987
        self.encoding = defaults['encoding']
 
988
        self.default_encoding = defaults['default_encoding']
 
989
        self.BOM = False
 
990
        self.newlines = None
759
991
        #
760
992
        self.initial_comment = []
761
993
        self.final_comment = []
762
994
        #
763
995
        if isinstance(infile, StringTypes):
764
 
            self.filename = os.path.abspath(infile)
765
 
            if os.path.isfile(self.filename):
766
 
                infile = open(self.filename).readlines()
 
996
            self.filename = infile
 
997
            if os.path.isfile(infile):
 
998
                infile = open(infile).read() or []
767
999
            elif self.file_error:
768
1000
                # raise an error if the file doesn't exist
769
1001
                raise IOError, 'Config file not found: "%s".' % self.filename
772
1004
                if self.create_empty:
773
1005
                    # this is a good test that the filename specified
774
1006
                    # isn't impossible - like on a non existent device
775
 
                    h = open(self.filename)
 
1007
                    h = open(infile, 'w')
776
1008
                    h.write('')
777
1009
                    h.close()
778
1010
                infile = []
779
1011
        elif isinstance(infile, (list, tuple)):
780
 
            self.filename = None
 
1012
            infile = list(infile)
781
1013
        elif isinstance(infile, dict):
782
1014
            # initialise self
783
1015
            # the Section class handles creating subsections
786
1018
                infile = infile.dict()
787
1019
            for entry in infile:
788
1020
                self[entry] = infile[entry]
789
 
            self.filename = None
790
1021
            del self._errors
791
1022
            if defaults['configspec'] is not None:
792
1023
                self._handle_configspec(defaults['configspec'])
793
1024
            else:
794
1025
                self.configspec = None
795
1026
            return
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)
 
1027
        elif hasattr(infile, 'read'):
 
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
802
1032
        else:
803
1033
            raise TypeError, ('infile must be a filename,'
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
 
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]
816
1052
        #
817
1053
        self._parse(infile)
818
1054
        # if we had any errors, now is the time to raise them
832
1068
        else:
833
1069
            self._handle_configspec(defaults['configspec'])
834
1070
 
 
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
 
835
1215
    def _parse(self, infile):
836
1216
        """
837
1217
        Actually parse the config file
1040
1420
            # no indentation used, set the type accordingly
1041
1421
            self.indent_type = ''
1042
1422
        # preserve the final comment
1043
 
        self.final_comment = comment_list
 
1423
        if not self and not self.initial_comment:
 
1424
            self.initial_comment = comment_list
 
1425
        else:
 
1426
            self.final_comment = comment_list
1044
1427
 
1045
1428
    def _match_depth(self, sect, depth):
1046
1429
        """
1095
1478
        Recursively quote members of a list and return a comma joined list.
1096
1479
        Multiline is ``False`` for lists.
1097
1480
        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).
1098
1484
        """
1099
1485
        if isinstance(value, (list, tuple)):
1100
1486
            if not value:
1101
1487
                return ','
1102
1488
            elif len(value) == 1:
1103
1489
                return self._quote(value[0], multiline=False) + ','
1104
 
            return ','.join([self._quote(val, multiline=False)
 
1490
            return ', '.join([self._quote(val, multiline=False)
1105
1491
                for val in value])
1106
1492
        if not isinstance(value, StringTypes):
1107
1493
            if self.stringify:
1116
1502
        tdquot = "'''%s'''"
1117
1503
        if not value:
1118
1504
            return '""'
1119
 
        if not (multiline and
 
1505
        if (not self.list_values and '\n' not in value) or not (multiline and
1120
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``
 
1509
                quot = noquot
1121
1510
            # for normal values either single or double quotes will do
1122
 
            if '\n' in value:
 
1511
            elif '\n' in value:
 
1512
                # will only happen if multiline is off - e.g. '\n' in key
1123
1513
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1124
1514
                    value)
1125
 
            if ((value[0] not in wspace_plus) and
 
1515
            elif ((value[0] not in wspace_plus) and
1126
1516
                    (value[-1] not in wspace_plus) and
1127
1517
                    (',' not in value)):
1128
1518
                quot = noquot
1244
1634
            if mat is None:
1245
1635
                raise SyntaxError
1246
1636
            (value, comment) = mat.groups()
1247
 
            # FIXME: unquoting here can be a source of error
1248
 
            return (self._unquote(value), comment)
 
1637
            # NOTE: we don't unquote here
 
1638
            return (value, comment)
1249
1639
        mat = self._valueexp.match(value)
1250
1640
        if mat is None:
1251
1641
            # the value is badly constructed, probably badly quoted,
1392
1782
 
1393
1783
    def _write_line(self, indent_string, entry, this_entry, comment):
1394
1784
        """Write an individual line, for the write method"""
1395
 
        return '%s%s = %s%s' % (
 
1785
        # NOTE: the calls to self._quote here handles non-StringType values.
 
1786
        return '%s%s%s%s%s' % (
1396
1787
            indent_string,
1397
 
            self._quote(entry, multiline=False),
1398
 
            self._quote(this_entry),
1399
 
            comment)
 
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))
1400
1792
 
1401
1793
    def _write_marker(self, indent_string, depth, entry, comment):
1402
1794
        """Write a section marker line"""
1403
1795
        return '%s%s%s%s%s' % (
1404
1796
            indent_string,
1405
 
            '[' * depth,
1406
 
            self._quote(entry, multiline=False),
1407
 
            ']' * depth,
1408
 
            comment)
 
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))
1409
1801
 
1410
1802
    def _handle_comment(self, comment):
1411
1803
        """
1437
1829
        if not comment:
1438
1830
            return ''
1439
1831
        if self.indent_type == '\t':
1440
 
            start = '\t'
 
1832
            start = self._a_to_u('\t')
1441
1833
        else:
1442
 
            start = ' ' * NUM_INDENT_SPACES
 
1834
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1443
1835
        if not comment.startswith('#'):
1444
 
            start += '# '
 
1836
            start += _a_to_u('# ')
1445
1837
        return (start + comment)
1446
1838
 
1447
1839
    def _compute_indent_string(self, depth):
1459
1851
 
1460
1852
    # Public methods
1461
1853
 
1462
 
    def write(self, section=None):
 
1854
    def write(self, outfile=None, section=None):
1463
1855
        """
1464
1856
        Write the current ConfigObj as a file
1465
1857
        
1488
1880
        >>> a.write()
1489
1881
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
1490
1882
        """
1491
 
        int_val = 'test'
1492
1883
        if self.indent_type is None:
1493
1884
            # this can be true if initialised from a dictionary
1494
1885
            self.indent_type = DEFAULT_INDENT_TYPE
1495
1886
        #
1496
1887
        out = []
1497
 
        return_list = True
 
1888
        cs = self._a_to_u('#')
 
1889
        csp = self._a_to_u('# ')
1498
1890
        if section is None:
1499
1891
            int_val = self.interpolation
1500
1892
            self.interpolation = False
1501
1893
            section = self
1502
 
            return_list = False
1503
1894
            for line in self.initial_comment:
 
1895
                line = self._decode_element(line)
1504
1896
                stripped_line = line.strip()
1505
 
                if stripped_line and not stripped_line.startswith('#'):
1506
 
                    line = '# ' + line
 
1897
                if stripped_line and not stripped_line.startswith(cs):
 
1898
                    line = csp + line
1507
1899
                out.append(line)
1508
1900
        #
1509
 
        indent_string = self._compute_indent_string(section.depth)
 
1901
        indent_string = self._a_to_u(
 
1902
            self._compute_indent_string(section.depth))
1510
1903
        for entry in (section.scalars + section.sections):
1511
1904
            if entry in section.defaults:
1512
1905
                # don't write out default values
1513
1906
                continue
1514
1907
            for comment_line in section.comments[entry]:
1515
 
                comment_line = comment_line.lstrip()
1516
 
                if comment_line and not comment_line.startswith('#'):
1517
 
                    comment_line = '#' + comment_line
 
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
1518
1911
                out.append(indent_string + comment_line)
1519
1912
            this_entry = section[entry]
1520
1913
            comment = self._handle_comment(section.inline_comments[entry])
1526
1919
                    this_entry.depth,
1527
1920
                    entry,
1528
1921
                    comment))
1529
 
                out.extend(self.write(this_entry))
 
1922
                out.extend(self.write(section=this_entry))
1530
1923
            else:
1531
1924
                out.append(self._write_line(
1532
1925
                    indent_string,
1534
1927
                    this_entry,
1535
1928
                    comment))
1536
1929
        #
1537
 
        if not return_list:
 
1930
        if section is self:
1538
1931
            for line in self.final_comment:
 
1932
                line = self._decode_element(line)
1539
1933
                stripped_line = line.strip()
1540
 
                if stripped_line and not stripped_line.startswith('#'):
1541
 
                    line = '# ' + line
 
1934
                if stripped_line and not stripped_line.startswith(cs):
 
1935
                    line = csp + line
1542
1936
                out.append(line)
1543
 
        #
1544
 
        if int_val != 'test':
1545
1937
            self.interpolation = int_val
1546
1938
        #
1547
 
        if (return_list) or (self.filename is None):
1548
 
            return out
1549
 
        #
1550
 
        if isinstance(self.filename, StringTypes):
 
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:
1551
1968
            h = open(self.filename, 'w')
1552
 
            h.write(self.BOM or '')
1553
 
            h.write('\n'.join(out))
 
1969
            h.write(output)
1554
1970
            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
1561
1971
 
1562
 
    def validate(self, validator, section=None):
 
1972
    def validate(self, validator, preserve_errors=False, section=None):
1563
1973
        """
1564
1974
        Test the ConfigObj against a configspec.
1565
1975
        
1581
1991
        In addition, it converts the values from strings to their native
1582
1992
        types if their checks pass (and ``stringify`` is set).
1583
1993
        
 
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
        
1584
2007
        >>> try:
1585
2008
        ...     from validate import Validator
1586
2009
        ... except ImportError:
1587
 
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
 
2010
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
1588
2011
        ... else:
1589
2012
        ...     config = '''
1590
2013
        ...     test1=40
1603
2026
        ...             test4=5.0
1604
2027
        ... '''.split('\\n')
1605
2028
        ...     configspec = '''
1606
 
        ...     test1='integer(30,50)'
1607
 
        ...     test2='string'
1608
 
        ...     test3='integer'
1609
 
        ...     test4='float(6.0)'
 
2029
        ...     test1= integer(30,50)
 
2030
        ...     test2= string
 
2031
        ...     test3=integer
 
2032
        ...     test4=float(6.0)
1610
2033
        ...     [section ]
1611
 
        ...         test1='integer(30,50)'
1612
 
        ...         test2='string'
1613
 
        ...         test3='integer'
1614
 
        ...         test4='float(6.0)'
 
2034
        ...         test1=integer(30,50)
 
2035
        ...         test2=string
 
2036
        ...         test3=integer
 
2037
        ...         test4=float(6.0)
1615
2038
        ...         [[sub section]]
1616
 
        ...             test1='integer(30,50)'
1617
 
        ...             test2='string'
1618
 
        ...             test3='integer'
1619
 
        ...             test4='float(6.0)'
 
2039
        ...             test1=integer(30,50)
 
2040
        ...             test2=string
 
2041
        ...             test3=integer
 
2042
        ...             test4=float(6.0)
1620
2043
        ...     '''.split('\\n')
1621
2044
        ...     val = Validator()
1622
2045
        ...     c1 = ConfigObj(config, configspec=configspec)
1670
2093
        >>> val_res == {'key2': True, 'section': True, 'key': False}
1671
2094
        1
1672
2095
        >>> configspec = '''
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)'
 
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)
1677
2100
        ...     [section ]
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)'
 
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)
1682
2105
        ...         [[sub section]]
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)'
 
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)
1687
2110
        ...     '''.split('\\n')
1688
2111
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1689
2112
        >>> default_test
1926
2349
        1
1927
2350
        
1928
2351
        Test that interpolation is preserved for validated string values.
 
2352
        Also check that interpolation works in configspecs.
1929
2353
        >>> t = ConfigObj()
1930
2354
        >>> t['DEFAULT'] = {}
1931
2355
        >>> t['DEFAULT']['test'] = 'a'
1939
2363
        >>> t.interpolation = False
1940
2364
        >>> t
1941
2365
        {'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'
1942
2376
        
1943
2377
        FIXME: Above tests will fail if we couldn't import Validator (the ones
1944
2378
        that don't raise errors will produce different output and still fail as
1947
2381
        if section is None:
1948
2382
            if self.configspec is None:
1949
2383
                raise ValueError, 'No configspec supplied.'
 
2384
            if preserve_errors:
 
2385
                if VdtMissingValue is None:
 
2386
                    raise ImportError('Missing validate module.')
1950
2387
            section = self
1951
2388
        #
1952
2389
        spec_section = section.configspec
1974
2411
            try:
1975
2412
                check = validator.check(spec_section[entry],
1976
2413
                                        val,
1977
 
                                        missing=missing)
1978
 
            except validator.baseErrorClass:
1979
 
                out[entry] = False
 
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
1980
2423
                ret_true = False
1981
 
            # MIKE: we want to raise all other exceptions, not just print ?
1982
 
##            except Exception, err:
1983
 
##                print err
1984
2424
            else:
1985
2425
                ret_false = False
1986
2426
                out[entry] = True
1990
2430
                    if not self.stringify:
1991
2431
                        if isinstance(check, (list, tuple)):
1992
2432
                            # preserve lists
1993
 
                            check = [str(item) for item in check]
 
2433
                            check = [self._str(item) for item in check]
1994
2434
                        elif missing and check is None:
1995
2435
                            # convert the None from a default to a ''
1996
2436
                            check = ''
1997
2437
                        else:
1998
 
                            check = str(check)
 
2438
                            check = self._str(check)
1999
2439
                    if (check != val) or missing:
2000
2440
                        section[entry] = check
2001
2441
                if missing and entry not in section.defaults:
2002
2442
                    section.defaults.append(entry)
2003
2443
        #
 
2444
        # FIXME: Will this miss missing sections ?
2004
2445
        for entry in section.sections:
2005
 
            check = self.validate(validator, section[entry])
 
2446
            if section is self and entry == 'DEFAULT':
 
2447
                continue
 
2448
            check = self.validate(validator, preserve_errors=preserve_errors,
 
2449
                section=section[entry])
2006
2450
            out[entry] = check
2007
2451
            if check == False:
2008
2452
                ret_true = False
2080
2524
            raise self.baseErrorClass
2081
2525
        return member
2082
2526
 
 
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
 
2083
2652
# FIXME: test error code for badly built multiline values
2084
2653
# FIXME: test handling of StringIO
2085
2654
# FIXME: test interpolation with writing
2146
2715
    >>> t2.inline_comments['b'] = ''
2147
2716
    >>> del t2['a']
2148
2717
    >>> 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 == None
 
2856
    1
 
2857
    >>> uc2.newlines == '\\r'
 
2858
    1
2149
2859
    """
2150
2860
 
2151
2861
if __name__ == '__main__':
2235
2945
    BUGS
2236
2946
    ====
2237
2947
    
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)
 
2948
    None known.
2241
2949
    
2242
2950
    TODO
2243
2951
    ====
2244
2952
    
 
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
    
2245
2966
    A method to optionally remove uniform indentation from multiline values.
2246
2967
    (do as an example of using ``walk`` - along with string-escape)
2247
2968
    
 
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
    
2248
2977
    INCOMPATIBLE CHANGES
2249
2978
    ====================
2250
2979
    
2304
3033
    ISSUES
2305
3034
    ======
2306
3035
    
 
3036
    ``validate`` doesn't report *extra* values or sections.
 
3037
    
2307
3038
    You can't have a keyword with the same name as a section (in the same
2308
3039
    section). They are both dictionary keys - so they would overlap.
2309
3040
    
 
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
    
2310
3045
    Interpolation checks first the 'DEFAULT' subsection of the current
2311
3046
    section, next it checks the 'DEFAULT' section of the parent section,
2312
3047
    last it checks the 'DEFAULT' section of the main section.
2325
3060
    Does it matter that we don't support the ':' divider, which is supported
2326
3061
    by ``ConfigParser`` ?
2327
3062
    
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
 
    
2335
3063
    The regular expression correctly removes the value -
2336
3064
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
2337
3065
    back quotes (called from ``_handle_value``). What should we do ??
2345
3073
    If the value is unchanged by validation (it's a string) - but other types
2346
3074
    will be.
2347
3075
    
 
3076
    
2348
3077
    List Value Syntax
2349
3078
    =================
2350
3079
    
2375
3104
    CHANGELOG
2376
3105
    =========
2377
3106
    
 
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
    
2378
3193
    2005/10/09
2379
3194
    ----------
2380
3195
    
2381
3196
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
2382
3197
    ``interpolation``).
 
3198
 
 
3199
    4.0.0 Final
2383
3200
    
2384
3201
    2005/09/16
2385
3202
    ----------
2410
3227
    2005/09/03
2411
3228
    ----------
2412
3229
    
2413
 
    Fixed bug in ``Section__delitem__`` oops.
 
3230
    Fixed bug in ``Section.__delitem__`` oops.
2414
3231
    
2415
3232
    2005/08/28
2416
3233
    ----------