~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Date: 2007-11-13 20:37:09 UTC
  • mto: This revision was merged to the branch mainline in revision 3001.
  • Revision ID: john@arbash-meinel.com-20071113203709-kysdte0emqv84pnj
Fix bug #162486, by having RemoteBranch properly initialize self._revision_id_to_revno_map.

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
 
        if not self.comments.has_key(key):
 
356
        if key not in self.comments:
291
357
            self.comments[key] = []
292
358
            self.inline_comments[key] = ''
293
359
        # remove the entry from defaults
295
361
            self.defaults.remove(key)
296
362
        #
297
363
        if isinstance(value, Section):
298
 
            if not self.has_key(key):
 
364
            if key not in self:
299
365
                self.sections.append(key)
300
366
            dict.__setitem__(self, key, value)
301
367
        elif isinstance(value, dict):
302
368
            # First create the new depth level,
303
369
            # then create the section
304
 
            if not self.has_key(key):
 
370
            if key not in self:
305
371
                self.sections.append(key)
306
372
            new_depth = self.depth + 1
307
373
            dict.__setitem__(
314
380
                    indict=value,
315
381
                    name=key))
316
382
        else:
317
 
            if not self.has_key(key):
 
383
            if key not in self:
318
384
                self.scalars.append(key)
319
385
            if not self.main.stringify:
320
386
                if isinstance(value, StringTypes):
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,
550
678
        >>> def testuni(val):
551
679
        ...     for entry in val:
552
680
        ...         if not isinstance(entry, unicode):
553
 
        ...             print >> sys.stderr, type(entry)
 
681
        ...             sys.stderr.write(type(entry))
 
682
        ...             sys.stderr.write('\n')
554
683
        ...             raise AssertionError, 'decode failed.'
555
684
        ...         if isinstance(val[entry], dict):
556
685
        ...             testuni(val[entry])
602
731
            section[newkey] = newval
603
732
        self.walk(encode, call_on_sections=True)
604
733
 
 
734
    def istrue(self, key):
 
735
        """A deprecated version of ``as_bool``."""
 
736
        warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
 
737
                'instead.', DeprecationWarning)
 
738
        return self.as_bool(key)
 
739
 
 
740
    def as_bool(self, key):
 
741
        """
 
742
        Accepts a key as input. The corresponding value must be a string or
 
743
        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
 
744
        retain compatibility with Python 2.2.
 
745
        
 
746
        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
 
747
        ``True``.
 
748
        
 
749
        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
 
750
        ``False``.
 
751
        
 
752
        ``as_bool`` is not case sensitive.
 
753
        
 
754
        Any other input will raise a ``ValueError``.
 
755
        
 
756
        >>> a = ConfigObj()
 
757
        >>> a['a'] = 'fish'
 
758
        >>> a.as_bool('a')
 
759
        Traceback (most recent call last):
 
760
        ValueError: Value "fish" is neither True nor False
 
761
        >>> a['b'] = 'True'
 
762
        >>> a.as_bool('b')
 
763
        1
 
764
        >>> a['b'] = 'off'
 
765
        >>> a.as_bool('b')
 
766
        0
 
767
        """
 
768
        val = self[key]
 
769
        if val == True:
 
770
            return True
 
771
        elif val == False:
 
772
            return False
 
773
        else:
 
774
            try:
 
775
                if not isinstance(val, StringTypes):
 
776
                    raise KeyError
 
777
                else:
 
778
                    return self.main._bools[val.lower()]
 
779
            except KeyError:
 
780
                raise ValueError('Value "%s" is neither True nor False' % val)
 
781
 
 
782
    def as_int(self, key):
 
783
        """
 
784
        A convenience method which coerces the specified value to an integer.
 
785
        
 
786
        If the value is an invalid literal for ``int``, a ``ValueError`` will
 
787
        be raised.
 
788
        
 
789
        >>> a = ConfigObj()
 
790
        >>> a['a'] = 'fish'
 
791
        >>> a.as_int('a')
 
792
        Traceback (most recent call last):
 
793
        ValueError: invalid literal for int(): fish
 
794
        >>> a['b'] = '1'
 
795
        >>> a.as_int('b')
 
796
        1
 
797
        >>> a['b'] = '3.2'
 
798
        >>> a.as_int('b')
 
799
        Traceback (most recent call last):
 
800
        ValueError: invalid literal for int(): 3.2
 
801
        """
 
802
        return int(self[key])
 
803
 
 
804
    def as_float(self, key):
 
805
        """
 
806
        A convenience method which coerces the specified value to a float.
 
807
        
 
808
        If the value is an invalid literal for ``float``, a ``ValueError`` will
 
809
        be raised.
 
810
        
 
811
        >>> a = ConfigObj()
 
812
        >>> a['a'] = 'fish'
 
813
        >>> a.as_float('a')
 
814
        Traceback (most recent call last):
 
815
        ValueError: invalid literal for float(): fish
 
816
        >>> a['b'] = '1'
 
817
        >>> a.as_float('b')
 
818
        1.0
 
819
        >>> a['b'] = '3.2'
 
820
        >>> a.as_float('b')
 
821
        3.2000000000000002
 
822
        """
 
823
        return float(self[key])
 
824
    
 
825
 
605
826
class ConfigObj(Section):
606
827
    """
607
828
    An object to read, create, and write config files.
722
943
        '"""': (_single_line_double, _multi_line_double),
723
944
    }
724
945
 
 
946
    # Used by the ``istrue`` Section method
 
947
    _bools = {
 
948
        'yes': True, 'no': False,
 
949
        'on': True, 'off': False,
 
950
        '1': True, '0': False,
 
951
        'true': True, 'false': False,
 
952
        }
 
953
 
725
954
    def __init__(self, infile=None, options=None, **kwargs):
726
955
        """
727
956
        Parse or create a config file object.
741
970
        for entry in options.keys():
742
971
            if entry not in defaults.keys():
743
972
                raise TypeError, 'Unrecognised option "%s".' % entry
744
 
        # TODO: check the values too
745
 
        # add the explicit options to the defaults
 
973
        # TODO: check the values too.
 
974
        #
 
975
        # Add any explicit options to the defaults
746
976
        defaults.update(options)
747
977
        #
748
978
        # initialise a few variables
 
979
        self.filename = None
749
980
        self._errors = []
750
981
        self.raise_errors = defaults['raise_errors']
751
982
        self.interpolation = defaults['interpolation']
754
985
        self.file_error = defaults['file_error']
755
986
        self.stringify = defaults['stringify']
756
987
        self.indent_type = defaults['indent_type']
757
 
        # used by the write method
758
 
        self.BOM = None
 
988
        self.encoding = defaults['encoding']
 
989
        self.default_encoding = defaults['default_encoding']
 
990
        self.BOM = False
 
991
        self.newlines = None
759
992
        #
760
993
        self.initial_comment = []
761
994
        self.final_comment = []
762
995
        #
763
996
        if isinstance(infile, StringTypes):
764
 
            self.filename = os.path.abspath(infile)
765
 
            if os.path.isfile(self.filename):
766
 
                infile = open(self.filename).readlines()
 
997
            self.filename = infile
 
998
            if os.path.isfile(infile):
 
999
                infile = open(infile).read() or []
767
1000
            elif self.file_error:
768
1001
                # raise an error if the file doesn't exist
769
1002
                raise IOError, 'Config file not found: "%s".' % self.filename
772
1005
                if self.create_empty:
773
1006
                    # this is a good test that the filename specified
774
1007
                    # isn't impossible - like on a non existent device
775
 
                    h = open(self.filename)
 
1008
                    h = open(infile, 'w')
776
1009
                    h.write('')
777
1010
                    h.close()
778
1011
                infile = []
779
1012
        elif isinstance(infile, (list, tuple)):
780
 
            self.filename = None
 
1013
            infile = list(infile)
781
1014
        elif isinstance(infile, dict):
782
1015
            # initialise self
783
1016
            # the Section class handles creating subsections
786
1019
                infile = infile.dict()
787
1020
            for entry in infile:
788
1021
                self[entry] = infile[entry]
789
 
            self.filename = None
790
1022
            del self._errors
791
1023
            if defaults['configspec'] is not None:
792
1024
                self._handle_configspec(defaults['configspec'])
793
1025
            else:
794
1026
                self.configspec = None
795
1027
            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)
 
1028
        elif getattr(infile, 'read', None) is not None:
 
1029
            # This supports file like objects
 
1030
            infile = infile.read() or []
 
1031
            # needs splitting into lines - but needs doing *after* decoding
 
1032
            # in case it's not an 8 bit encoding
802
1033
        else:
803
1034
            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
 
1035
                ' file like object, or list of lines.')
 
1036
        #
 
1037
        if infile:
 
1038
            # don't do it for the empty ConfigObj
 
1039
            infile = self._handle_bom(infile)
 
1040
            # infile is now *always* a list
 
1041
            #
 
1042
            # Set the newlines attribute (first line ending it finds)
 
1043
            # and strip trailing '\n' or '\r' from lines
 
1044
            for line in infile:
 
1045
                if (not line) or (line[-1] not in '\r\n'):
 
1046
                    continue
 
1047
                for end in ('\r\n', '\n', '\r'):
 
1048
                    if line.endswith(end):
 
1049
                        self.newlines = end
 
1050
                        break
 
1051
                break
 
1052
            infile = [line.rstrip('\r\n') for line in infile]
816
1053
        #
817
1054
        self._parse(infile)
818
1055
        # if we had any errors, now is the time to raise them
832
1069
        else:
833
1070
            self._handle_configspec(defaults['configspec'])
834
1071
 
 
1072
    def _handle_bom(self, infile):
 
1073
        """
 
1074
        Handle any BOM, and decode if necessary.
 
1075
        
 
1076
        If an encoding is specified, that *must* be used - but the BOM should
 
1077
        still be removed (and the BOM attribute set).
 
1078
        
 
1079
        (If the encoding is wrongly specified, then a BOM for an alternative
 
1080
        encoding won't be discovered or removed.)
 
1081
        
 
1082
        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
 
1083
        removed. The BOM attribute will be set. UTF16 will be decoded to
 
1084
        unicode.
 
1085
        
 
1086
        NOTE: This method must not be called with an empty ``infile``.
 
1087
        
 
1088
        Specifying the *wrong* encoding is likely to cause a
 
1089
        ``UnicodeDecodeError``.
 
1090
        
 
1091
        ``infile`` must always be returned as a list of lines, but may be
 
1092
        passed in as a single string.
 
1093
        """
 
1094
        if ((self.encoding is not None) and
 
1095
            (self.encoding.lower() not in BOM_LIST)):
 
1096
            # No need to check for a BOM
 
1097
            # encoding specified doesn't have one
 
1098
            # just decode
 
1099
            return self._decode(infile, self.encoding)
 
1100
        #
 
1101
        if isinstance(infile, (list, tuple)):
 
1102
            line = infile[0]
 
1103
        else:
 
1104
            line = infile
 
1105
        if self.encoding is not None:
 
1106
            # encoding explicitly supplied
 
1107
            # And it could have an associated BOM
 
1108
            # TODO: if encoding is just UTF16 - we ought to check for both
 
1109
            # TODO: big endian and little endian versions.
 
1110
            enc = BOM_LIST[self.encoding.lower()]
 
1111
            if enc == 'utf_16':
 
1112
                # For UTF16 we try big endian and little endian
 
1113
                for BOM, (encoding, final_encoding) in BOMS.items():
 
1114
                    if not final_encoding:
 
1115
                        # skip UTF8
 
1116
                        continue
 
1117
                    if infile.startswith(BOM):
 
1118
                        ### BOM discovered
 
1119
                        ##self.BOM = True
 
1120
                        # Don't need to remove BOM
 
1121
                        return self._decode(infile, encoding)
 
1122
                #
 
1123
                # If we get this far, will *probably* raise a DecodeError
 
1124
                # As it doesn't appear to start with a BOM
 
1125
                return self._decode(infile, self.encoding)
 
1126
            #
 
1127
            # Must be UTF8
 
1128
            BOM = BOM_SET[enc]
 
1129
            if not line.startswith(BOM):
 
1130
                return self._decode(infile, self.encoding)
 
1131
            #
 
1132
            newline = line[len(BOM):]
 
1133
            #
 
1134
            # BOM removed
 
1135
            if isinstance(infile, (list, tuple)):
 
1136
                infile[0] = newline
 
1137
            else:
 
1138
                infile = newline
 
1139
            self.BOM = True
 
1140
            return self._decode(infile, self.encoding)
 
1141
        #
 
1142
        # No encoding specified - so we need to check for UTF8/UTF16
 
1143
        for BOM, (encoding, final_encoding) in BOMS.items():
 
1144
            if not line.startswith(BOM):
 
1145
                continue
 
1146
            else:
 
1147
                # BOM discovered
 
1148
                self.encoding = final_encoding
 
1149
                if not final_encoding:
 
1150
                    self.BOM = True
 
1151
                    # UTF8
 
1152
                    # remove BOM
 
1153
                    newline = line[len(BOM):]
 
1154
                    if isinstance(infile, (list, tuple)):
 
1155
                        infile[0] = newline
 
1156
                    else:
 
1157
                        infile = newline
 
1158
                    # UTF8 - don't decode
 
1159
                    if isinstance(infile, StringTypes):
 
1160
                        return infile.splitlines(True)
 
1161
                    else:
 
1162
                        return infile
 
1163
                # UTF16 - have to decode
 
1164
                return self._decode(infile, encoding)
 
1165
        #
 
1166
        # No BOM discovered and no encoding specified, just return
 
1167
        if isinstance(infile, StringTypes):
 
1168
            # infile read from a file will be a single string
 
1169
            return infile.splitlines(True)
 
1170
        else:
 
1171
            return infile
 
1172
 
 
1173
    def _a_to_u(self, string):
 
1174
        """Decode ascii strings to unicode if a self.encoding is specified."""
 
1175
        if not self.encoding:
 
1176
            return string
 
1177
        else:
 
1178
            return string.decode('ascii')
 
1179
 
 
1180
    def _decode(self, infile, encoding):
 
1181
        """
 
1182
        Decode infile to unicode. Using the specified encoding.
 
1183
        
 
1184
        if is a string, it also needs converting to a list.
 
1185
        """
 
1186
        if isinstance(infile, StringTypes):
 
1187
            # can't be unicode
 
1188
            # NOTE: Could raise a ``UnicodeDecodeError``
 
1189
            return infile.decode(encoding).splitlines(True)
 
1190
        for i, line in enumerate(infile):
 
1191
            if not isinstance(line, unicode):
 
1192
                # NOTE: The isinstance test here handles mixed lists of unicode/string
 
1193
                # NOTE: But the decode will break on any non-string values
 
1194
                # NOTE: Or could raise a ``UnicodeDecodeError``
 
1195
                infile[i] = line.decode(encoding)
 
1196
        return infile
 
1197
 
 
1198
    def _decode_element(self, line):
 
1199
        """Decode element to unicode if necessary."""
 
1200
        if not self.encoding:
 
1201
            return line
 
1202
        if isinstance(line, str) and self.default_encoding:
 
1203
            return line.decode(self.default_encoding)
 
1204
        return line
 
1205
 
 
1206
    def _str(self, value):
 
1207
        """
 
1208
        Used by ``stringify`` within validate, to turn non-string values
 
1209
        into strings.
 
1210
        """
 
1211
        if not isinstance(value, StringTypes):
 
1212
            return str(value)
 
1213
        else:
 
1214
            return value
 
1215
 
835
1216
    def _parse(self, infile):
836
1217
        """
837
1218
        Actually parse the config file
928
1309
            reset_comment = True
929
1310
            # first we check if it's a section marker
930
1311
            mat = self._sectionmarker.match(line)
931
 
##            print >> sys.stderr, sline, mat
 
1312
##            sys.stderr.write('%s %s\n' % (sline, mat))
932
1313
            if mat is not None:
933
1314
                # is a section line
934
1315
                (indent, sect_open, sect_name, sect_close, comment) = (
964
1345
                        NestingError, infile, cur_index)
965
1346
                #
966
1347
                sect_name = self._unquote(sect_name)
967
 
                if parent.has_key(sect_name):
968
 
##                    print >> sys.stderr, sect_name
 
1348
                if sect_name in parent:
 
1349
##                    sys.stderr.write(sect_name)
 
1350
##                    sys.stderr.write('\n')
969
1351
                    self._handle_error(
970
1352
                        'Duplicate section name at line %s.',
971
1353
                        DuplicateError, infile, cur_index)
979
1361
                parent[sect_name] = this_section
980
1362
                parent.inline_comments[sect_name] = comment
981
1363
                parent.comments[sect_name] = comment_list
982
 
##                print >> sys.stderr, parent[sect_name] is this_section
 
1364
##                sys.stderr.write(parent[sect_name] is this_section)
 
1365
##                sys.stderr.write('\n')
983
1366
                continue
984
1367
            #
985
1368
            # it's not a section marker,
986
1369
            # so it should be a valid ``key = value`` line
987
1370
            mat = self._keyword.match(line)
988
 
##            print >> sys.stderr, sline, mat
 
1371
##            sys.stderr.write('%s %s\n' % (sline, mat))
989
1372
            if mat is not None:
990
1373
                # is a keyword value
991
1374
                # value will include any inline comment
1012
1395
                            ParseError, infile, cur_index)
1013
1396
                        continue
1014
1397
                #
1015
 
##                print >> sys.stderr, sline
 
1398
##                sys.stderr.write(sline)
 
1399
##                sys.stderr.write('\n')
1016
1400
                key = self._unquote(key)
1017
 
                if this_section.has_key(key):
 
1401
                if key in this_section:
1018
1402
                    self._handle_error(
1019
1403
                        'Duplicate keyword name at line %s.',
1020
1404
                        DuplicateError, infile, cur_index)
1021
1405
                    continue
1022
1406
                # add the key
1023
 
##                print >> sys.stderr, this_section.name
 
1407
##                sys.stderr.write(this_section.name + '\n')
1024
1408
                this_section[key] = value
1025
1409
                this_section.inline_comments[key] = comment
1026
1410
                this_section.comments[key] = comment_list
1027
 
##                print >> sys.stderr, key, this_section[key]
 
1411
##                sys.stderr.write('%s %s\n' % (key, this_section[key]))
1028
1412
##                if this_section.name is not None:
1029
 
##                    print >> sys.stderr, this_section
1030
 
##                    print >> sys.stderr, this_section.parent
1031
 
##                    print >> sys.stderr, this_section.parent[this_section.name]
 
1413
##                    sys.stderr.write(this_section + '\n')
 
1414
##                    sys.stderr.write(this_section.parent + '\n')
 
1415
##                    sys.stderr.write(this_section.parent[this_section.name])
 
1416
##                    sys.stderr.write('\n')
1032
1417
                continue
1033
1418
            #
1034
1419
            # it neither matched as a keyword
1040
1425
            # no indentation used, set the type accordingly
1041
1426
            self.indent_type = ''
1042
1427
        # preserve the final comment
1043
 
        self.final_comment = comment_list
 
1428
        if not self and not self.initial_comment:
 
1429
            self.initial_comment = comment_list
 
1430
        else:
 
1431
            self.final_comment = comment_list
1044
1432
 
1045
1433
    def _match_depth(self, sect, depth):
1046
1434
        """
1068
1456
        The error will have occured at ``cur_index``
1069
1457
        """
1070
1458
        line = infile[cur_index]
 
1459
        cur_index += 1
1071
1460
        message = text % cur_index
1072
1461
        error = ErrorClass(message, cur_index, line)
1073
1462
        if self.raise_errors:
1095
1484
        Recursively quote members of a list and return a comma joined list.
1096
1485
        Multiline is ``False`` for lists.
1097
1486
        Obey list syntax for empty and single member lists.
 
1487
        
 
1488
        If ``list_values=False`` then the value is only quoted if it contains
 
1489
        a ``\n`` (is multiline).
1098
1490
        """
1099
1491
        if isinstance(value, (list, tuple)):
1100
1492
            if not value:
1101
1493
                return ','
1102
1494
            elif len(value) == 1:
1103
1495
                return self._quote(value[0], multiline=False) + ','
1104
 
            return ','.join([self._quote(val, multiline=False)
 
1496
            return ', '.join([self._quote(val, multiline=False)
1105
1497
                for val in value])
1106
1498
        if not isinstance(value, StringTypes):
1107
1499
            if self.stringify:
1116
1508
        tdquot = "'''%s'''"
1117
1509
        if not value:
1118
1510
            return '""'
1119
 
        if not (multiline and
 
1511
        if (not self.list_values and '\n' not in value) or not (multiline and
1120
1512
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
 
1513
            if not self.list_values:
 
1514
                # we don't quote if ``list_values=False``
 
1515
                quot = noquot
1121
1516
            # for normal values either single or double quotes will do
1122
 
            if '\n' in value:
 
1517
            elif '\n' in value:
 
1518
                # will only happen if multiline is off - e.g. '\n' in key
1123
1519
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1124
1520
                    value)
1125
 
            if ((value[0] not in wspace_plus) and
 
1521
            elif ((value[0] not in wspace_plus) and
1126
1522
                    (value[-1] not in wspace_plus) and
1127
1523
                    (',' not in value)):
1128
1524
                quot = noquot
1244
1640
            if mat is None:
1245
1641
                raise SyntaxError
1246
1642
            (value, comment) = mat.groups()
1247
 
            # FIXME: unquoting here can be a source of error
1248
 
            return (self._unquote(value), comment)
 
1643
            # NOTE: we don't unquote here
 
1644
            return (value, comment)
1249
1645
        mat = self._valueexp.match(value)
1250
1646
        if mat is None:
1251
1647
            # the value is badly constructed, probably badly quoted,
1355
1751
        for entry in configspec.sections:
1356
1752
            if entry == '__many__':
1357
1753
                continue
1358
 
            if not section.has_key(entry):
 
1754
            if entry not in section:
1359
1755
                section[entry] = {}
1360
1756
            self._set_configspec_value(configspec[entry], section[entry])
1361
1757
 
1386
1782
        #
1387
1783
        section.configspec = scalars
1388
1784
        for entry in sections:
1389
 
            if not section.has_key(entry):
 
1785
            if entry not in section:
1390
1786
                section[entry] = {}
1391
1787
            self._handle_repeat(section[entry], sections[entry])
1392
1788
 
1393
1789
    def _write_line(self, indent_string, entry, this_entry, comment):
1394
1790
        """Write an individual line, for the write method"""
1395
 
        return '%s%s = %s%s' % (
 
1791
        # NOTE: the calls to self._quote here handles non-StringType values.
 
1792
        return '%s%s%s%s%s' % (
1396
1793
            indent_string,
1397
 
            self._quote(entry, multiline=False),
1398
 
            self._quote(this_entry),
1399
 
            comment)
 
1794
            self._decode_element(self._quote(entry, multiline=False)),
 
1795
            self._a_to_u(' = '),
 
1796
            self._decode_element(self._quote(this_entry)),
 
1797
            self._decode_element(comment))
1400
1798
 
1401
1799
    def _write_marker(self, indent_string, depth, entry, comment):
1402
1800
        """Write a section marker line"""
1403
1801
        return '%s%s%s%s%s' % (
1404
1802
            indent_string,
1405
 
            '[' * depth,
1406
 
            self._quote(entry, multiline=False),
1407
 
            ']' * depth,
1408
 
            comment)
 
1803
            self._a_to_u('[' * depth),
 
1804
            self._quote(self._decode_element(entry), multiline=False),
 
1805
            self._a_to_u(']' * depth),
 
1806
            self._decode_element(comment))
1409
1807
 
1410
1808
    def _handle_comment(self, comment):
1411
1809
        """
1437
1835
        if not comment:
1438
1836
            return ''
1439
1837
        if self.indent_type == '\t':
1440
 
            start = '\t'
 
1838
            start = self._a_to_u('\t')
1441
1839
        else:
1442
 
            start = ' ' * NUM_INDENT_SPACES
 
1840
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1443
1841
        if not comment.startswith('#'):
1444
 
            start += '# '
 
1842
            start += _a_to_u('# ')
1445
1843
        return (start + comment)
1446
1844
 
1447
1845
    def _compute_indent_string(self, depth):
1459
1857
 
1460
1858
    # Public methods
1461
1859
 
1462
 
    def write(self, section=None):
 
1860
    def write(self, outfile=None, section=None):
1463
1861
        """
1464
1862
        Write the current ConfigObj as a file
1465
1863
        
1488
1886
        >>> a.write()
1489
1887
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
1490
1888
        """
1491
 
        int_val = 'test'
1492
1889
        if self.indent_type is None:
1493
1890
            # this can be true if initialised from a dictionary
1494
1891
            self.indent_type = DEFAULT_INDENT_TYPE
1495
1892
        #
1496
1893
        out = []
1497
 
        return_list = True
 
1894
        cs = self._a_to_u('#')
 
1895
        csp = self._a_to_u('# ')
1498
1896
        if section is None:
1499
1897
            int_val = self.interpolation
1500
1898
            self.interpolation = False
1501
1899
            section = self
1502
 
            return_list = False
1503
1900
            for line in self.initial_comment:
 
1901
                line = self._decode_element(line)
1504
1902
                stripped_line = line.strip()
1505
 
                if stripped_line and not stripped_line.startswith('#'):
1506
 
                    line = '# ' + line
 
1903
                if stripped_line and not stripped_line.startswith(cs):
 
1904
                    line = csp + line
1507
1905
                out.append(line)
1508
1906
        #
1509
 
        indent_string = self._compute_indent_string(section.depth)
 
1907
        indent_string = self._a_to_u(
 
1908
            self._compute_indent_string(section.depth))
1510
1909
        for entry in (section.scalars + section.sections):
1511
1910
            if entry in section.defaults:
1512
1911
                # don't write out default values
1513
1912
                continue
1514
1913
            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
 
1914
                comment_line = self._decode_element(comment_line.lstrip())
 
1915
                if comment_line and not comment_line.startswith(cs):
 
1916
                    comment_line = csp + comment_line
1518
1917
                out.append(indent_string + comment_line)
1519
1918
            this_entry = section[entry]
1520
1919
            comment = self._handle_comment(section.inline_comments[entry])
1526
1925
                    this_entry.depth,
1527
1926
                    entry,
1528
1927
                    comment))
1529
 
                out.extend(self.write(this_entry))
 
1928
                out.extend(self.write(section=this_entry))
1530
1929
            else:
1531
1930
                out.append(self._write_line(
1532
1931
                    indent_string,
1534
1933
                    this_entry,
1535
1934
                    comment))
1536
1935
        #
1537
 
        if not return_list:
 
1936
        if section is self:
1538
1937
            for line in self.final_comment:
 
1938
                line = self._decode_element(line)
1539
1939
                stripped_line = line.strip()
1540
 
                if stripped_line and not stripped_line.startswith('#'):
1541
 
                    line = '# ' + line
 
1940
                if stripped_line and not stripped_line.startswith(cs):
 
1941
                    line = csp + line
1542
1942
                out.append(line)
1543
 
        #
1544
 
        if int_val != 'test':
1545
1943
            self.interpolation = int_val
1546
1944
        #
1547
 
        if (return_list) or (self.filename is None):
1548
 
            return out
1549
 
        #
1550
 
        if isinstance(self.filename, StringTypes):
 
1945
        if section is not self:
 
1946
            return out
 
1947
        #
 
1948
        if (self.filename is None) and (outfile is None):
 
1949
            # output a list of lines
 
1950
            # might need to encode
 
1951
            # NOTE: This will *screw* UTF16, each line will start with the BOM
 
1952
            if self.encoding:
 
1953
                out = [l.encode(self.encoding) for l in out]
 
1954
            if (self.BOM and ((self.encoding is None) or
 
1955
                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
1956
                # Add the UTF8 BOM
 
1957
                if not out:
 
1958
                    out.append('')
 
1959
                out[0] = BOM_UTF8 + out[0]
 
1960
            return out
 
1961
        #
 
1962
        # Turn the list to a string, joined with correct newlines
 
1963
        output = (self._a_to_u(self.newlines or os.linesep)
 
1964
            ).join(out)
 
1965
        if self.encoding:
 
1966
            output = output.encode(self.encoding)
 
1967
        if (self.BOM and ((self.encoding is None) or
 
1968
            (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
1969
            # Add the UTF8 BOM
 
1970
            output = BOM_UTF8 + output
 
1971
        if outfile is not None:
 
1972
            outfile.write(output)
 
1973
        else:
1551
1974
            h = open(self.filename, 'w')
1552
 
            h.write(self.BOM or '')
1553
 
            h.write('\n'.join(out))
 
1975
            h.write(output)
1554
1976
            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
1977
 
1562
 
    def validate(self, validator, section=None):
 
1978
    def validate(self, validator, preserve_errors=False, section=None):
1563
1979
        """
1564
1980
        Test the ConfigObj against a configspec.
1565
1981
        
1581
1997
        In addition, it converts the values from strings to their native
1582
1998
        types if their checks pass (and ``stringify`` is set).
1583
1999
        
 
2000
        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
 
2001
        of a marking a fail with a ``False``, it will preserve the actual
 
2002
        exception object. This can contain info about the reason for failure.
 
2003
        For example the ``VdtValueTooSmallError`` indeicates that the value
 
2004
        supplied was too small. If a value (or section) is missing it will
 
2005
        still be marked as ``False``.
 
2006
        
 
2007
        You must have the validate module to use ``preserve_errors=True``.
 
2008
        
 
2009
        You can then use the ``flatten_errors`` function to turn your nested
 
2010
        results dictionary into a flattened list of failures - useful for
 
2011
        displaying meaningful error messages.
 
2012
        
1584
2013
        >>> try:
1585
2014
        ...     from validate import Validator
1586
2015
        ... except ImportError:
1587
 
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
 
2016
        ...     sys.stderr.write('Cannot import the Validator object, skipping the related tests\n')
1588
2017
        ... else:
1589
2018
        ...     config = '''
1590
2019
        ...     test1=40
1603
2032
        ...             test4=5.0
1604
2033
        ... '''.split('\\n')
1605
2034
        ...     configspec = '''
1606
 
        ...     test1='integer(30,50)'
1607
 
        ...     test2='string'
1608
 
        ...     test3='integer'
1609
 
        ...     test4='float(6.0)'
 
2035
        ...     test1= integer(30,50)
 
2036
        ...     test2= string
 
2037
        ...     test3=integer
 
2038
        ...     test4=float(6.0)
1610
2039
        ...     [section ]
1611
 
        ...         test1='integer(30,50)'
1612
 
        ...         test2='string'
1613
 
        ...         test3='integer'
1614
 
        ...         test4='float(6.0)'
 
2040
        ...         test1=integer(30,50)
 
2041
        ...         test2=string
 
2042
        ...         test3=integer
 
2043
        ...         test4=float(6.0)
1615
2044
        ...         [[sub section]]
1616
 
        ...             test1='integer(30,50)'
1617
 
        ...             test2='string'
1618
 
        ...             test3='integer'
1619
 
        ...             test4='float(6.0)'
 
2045
        ...             test1=integer(30,50)
 
2046
        ...             test2=string
 
2047
        ...             test3=integer
 
2048
        ...             test4=float(6.0)
1620
2049
        ...     '''.split('\\n')
1621
2050
        ...     val = Validator()
1622
2051
        ...     c1 = ConfigObj(config, configspec=configspec)
1670
2099
        >>> val_res == {'key2': True, 'section': True, 'key': False}
1671
2100
        1
1672
2101
        >>> 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)'
 
2102
        ...     test1=integer(30,50, default=40)
 
2103
        ...     test2=string(default="hello")
 
2104
        ...     test3=integer(default=3)
 
2105
        ...     test4=float(6.0, default=6.0)
1677
2106
        ...     [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)'
 
2107
        ...         test1=integer(30,50, default=40)
 
2108
        ...         test2=string(default="hello")
 
2109
        ...         test3=integer(default=3)
 
2110
        ...         test4=float(6.0, default=6.0)
1682
2111
        ...         [[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)'
 
2112
        ...             test1=integer(30,50, default=40)
 
2113
        ...             test2=string(default="hello")
 
2114
        ...             test3=integer(default=3)
 
2115
        ...             test4=float(6.0, default=6.0)
1687
2116
        ...     '''.split('\\n')
1688
2117
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1689
2118
        >>> default_test
1926
2355
        1
1927
2356
        
1928
2357
        Test that interpolation is preserved for validated string values.
 
2358
        Also check that interpolation works in configspecs.
1929
2359
        >>> t = ConfigObj()
1930
2360
        >>> t['DEFAULT'] = {}
1931
2361
        >>> t['DEFAULT']['test'] = 'a'
1939
2369
        >>> t.interpolation = False
1940
2370
        >>> t
1941
2371
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 
2372
        >>> specs = [
 
2373
        ...    'interpolated string  = string(default="fuzzy-%(man)s")',
 
2374
        ...    '[DEFAULT]',
 
2375
        ...    'man = wuzzy',
 
2376
        ...    ]
 
2377
        >>> c = ConfigObj(configspec=specs)
 
2378
        >>> c.validate(v)
 
2379
        1
 
2380
        >>> c['interpolated string']
 
2381
        'fuzzy-wuzzy'
1942
2382
        
1943
2383
        FIXME: Above tests will fail if we couldn't import Validator (the ones
1944
2384
        that don't raise errors will produce different output and still fail as
1947
2387
        if section is None:
1948
2388
            if self.configspec is None:
1949
2389
                raise ValueError, 'No configspec supplied.'
 
2390
            if preserve_errors:
 
2391
                if VdtMissingValue is None:
 
2392
                    raise ImportError('Missing validate module.')
1950
2393
            section = self
1951
2394
        #
1952
2395
        spec_section = section.configspec
1974
2417
            try:
1975
2418
                check = validator.check(spec_section[entry],
1976
2419
                                        val,
1977
 
                                        missing=missing)
1978
 
            except validator.baseErrorClass:
1979
 
                out[entry] = False
 
2420
                                        missing=missing
 
2421
                                        )
 
2422
            except validator.baseErrorClass, e:
 
2423
                if not preserve_errors or isinstance(e, VdtMissingValue):
 
2424
                    out[entry] = False
 
2425
                else:
 
2426
                    # preserve the error
 
2427
                    out[entry] = e
 
2428
                    ret_false = False
1980
2429
                ret_true = False
1981
 
            # MIKE: we want to raise all other exceptions, not just print ?
1982
 
##            except Exception, err:
1983
 
##                print err
1984
2430
            else:
1985
2431
                ret_false = False
1986
2432
                out[entry] = True
1990
2436
                    if not self.stringify:
1991
2437
                        if isinstance(check, (list, tuple)):
1992
2438
                            # preserve lists
1993
 
                            check = [str(item) for item in check]
 
2439
                            check = [self._str(item) for item in check]
1994
2440
                        elif missing and check is None:
1995
2441
                            # convert the None from a default to a ''
1996
2442
                            check = ''
1997
2443
                        else:
1998
 
                            check = str(check)
 
2444
                            check = self._str(check)
1999
2445
                    if (check != val) or missing:
2000
2446
                        section[entry] = check
2001
2447
                if missing and entry not in section.defaults:
2002
2448
                    section.defaults.append(entry)
2003
2449
        #
 
2450
        # FIXME: Will this miss missing sections ?
2004
2451
        for entry in section.sections:
2005
 
            check = self.validate(validator, section[entry])
 
2452
            if section is self and entry == 'DEFAULT':
 
2453
                continue
 
2454
            check = self.validate(validator, preserve_errors=preserve_errors,
 
2455
                section=section[entry])
2006
2456
            out[entry] = check
2007
2457
            if check == False:
2008
2458
                ret_true = False
2080
2530
            raise self.baseErrorClass
2081
2531
        return member
2082
2532
 
 
2533
# Check / processing functions for options
 
2534
def flatten_errors(cfg, res, levels=None, results=None):
 
2535
    """
 
2536
    An example function that will turn a nested dictionary of results
 
2537
    (as returned by ``ConfigObj.validate``) into a flat list.
 
2538
    
 
2539
    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
 
2540
    dictionary returned by ``validate``.
 
2541
    
 
2542
    (This is a recursive function, so you shouldn't use the ``levels`` or
 
2543
    ``results`` arguments - they are used by the function.
 
2544
    
 
2545
    Returns a list of keys that failed. Each member of the list is a tuple :
 
2546
    ::
 
2547
    
 
2548
        ([list of sections...], key, result)
 
2549
    
 
2550
    If ``validate`` was called with ``preserve_errors=False`` (the default)
 
2551
    then ``result`` will always be ``False``.
 
2552
 
 
2553
    *list of sections* is a flattened list of sections that the key was found
 
2554
    in.
 
2555
    
 
2556
    If the section was missing then key will be ``None``.
 
2557
    
 
2558
    If the value (or section) was missing then ``result`` will be ``False``.
 
2559
    
 
2560
    If ``validate`` was called with ``preserve_errors=True`` and a value
 
2561
    was present, but failed the check, then ``result`` will be the exception
 
2562
    object returned. You can use this as a string that describes the failure.
 
2563
    
 
2564
    For example *The value "3" is of the wrong type*.
 
2565
    
 
2566
    # FIXME: is the ordering of the output arbitrary ?
 
2567
    >>> import validate
 
2568
    >>> vtor = validate.Validator()
 
2569
    >>> my_ini = '''
 
2570
    ...     option1 = True
 
2571
    ...     [section1]
 
2572
    ...     option1 = True
 
2573
    ...     [section2]
 
2574
    ...     another_option = Probably
 
2575
    ...     [section3]
 
2576
    ...     another_option = True
 
2577
    ...     [[section3b]]
 
2578
    ...     value = 3
 
2579
    ...     value2 = a
 
2580
    ...     value3 = 11
 
2581
    ...     '''
 
2582
    >>> my_cfg = '''
 
2583
    ...     option1 = boolean()
 
2584
    ...     option2 = boolean()
 
2585
    ...     option3 = boolean(default=Bad_value)
 
2586
    ...     [section1]
 
2587
    ...     option1 = boolean()
 
2588
    ...     option2 = boolean()
 
2589
    ...     option3 = boolean(default=Bad_value)
 
2590
    ...     [section2]
 
2591
    ...     another_option = boolean()
 
2592
    ...     [section3]
 
2593
    ...     another_option = boolean()
 
2594
    ...     [[section3b]]
 
2595
    ...     value = integer
 
2596
    ...     value2 = integer
 
2597
    ...     value3 = integer(0, 10)
 
2598
    ...         [[[section3b-sub]]]
 
2599
    ...         value = string
 
2600
    ...     [section4]
 
2601
    ...     another_option = boolean()
 
2602
    ...     '''
 
2603
    >>> cs = my_cfg.split('\\n')
 
2604
    >>> ini = my_ini.split('\\n')
 
2605
    >>> cfg = ConfigObj(ini, configspec=cs)
 
2606
    >>> res = cfg.validate(vtor, preserve_errors=True)
 
2607
    >>> errors = []
 
2608
    >>> for entry in flatten_errors(cfg, res):
 
2609
    ...     section_list, key, error = entry
 
2610
    ...     section_list.insert(0, '[root]')
 
2611
    ...     if key is not None:
 
2612
    ...        section_list.append(key)
 
2613
    ...     else:
 
2614
    ...         section_list.append('[missing]')
 
2615
    ...     section_string = ', '.join(section_list)
 
2616
    ...     errors.append((section_string, ' = ', error))
 
2617
    >>> errors.sort()
 
2618
    >>> for entry in errors:
 
2619
    ...     print entry[0], entry[1], (entry[2] or 0)
 
2620
    [root], option2  =  0
 
2621
    [root], option3  =  the value "Bad_value" is of the wrong type.
 
2622
    [root], section1, option2  =  0
 
2623
    [root], section1, option3  =  the value "Bad_value" is of the wrong type.
 
2624
    [root], section2, another_option  =  the value "Probably" is of the wrong type.
 
2625
    [root], section3, section3b, section3b-sub, [missing]  =  0
 
2626
    [root], section3, section3b, value2  =  the value "a" is of the wrong type.
 
2627
    [root], section3, section3b, value3  =  the value "11" is too big.
 
2628
    [root], section4, [missing]  =  0
 
2629
    """
 
2630
    if levels is None:
 
2631
        # first time called
 
2632
        levels = []
 
2633
        results = []
 
2634
    if res is True:
 
2635
        return results
 
2636
    if res is False:
 
2637
        results.append((levels[:], None, False))
 
2638
        if levels:
 
2639
            levels.pop()
 
2640
        return results
 
2641
    for (key, val) in res.items():
 
2642
        if val == True:
 
2643
            continue
 
2644
        if isinstance(cfg.get(key), dict):
 
2645
            # Go down one level
 
2646
            levels.append(key)
 
2647
            flatten_errors(cfg[key], val, levels, results)
 
2648
            continue
 
2649
        results.append((levels[:], key, val))
 
2650
    #
 
2651
    # Go up one level
 
2652
    if levels:
 
2653
        levels.pop()
 
2654
    #
 
2655
    return results
 
2656
 
 
2657
 
2083
2658
# FIXME: test error code for badly built multiline values
2084
2659
# FIXME: test handling of StringIO
2085
2660
# FIXME: test interpolation with writing
2146
2721
    >>> t2.inline_comments['b'] = ''
2147
2722
    >>> del t2['a']
2148
2723
    >>> assert t2.write() == ['','b = b', '']
 
2724
    
 
2725
    # Test ``list_values=False`` stuff
 
2726
    >>> c = '''
 
2727
    ...     key1 = no quotes
 
2728
    ...     key2 = 'single quotes'
 
2729
    ...     key3 = "double quotes"
 
2730
    ...     key4 = "list", 'with', several, "quotes"
 
2731
    ...     '''
 
2732
    >>> cfg = ConfigObj(c.splitlines(), list_values=False)
 
2733
    >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'", 
 
2734
    ... 'key3': '"double quotes"', 
 
2735
    ... 'key4': '"list", \\'with\\', several, "quotes"'
 
2736
    ... }
 
2737
    1
 
2738
    >>> cfg = ConfigObj(list_values=False)
 
2739
    >>> cfg['key1'] = 'Multiline\\nValue'
 
2740
    >>> cfg['key2'] = '''"Value" with 'quotes' !'''
 
2741
    >>> cfg.write()
 
2742
    ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
 
2743
    >>> cfg.list_values = True
 
2744
    >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
 
2745
    ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
 
2746
    1
 
2747
    
 
2748
    Test flatten_errors:
 
2749
    
 
2750
    >>> from validate import Validator, VdtValueTooSmallError
 
2751
    >>> config = '''
 
2752
    ...     test1=40
 
2753
    ...     test2=hello
 
2754
    ...     test3=3
 
2755
    ...     test4=5.0
 
2756
    ...     [section]
 
2757
    ...         test1=40
 
2758
    ...         test2=hello
 
2759
    ...         test3=3
 
2760
    ...         test4=5.0
 
2761
    ...         [[sub section]]
 
2762
    ...             test1=40
 
2763
    ...             test2=hello
 
2764
    ...             test3=3
 
2765
    ...             test4=5.0
 
2766
    ... '''.split('\\n')
 
2767
    >>> configspec = '''
 
2768
    ...     test1= integer(30,50)
 
2769
    ...     test2= string
 
2770
    ...     test3=integer
 
2771
    ...     test4=float(6.0)
 
2772
    ...     [section ]
 
2773
    ...         test1=integer(30,50)
 
2774
    ...         test2=string
 
2775
    ...         test3=integer
 
2776
    ...         test4=float(6.0)
 
2777
    ...         [[sub section]]
 
2778
    ...             test1=integer(30,50)
 
2779
    ...             test2=string
 
2780
    ...             test3=integer
 
2781
    ...             test4=float(6.0)
 
2782
    ...     '''.split('\\n')
 
2783
    >>> val = Validator()
 
2784
    >>> c1 = ConfigObj(config, configspec=configspec)
 
2785
    >>> res = c1.validate(val)
 
2786
    >>> flatten_errors(c1, res) == [([], 'test4', False), (['section', 
 
2787
    ...     'sub section'], 'test4', False), (['section'], 'test4', False)]
 
2788
    True
 
2789
    >>> res = c1.validate(val, preserve_errors=True)
 
2790
    >>> check = flatten_errors(c1, res)
 
2791
    >>> check[0][:2]
 
2792
    ([], 'test4')
 
2793
    >>> check[1][:2]
 
2794
    (['section', 'sub section'], 'test4')
 
2795
    >>> check[2][:2]
 
2796
    (['section'], 'test4')
 
2797
    >>> for entry in check:
 
2798
    ...     isinstance(entry[2], VdtValueTooSmallError)
 
2799
    ...     print str(entry[2])
 
2800
    True
 
2801
    the value "5.0" is too small.
 
2802
    True
 
2803
    the value "5.0" is too small.
 
2804
    True
 
2805
    the value "5.0" is too small.
 
2806
    
 
2807
    Test unicode handling, BOM, write witha file like object and line endings :
 
2808
    >>> u_base = '''
 
2809
    ... # initial comment
 
2810
    ...     # inital comment 2
 
2811
    ... 
 
2812
    ... test1 = some value
 
2813
    ... # comment
 
2814
    ... test2 = another value    # inline comment
 
2815
    ... # section comment
 
2816
    ... [section]    # inline comment
 
2817
    ...     test = test    # another inline comment
 
2818
    ...     test2 = test2
 
2819
    ... 
 
2820
    ... # final comment
 
2821
    ... # final comment2
 
2822
    ... '''
 
2823
    >>> u = u_base.encode('utf_8').splitlines(True)
 
2824
    >>> u[0] = BOM_UTF8 + u[0]
 
2825
    >>> uc = ConfigObj(u)
 
2826
    >>> uc.encoding = None
 
2827
    >>> uc.BOM == True
 
2828
    1
 
2829
    >>> uc == {'test1': 'some value', 'test2': 'another value',
 
2830
    ... 'section': {'test': 'test', 'test2': 'test2'}}
 
2831
    1
 
2832
    >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
 
2833
    >>> uc.BOM
 
2834
    1
 
2835
    >>> isinstance(uc['test1'], unicode)
 
2836
    1
 
2837
    >>> uc.encoding
 
2838
    'utf_8'
 
2839
    >>> uc.newlines
 
2840
    '\\n'
 
2841
    >>> uc['latin1'] = "This costs lot's of "
 
2842
    >>> a_list = uc.write()
 
2843
    >>> len(a_list)
 
2844
    15
 
2845
    >>> isinstance(a_list[0], str)
 
2846
    1
 
2847
    >>> a_list[0].startswith(BOM_UTF8)
 
2848
    1
 
2849
    >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
 
2850
    >>> uc = ConfigObj(u)
 
2851
    >>> uc.newlines
 
2852
    '\\r\\n'
 
2853
    >>> uc.newlines = '\\r'
 
2854
    >>> from cStringIO import StringIO
 
2855
    >>> file_like = StringIO()
 
2856
    >>> uc.write(file_like)
 
2857
    >>> file_like.seek(0)
 
2858
    >>> uc2 = ConfigObj(file_like)
 
2859
    >>> uc2 == uc
 
2860
    1
 
2861
    >>> uc2.filename is None
 
2862
    1
 
2863
    >>> uc2.newlines == '\\r'
 
2864
    1
2149
2865
    """
2150
2866
 
2151
2867
if __name__ == '__main__':
2235
2951
    BUGS
2236
2952
    ====
2237
2953
    
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)
 
2954
    None known.
2241
2955
    
2242
2956
    TODO
2243
2957
    ====
2244
2958
    
 
2959
    Better support for configuration from multiple files, including tracking
 
2960
    *where* the original file came from and writing changes to the correct
 
2961
    file.
 
2962
    
 
2963
    
 
2964
    Make ``newline`` an option (as well as an attribute) ?
 
2965
    
 
2966
    ``UTF16`` encoded files, when returned as a list of lines, will have the
 
2967
    BOM at the start of every line. Should this be removed from all but the
 
2968
    first line ?
 
2969
    
 
2970
    Option to set warning type for unicode decode ? (Defaults to strict).
 
2971
    
2245
2972
    A method to optionally remove uniform indentation from multiline values.
2246
2973
    (do as an example of using ``walk`` - along with string-escape)
2247
2974
    
 
2975
    Should the results dictionary from validate be an ordered dictionary if
 
2976
    `odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
 
2977
    
 
2978
    Implement a better ``__repr__`` ? (``ConfigObj({})``)
 
2979
    
 
2980
    Implement some of the sequence methods (which include slicing) from the
 
2981
    newer ``odict`` ?
 
2982
    
2248
2983
    INCOMPATIBLE CHANGES
2249
2984
    ====================
2250
2985
    
2304
3039
    ISSUES
2305
3040
    ======
2306
3041
    
 
3042
    ``validate`` doesn't report *extra* values or sections.
 
3043
    
2307
3044
    You can't have a keyword with the same name as a section (in the same
2308
3045
    section). They are both dictionary keys - so they would overlap.
2309
3046
    
 
3047
    ConfigObj doesn't quote and unquote values if ``list_values=False``.
 
3048
    This means that leading or trailing whitespace in values will be lost when
 
3049
    writing. (Unless you manually quote).
 
3050
    
2310
3051
    Interpolation checks first the 'DEFAULT' subsection of the current
2311
3052
    section, next it checks the 'DEFAULT' section of the parent section,
2312
3053
    last it checks the 'DEFAULT' section of the main section.
2325
3066
    Does it matter that we don't support the ':' divider, which is supported
2326
3067
    by ``ConfigParser`` ?
2327
3068
    
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
3069
    The regular expression correctly removes the value -
2336
3070
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
2337
3071
    back quotes (called from ``_handle_value``). What should we do ??
2345
3079
    If the value is unchanged by validation (it's a string) - but other types
2346
3080
    will be.
2347
3081
    
 
3082
    
2348
3083
    List Value Syntax
2349
3084
    =================
2350
3085
    
2375
3110
    CHANGELOG
2376
3111
    =========
2377
3112
    
 
3113
    2006/02/04
 
3114
    ----------
 
3115
    
 
3116
    Removed ``BOM_UTF8`` from ``__all__``.
 
3117
    
 
3118
    The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
 
3119
    *only* ``True`` for the ``UTF16`` encoding.
 
3120
    
 
3121
    File like objects no longer need a ``seek`` attribute.
 
3122
    
 
3123
    ConfigObj no longer keeps a reference to file like objects. Instead the
 
3124
    ``write`` method takes a file like object as an optional argument. (Which
 
3125
    will be used in preference of the ``filename`` attribute if htat exists as
 
3126
    well.)
 
3127
    
 
3128
    Full unicode support added. New options/attributes ``encoding``,
 
3129
    ``default_encoding``.
 
3130
    
 
3131
    utf16 files decoded to unicode.
 
3132
    
 
3133
    If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
 
3134
    written out at the start of the file. (It will normally only be ``True`` if
 
3135
    the utf8 BOM was found when the file was read.)
 
3136
    
 
3137
    File paths are *not* converted to absolute paths, relative paths will
 
3138
    remain relative as the ``filename`` attribute.
 
3139
    
 
3140
    Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
 
3141
    a list of lines.
 
3142
    
 
3143
    2006/01/31
 
3144
    ----------
 
3145
    
 
3146
    Added ``True``, ``False``, and ``enumerate`` if they are not defined.
 
3147
    (``True`` and ``False`` are needed for *early* versions of Python 2.2,
 
3148
    ``enumerate`` is needed for all versions ofPython 2.2)
 
3149
    
 
3150
    Deprecated ``istrue``, replaced it with ``as_bool``.
 
3151
    
 
3152
    Added ``as_int`` and ``as_float``.
 
3153
    
 
3154
    utf8 and utf16 BOM handled in an endian agnostic way.
 
3155
    
 
3156
    2005/12/14
 
3157
    ----------
 
3158
    
 
3159
    Validation no longer done on the 'DEFAULT' section (only in the root
 
3160
    level). This allows interpolation in configspecs.
 
3161
    
 
3162
    Change in validation syntax implemented in validate 0.2.1
 
3163
    
 
3164
    4.1.0
 
3165
    
 
3166
    2005/12/10
 
3167
    ----------
 
3168
    
 
3169
    Added ``merge``, a recursive update.
 
3170
    
 
3171
    Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
 
3172
    example function.
 
3173
    
 
3174
    Thanks to Matthew Brett for suggestions and helping me iron out bugs.
 
3175
    
 
3176
    Fixed bug where a config file is *all* comment, the comment will now be
 
3177
    ``initial_comment`` rather than ``final_comment``.
 
3178
    
 
3179
    2005/12/02
 
3180
    ----------
 
3181
    
 
3182
    Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
 
3183
    
 
3184
    2005/11/04
 
3185
    ----------
 
3186
    
 
3187
    Fixed bug in ``Section.walk`` when transforming names as well as values.
 
3188
    
 
3189
    Added the ``istrue`` method. (Fetches the boolean equivalent of a string
 
3190
    value).
 
3191
    
 
3192
    Fixed ``list_values=False`` - they are now only quoted/unquoted if they
 
3193
    are multiline values.
 
3194
    
 
3195
    List values are written as ``item, item`` rather than ``item,item``.
 
3196
    
 
3197
    4.0.1
 
3198
    
2378
3199
    2005/10/09
2379
3200
    ----------
2380
3201
    
2381
3202
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
2382
3203
    ``interpolation``).
 
3204
 
 
3205
    4.0.0 Final
2383
3206
    
2384
3207
    2005/09/16
2385
3208
    ----------
2410
3233
    2005/09/03
2411
3234
    ----------
2412
3235
    
2413
 
    Fixed bug in ``Section__delitem__`` oops.
 
3236
    Fixed bug in ``Section.__delitem__`` oops.
2414
3237
    
2415
3238
    2005/08/28
2416
3239
    ----------