~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-01-06 01:13:05 UTC
  • mfrom: (1534.1.4 integration)
  • Revision ID: mbp@sourcefrog.net-20060106011305-3772285d84b5cbb4
[merge] robertc

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
#         nico AT tekNico DOT net
6
6
 
7
7
# ConfigObj 4
8
 
# http://www.voidspace.org.uk/python/configobj.html
9
8
 
10
9
# Released subject to the BSD License
11
 
# Please see http://www.voidspace.org.uk/python/license.shtml
 
10
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
12
11
 
13
12
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14
13
# For information about bugfixes, updates and support, please join the
16
15
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17
16
# Comments, suggestions and bug reports welcome.
18
17
 
19
 
from __future__ import generators
20
 
 
21
18
"""
22
19
    >>> z = ConfigObj()
23
20
    >>> z['a'] = 'a'
40
37
 
41
38
import os, re
42
39
from types import StringTypes
43
 
from warnings import warn
44
 
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
45
 
 
46
 
# A dictionary mapping BOM to
47
 
# the encoding to decode with, and what to set the
48
 
# encoding attribute to.
49
 
BOMS = {
50
 
    BOM_UTF8: ('utf_8', None),
51
 
    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
52
 
    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
53
 
    BOM_UTF16: ('utf_16', 'utf_16'),
54
 
    }
55
 
# All legal variants of the BOM codecs.
56
 
# TODO: the list of aliases is not meant to be exhaustive, is there a
57
 
#   better way ?
58
 
BOM_LIST = {
59
 
    'utf_16': 'utf_16',
60
 
    'u16': 'utf_16',
61
 
    'utf16': 'utf_16',
62
 
    'utf-16': 'utf_16',
63
 
    'utf16_be': 'utf16_be',
64
 
    'utf_16_be': 'utf16_be',
65
 
    'utf-16be': 'utf16_be',
66
 
    'utf16_le': 'utf16_le',
67
 
    'utf_16_le': 'utf16_le',
68
 
    'utf-16le': 'utf16_le',
69
 
    'utf_8': 'utf_8',
70
 
    'u8': 'utf_8',
71
 
    'utf': 'utf_8',
72
 
    'utf8': 'utf_8',
73
 
    'utf-8': 'utf_8',
74
 
    }
75
 
 
76
 
# Map of encodings to the BOM to write.
77
 
BOM_SET = {
78
 
    'utf_8': BOM_UTF8,
79
 
    'utf_16': BOM_UTF16,
80
 
    'utf16_be': BOM_UTF16_BE,
81
 
    'utf16_le': BOM_UTF16_LE,
82
 
    None: BOM_UTF8
83
 
    }
84
 
 
85
 
try:
86
 
    from validate import VdtMissingValue
87
 
except ImportError:
88
 
    VdtMissingValue = None
89
 
 
90
 
try:
91
 
    enumerate
92
 
except NameError:
93
 
    def enumerate(obj):
94
 
        """enumerate for Python 2.2."""
95
 
        i = -1
96
 
        for item in obj:
97
 
            i += 1
98
 
            yield i, item
99
 
 
100
 
try:
101
 
    True, False
102
 
except NameError:
103
 
    True, False = 1, 0
104
 
 
105
 
 
106
 
__version__ = '4.2.0beta2'
107
 
 
108
 
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
 
40
 
 
41
# the UTF8 BOM - from codecs module
 
42
BOM_UTF8 = '\xef\xbb\xbf'
 
43
 
 
44
__version__ = '4.0.0'
 
45
 
 
46
__revision__ = '$Id: configobj.py 135 2005-10-12 14:52:28Z fuzzyman $'
109
47
 
110
48
__docformat__ = "restructuredtext en"
111
49
 
112
 
# NOTE: Does it make sense to have the following in __all__ ?
113
 
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
114
 
# NOTE: If used as from configobj import...
115
 
# NOTE: They are effectively read only
116
50
__all__ = (
117
51
    '__version__',
 
52
    'BOM_UTF8',
118
53
    'DEFAULT_INDENT_TYPE',
119
54
    'NUM_INDENT_SPACES',
120
55
    'MAX_INTERPOL_DEPTH',
130
65
    'MissingInterpolationOption',
131
66
    'RepeatSectionError',
132
67
    '__docformat__',
133
 
    'flatten_errors',
134
68
)
135
69
 
136
70
DEFAULT_INDENT_TYPE = ' '
147
81
    'stringify': True,
148
82
    # option may be set to one of ('', ' ', '\t')
149
83
    'indent_type': None,
150
 
    'encoding': None,
151
 
    'default_encoding': None,
152
84
}
153
85
 
154
86
class ConfigObjError(SyntaxError):
264
196
 
265
197
    def __init__(self, parent, depth, main, indict=None, name=None):
266
198
        """
267
 
        * parent is the section above
268
 
        * depth is the depth level of this section
269
 
        * main is the main ConfigObj
270
 
        * indict is a dictionary to initialise the section with
 
199
        parent is the section above
 
200
        depth is the depth level of this section
 
201
        main is the main ConfigObj
 
202
        indict is a dictionary to initialise the section with
271
203
        """
272
204
        if indict is None:
273
205
            indict = {}
352
284
        """
353
285
        if not isinstance(key, StringTypes):
354
286
            raise ValueError, 'The key "%s" is not a string.' % key
 
287
##        if self.depth is None:
 
288
##            self.depth = 0
355
289
        # add the comment
356
 
        if key not in self.comments:
 
290
        if not self.comments.has_key(key):
357
291
            self.comments[key] = []
358
292
            self.inline_comments[key] = ''
359
293
        # remove the entry from defaults
361
295
            self.defaults.remove(key)
362
296
        #
363
297
        if isinstance(value, Section):
364
 
            if key not in self:
 
298
            if not self.has_key(key):
365
299
                self.sections.append(key)
366
300
            dict.__setitem__(self, key, value)
367
301
        elif isinstance(value, dict):
368
302
            # First create the new depth level,
369
303
            # then create the section
370
 
            if key not in self:
 
304
            if not self.has_key(key):
371
305
                self.sections.append(key)
372
306
            new_depth = self.depth + 1
373
307
            dict.__setitem__(
380
314
                    indict=value,
381
315
                    name=key))
382
316
        else:
383
 
            if key not in self:
 
317
            if not self.has_key(key):
384
318
                self.scalars.append(key)
385
319
            if not self.main.stringify:
386
320
                if isinstance(value, StringTypes):
412
346
            return default
413
347
 
414
348
    def update(self, indict):
415
 
        """
416
 
        A version of update that uses our ``__setitem__``.
417
 
        """
 
349
        """A version of update that uses our ``__setitem__``."""
418
350
        for entry in indict:
419
351
            self[entry] = indict[entry]
420
352
 
421
 
 
422
353
    def pop(self, key, *args):
423
354
        """ """
424
355
        val = dict.pop(self, key, *args)
525
456
            newdict[entry] = this_entry
526
457
        return newdict
527
458
 
528
 
    def merge(self, indict):
529
 
        """
530
 
        A recursive update - useful for merging config files.
531
 
        
532
 
        >>> a = '''[section1]
533
 
        ...     option1 = True
534
 
        ...     [[subsection]]
535
 
        ...     more_options = False
536
 
        ...     # end of file'''.splitlines()
537
 
        >>> b = '''# File is user.ini
538
 
        ...     [section1]
539
 
        ...     option1 = False
540
 
        ...     # end of file'''.splitlines()
541
 
        >>> c1 = ConfigObj(b)
542
 
        >>> c2 = ConfigObj(a)
543
 
        >>> c2.merge(c1)
544
 
        >>> c2
545
 
        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
546
 
        """
547
 
        for key, val in indict.items():
548
 
            if (key in self and isinstance(self[key], dict) and
549
 
                                isinstance(val, dict)):
550
 
                self[key].merge(val)
551
 
            else:   
552
 
                self[key] = val
553
 
 
554
459
    def rename(self, oldkey, newkey):
555
460
        """
556
461
        Change a keyname to another, without changing position in sequence.
602
507
        return value when called on the whole subsection has to be discarded.
603
508
        
604
509
        See  the encode and decode methods for examples, including functions.
605
 
        
606
 
        .. caution::
607
 
        
608
 
            You can use ``walk`` to transform the names of members of a section
609
 
            but you mustn't add or delete members.
610
 
        
611
 
        >>> config = '''[XXXXsection]
612
 
        ... XXXXkey = XXXXvalue'''.splitlines()
613
 
        >>> cfg = ConfigObj(config)
614
 
        >>> cfg
615
 
        {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
616
 
        >>> def transform(section, key):
617
 
        ...     val = section[key]
618
 
        ...     newkey = key.replace('XXXX', 'CLIENT1')
619
 
        ...     section.rename(key, newkey)
620
 
        ...     if isinstance(val, (tuple, list, dict)):
621
 
        ...         pass
622
 
        ...     else:
623
 
        ...         val = val.replace('XXXX', 'CLIENT1')
624
 
        ...         section[newkey] = val
625
 
        >>> cfg.walk(transform, call_on_sections=True)
626
 
        {'CLIENT1section': {'CLIENT1key': None}}
627
 
        >>> cfg
628
 
        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
629
510
        """
630
511
        out = {}
631
512
        # scalars first
632
 
        for i in range(len(self.scalars)):
633
 
            entry = self.scalars[i]
 
513
        for entry in self.scalars[:]:
634
514
            try:
635
 
                val = function(self, entry, **keywargs)
636
 
                # bound again in case name has changed
637
 
                entry = self.scalars[i]
638
 
                out[entry] = val
 
515
                out[entry] = function(self, entry, **keywargs)
639
516
            except Exception:
640
517
                if raise_errors:
641
518
                    raise
642
519
                else:
643
 
                    entry = self.scalars[i]
644
520
                    out[entry] = False
645
521
        # then sections
646
 
        for i in range(len(self.sections)):
647
 
            entry = self.sections[i]
 
522
        for entry in self.sections[:]:
648
523
            if call_on_sections:
649
524
                try:
650
525
                    function(self, entry, **keywargs)
652
527
                    if raise_errors:
653
528
                        raise
654
529
                    else:
655
 
                        entry = self.sections[i]
656
530
                        out[entry] = False
657
 
                # bound again in case name has changed
658
 
                entry = self.sections[i]
659
531
            # previous result is discarded
660
532
            out[entry] = self[entry].walk(
661
533
                function,
678
550
        >>> def testuni(val):
679
551
        ...     for entry in val:
680
552
        ...         if not isinstance(entry, unicode):
681
 
        ...             sys.stderr.write(type(entry))
682
 
        ...             sys.stderr.write('\n')
 
553
        ...             print >> sys.stderr, type(entry)
683
554
        ...             raise AssertionError, 'decode failed.'
684
555
        ...         if isinstance(val[entry], dict):
685
556
        ...             testuni(val[entry])
731
602
            section[newkey] = newval
732
603
        self.walk(encode, call_on_sections=True)
733
604
 
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
 
 
826
605
class ConfigObj(Section):
827
606
    """
828
607
    An object to read, create, and write config files.
943
722
        '"""': (_single_line_double, _multi_line_double),
944
723
    }
945
724
 
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
 
 
954
725
    def __init__(self, infile=None, options=None, **kwargs):
955
726
        """
956
727
        Parse or create a config file object.
970
741
        for entry in options.keys():
971
742
            if entry not in defaults.keys():
972
743
                raise TypeError, 'Unrecognised option "%s".' % entry
973
 
        # TODO: check the values too.
974
 
        #
975
 
        # Add any explicit options to the defaults
 
744
        # TODO: check the values too
 
745
        # add the explicit options to the defaults
976
746
        defaults.update(options)
977
747
        #
978
748
        # initialise a few variables
979
 
        self.filename = None
980
749
        self._errors = []
981
750
        self.raise_errors = defaults['raise_errors']
982
751
        self.interpolation = defaults['interpolation']
985
754
        self.file_error = defaults['file_error']
986
755
        self.stringify = defaults['stringify']
987
756
        self.indent_type = defaults['indent_type']
988
 
        self.encoding = defaults['encoding']
989
 
        self.default_encoding = defaults['default_encoding']
990
 
        self.BOM = False
991
 
        self.newlines = None
 
757
        # used by the write method
 
758
        self.BOM = None
992
759
        #
993
760
        self.initial_comment = []
994
761
        self.final_comment = []
995
762
        #
996
763
        if isinstance(infile, StringTypes):
997
 
            self.filename = infile
998
 
            if os.path.isfile(infile):
999
 
                infile = open(infile).read() or []
 
764
            self.filename = os.path.abspath(infile)
 
765
            if os.path.isfile(self.filename):
 
766
                infile = open(self.filename).readlines()
1000
767
            elif self.file_error:
1001
768
                # raise an error if the file doesn't exist
1002
769
                raise IOError, 'Config file not found: "%s".' % self.filename
1005
772
                if self.create_empty:
1006
773
                    # this is a good test that the filename specified
1007
774
                    # isn't impossible - like on a non existent device
1008
 
                    h = open(infile, 'w')
 
775
                    h = open(self.filename)
1009
776
                    h.write('')
1010
777
                    h.close()
1011
778
                infile = []
1012
779
        elif isinstance(infile, (list, tuple)):
1013
 
            infile = list(infile)
 
780
            self.filename = None
1014
781
        elif isinstance(infile, dict):
1015
782
            # initialise self
1016
783
            # the Section class handles creating subsections
1019
786
                infile = infile.dict()
1020
787
            for entry in infile:
1021
788
                self[entry] = infile[entry]
 
789
            self.filename = None
1022
790
            del self._errors
1023
791
            if defaults['configspec'] is not None:
1024
792
                self._handle_configspec(defaults['configspec'])
1025
793
            else:
1026
794
                self.configspec = None
1027
795
            return
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
 
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)
1033
802
        else:
1034
803
            raise TypeError, ('infile must be a filename,'
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]
 
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
1053
816
        #
1054
817
        self._parse(infile)
1055
818
        # if we had any errors, now is the time to raise them
1069
832
        else:
1070
833
            self._handle_configspec(defaults['configspec'])
1071
834
 
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
 
 
1216
835
    def _parse(self, infile):
1217
836
        """
1218
837
        Actually parse the config file
1309
928
            reset_comment = True
1310
929
            # first we check if it's a section marker
1311
930
            mat = self._sectionmarker.match(line)
1312
 
##            sys.stderr.write('%s %s\n' % (sline, mat))
 
931
##            print >> sys.stderr, sline, mat
1313
932
            if mat is not None:
1314
933
                # is a section line
1315
934
                (indent, sect_open, sect_name, sect_close, comment) = (
1345
964
                        NestingError, infile, cur_index)
1346
965
                #
1347
966
                sect_name = self._unquote(sect_name)
1348
 
                if sect_name in parent:
1349
 
##                    sys.stderr.write(sect_name)
1350
 
##                    sys.stderr.write('\n')
 
967
                if parent.has_key(sect_name):
 
968
##                    print >> sys.stderr, sect_name
1351
969
                    self._handle_error(
1352
970
                        'Duplicate section name at line %s.',
1353
971
                        DuplicateError, infile, cur_index)
1361
979
                parent[sect_name] = this_section
1362
980
                parent.inline_comments[sect_name] = comment
1363
981
                parent.comments[sect_name] = comment_list
1364
 
##                sys.stderr.write(parent[sect_name] is this_section)
1365
 
##                sys.stderr.write('\n')
 
982
##                print >> sys.stderr, parent[sect_name] is this_section
1366
983
                continue
1367
984
            #
1368
985
            # it's not a section marker,
1369
986
            # so it should be a valid ``key = value`` line
1370
987
            mat = self._keyword.match(line)
1371
 
##            sys.stderr.write('%s %s\n' % (sline, mat))
 
988
##            print >> sys.stderr, sline, mat
1372
989
            if mat is not None:
1373
990
                # is a keyword value
1374
991
                # value will include any inline comment
1395
1012
                            ParseError, infile, cur_index)
1396
1013
                        continue
1397
1014
                #
1398
 
##                sys.stderr.write(sline)
1399
 
##                sys.stderr.write('\n')
 
1015
##                print >> sys.stderr, sline
1400
1016
                key = self._unquote(key)
1401
 
                if key in this_section:
 
1017
                if this_section.has_key(key):
1402
1018
                    self._handle_error(
1403
1019
                        'Duplicate keyword name at line %s.',
1404
1020
                        DuplicateError, infile, cur_index)
1405
1021
                    continue
1406
1022
                # add the key
1407
 
##                sys.stderr.write(this_section.name + '\n')
 
1023
##                print >> sys.stderr, this_section.name
1408
1024
                this_section[key] = value
1409
1025
                this_section.inline_comments[key] = comment
1410
1026
                this_section.comments[key] = comment_list
1411
 
##                sys.stderr.write('%s %s\n' % (key, this_section[key]))
 
1027
##                print >> sys.stderr, key, this_section[key]
1412
1028
##                if this_section.name is not None:
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')
 
1029
##                    print >> sys.stderr, this_section
 
1030
##                    print >> sys.stderr, this_section.parent
 
1031
##                    print >> sys.stderr, this_section.parent[this_section.name]
1417
1032
                continue
1418
1033
            #
1419
1034
            # it neither matched as a keyword
1425
1040
            # no indentation used, set the type accordingly
1426
1041
            self.indent_type = ''
1427
1042
        # preserve the final comment
1428
 
        if not self and not self.initial_comment:
1429
 
            self.initial_comment = comment_list
1430
 
        else:
1431
 
            self.final_comment = comment_list
 
1043
        self.final_comment = comment_list
1432
1044
 
1433
1045
    def _match_depth(self, sect, depth):
1434
1046
        """
1456
1068
        The error will have occured at ``cur_index``
1457
1069
        """
1458
1070
        line = infile[cur_index]
1459
 
        cur_index += 1
1460
1071
        message = text % cur_index
1461
1072
        error = ErrorClass(message, cur_index, line)
1462
1073
        if self.raise_errors:
1484
1095
        Recursively quote members of a list and return a comma joined list.
1485
1096
        Multiline is ``False`` for lists.
1486
1097
        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).
1490
1098
        """
1491
1099
        if isinstance(value, (list, tuple)):
1492
1100
            if not value:
1493
1101
                return ','
1494
1102
            elif len(value) == 1:
1495
1103
                return self._quote(value[0], multiline=False) + ','
1496
 
            return ', '.join([self._quote(val, multiline=False)
 
1104
            return ','.join([self._quote(val, multiline=False)
1497
1105
                for val in value])
1498
1106
        if not isinstance(value, StringTypes):
1499
1107
            if self.stringify:
1508
1116
        tdquot = "'''%s'''"
1509
1117
        if not value:
1510
1118
            return '""'
1511
 
        if (not self.list_values and '\n' not in value) or not (multiline and
 
1119
        if not (multiline and
1512
1120
                ((("'" 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
1516
1121
            # for normal values either single or double quotes will do
1517
 
            elif '\n' in value:
1518
 
                # will only happen if multiline is off - e.g. '\n' in key
 
1122
            if '\n' in value:
1519
1123
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1520
1124
                    value)
1521
 
            elif ((value[0] not in wspace_plus) and
 
1125
            if ((value[0] not in wspace_plus) and
1522
1126
                    (value[-1] not in wspace_plus) and
1523
1127
                    (',' not in value)):
1524
1128
                quot = noquot
1640
1244
            if mat is None:
1641
1245
                raise SyntaxError
1642
1246
            (value, comment) = mat.groups()
1643
 
            # NOTE: we don't unquote here
1644
 
            return (value, comment)
 
1247
            # FIXME: unquoting here can be a source of error
 
1248
            return (self._unquote(value), comment)
1645
1249
        mat = self._valueexp.match(value)
1646
1250
        if mat is None:
1647
1251
            # the value is badly constructed, probably badly quoted,
1751
1355
        for entry in configspec.sections:
1752
1356
            if entry == '__many__':
1753
1357
                continue
1754
 
            if entry not in section:
 
1358
            if not section.has_key(entry):
1755
1359
                section[entry] = {}
1756
1360
            self._set_configspec_value(configspec[entry], section[entry])
1757
1361
 
1782
1386
        #
1783
1387
        section.configspec = scalars
1784
1388
        for entry in sections:
1785
 
            if entry not in section:
 
1389
            if not section.has_key(entry):
1786
1390
                section[entry] = {}
1787
1391
            self._handle_repeat(section[entry], sections[entry])
1788
1392
 
1789
1393
    def _write_line(self, indent_string, entry, this_entry, comment):
1790
1394
        """Write an individual line, for the write method"""
1791
 
        # NOTE: the calls to self._quote here handles non-StringType values.
1792
 
        return '%s%s%s%s%s' % (
 
1395
        return '%s%s = %s%s' % (
1793
1396
            indent_string,
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))
 
1397
            self._quote(entry, multiline=False),
 
1398
            self._quote(this_entry),
 
1399
            comment)
1798
1400
 
1799
1401
    def _write_marker(self, indent_string, depth, entry, comment):
1800
1402
        """Write a section marker line"""
1801
1403
        return '%s%s%s%s%s' % (
1802
1404
            indent_string,
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))
 
1405
            '[' * depth,
 
1406
            self._quote(entry, multiline=False),
 
1407
            ']' * depth,
 
1408
            comment)
1807
1409
 
1808
1410
    def _handle_comment(self, comment):
1809
1411
        """
1835
1437
        if not comment:
1836
1438
            return ''
1837
1439
        if self.indent_type == '\t':
1838
 
            start = self._a_to_u('\t')
 
1440
            start = '\t'
1839
1441
        else:
1840
 
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
 
1442
            start = ' ' * NUM_INDENT_SPACES
1841
1443
        if not comment.startswith('#'):
1842
 
            start += _a_to_u('# ')
 
1444
            start += '# '
1843
1445
        return (start + comment)
1844
1446
 
1845
1447
    def _compute_indent_string(self, depth):
1857
1459
 
1858
1460
    # Public methods
1859
1461
 
1860
 
    def write(self, outfile=None, section=None):
 
1462
    def write(self, section=None):
1861
1463
        """
1862
1464
        Write the current ConfigObj as a file
1863
1465
        
1886
1488
        >>> a.write()
1887
1489
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
1888
1490
        """
 
1491
        int_val = 'test'
1889
1492
        if self.indent_type is None:
1890
1493
            # this can be true if initialised from a dictionary
1891
1494
            self.indent_type = DEFAULT_INDENT_TYPE
1892
1495
        #
1893
1496
        out = []
1894
 
        cs = self._a_to_u('#')
1895
 
        csp = self._a_to_u('# ')
 
1497
        return_list = True
1896
1498
        if section is None:
1897
1499
            int_val = self.interpolation
1898
1500
            self.interpolation = False
1899
1501
            section = self
 
1502
            return_list = False
1900
1503
            for line in self.initial_comment:
1901
 
                line = self._decode_element(line)
1902
1504
                stripped_line = line.strip()
1903
 
                if stripped_line and not stripped_line.startswith(cs):
1904
 
                    line = csp + line
 
1505
                if stripped_line and not stripped_line.startswith('#'):
 
1506
                    line = '# ' + line
1905
1507
                out.append(line)
1906
1508
        #
1907
 
        indent_string = self._a_to_u(
1908
 
            self._compute_indent_string(section.depth))
 
1509
        indent_string = self._compute_indent_string(section.depth)
1909
1510
        for entry in (section.scalars + section.sections):
1910
1511
            if entry in section.defaults:
1911
1512
                # don't write out default values
1912
1513
                continue
1913
1514
            for comment_line in section.comments[entry]:
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
 
1515
                comment_line = comment_line.lstrip()
 
1516
                if comment_line and not comment_line.startswith('#'):
 
1517
                    comment_line = '#' + comment_line
1917
1518
                out.append(indent_string + comment_line)
1918
1519
            this_entry = section[entry]
1919
1520
            comment = self._handle_comment(section.inline_comments[entry])
1925
1526
                    this_entry.depth,
1926
1527
                    entry,
1927
1528
                    comment))
1928
 
                out.extend(self.write(section=this_entry))
 
1529
                out.extend(self.write(this_entry))
1929
1530
            else:
1930
1531
                out.append(self._write_line(
1931
1532
                    indent_string,
1933
1534
                    this_entry,
1934
1535
                    comment))
1935
1536
        #
1936
 
        if section is self:
 
1537
        if not return_list:
1937
1538
            for line in self.final_comment:
1938
 
                line = self._decode_element(line)
1939
1539
                stripped_line = line.strip()
1940
 
                if stripped_line and not stripped_line.startswith(cs):
1941
 
                    line = csp + line
 
1540
                if stripped_line and not stripped_line.startswith('#'):
 
1541
                    line = '# ' + line
1942
1542
                out.append(line)
 
1543
        #
 
1544
        if int_val != 'test':
1943
1545
            self.interpolation = int_val
1944
1546
        #
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:
 
1547
        if (return_list) or (self.filename is None):
 
1548
            return out
 
1549
        #
 
1550
        if isinstance(self.filename, StringTypes):
1974
1551
            h = open(self.filename, 'w')
1975
 
            h.write(output)
 
1552
            h.write(self.BOM or '')
 
1553
            h.write('\n'.join(out))
1976
1554
            h.close()
 
1555
        else:
 
1556
            self.filename.seek(0)
 
1557
            self.filename.write(self.BOM or '')
 
1558
            self.filename.write('\n'.join(out))
 
1559
            # if we have a stored file object (or StringIO)
 
1560
            # we *don't* close it
1977
1561
 
1978
 
    def validate(self, validator, preserve_errors=False, section=None):
 
1562
    def validate(self, validator, section=None):
1979
1563
        """
1980
1564
        Test the ConfigObj against a configspec.
1981
1565
        
1997
1581
        In addition, it converts the values from strings to their native
1998
1582
        types if their checks pass (and ``stringify`` is set).
1999
1583
        
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
 
        
2013
1584
        >>> try:
2014
1585
        ...     from validate import Validator
2015
1586
        ... except ImportError:
2016
 
        ...     sys.stderr.write('Cannot import the Validator object, skipping the related tests\n')
 
1587
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
2017
1588
        ... else:
2018
1589
        ...     config = '''
2019
1590
        ...     test1=40
2032
1603
        ...             test4=5.0
2033
1604
        ... '''.split('\\n')
2034
1605
        ...     configspec = '''
2035
 
        ...     test1= integer(30,50)
2036
 
        ...     test2= string
2037
 
        ...     test3=integer
2038
 
        ...     test4=float(6.0)
 
1606
        ...     test1='integer(30,50)'
 
1607
        ...     test2='string'
 
1608
        ...     test3='integer'
 
1609
        ...     test4='float(6.0)'
2039
1610
        ...     [section ]
2040
 
        ...         test1=integer(30,50)
2041
 
        ...         test2=string
2042
 
        ...         test3=integer
2043
 
        ...         test4=float(6.0)
 
1611
        ...         test1='integer(30,50)'
 
1612
        ...         test2='string'
 
1613
        ...         test3='integer'
 
1614
        ...         test4='float(6.0)'
2044
1615
        ...         [[sub section]]
2045
 
        ...             test1=integer(30,50)
2046
 
        ...             test2=string
2047
 
        ...             test3=integer
2048
 
        ...             test4=float(6.0)
 
1616
        ...             test1='integer(30,50)'
 
1617
        ...             test2='string'
 
1618
        ...             test3='integer'
 
1619
        ...             test4='float(6.0)'
2049
1620
        ...     '''.split('\\n')
2050
1621
        ...     val = Validator()
2051
1622
        ...     c1 = ConfigObj(config, configspec=configspec)
2099
1670
        >>> val_res == {'key2': True, 'section': True, 'key': False}
2100
1671
        1
2101
1672
        >>> configspec = '''
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)
 
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)'
2106
1677
        ...     [section ]
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)
 
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)'
2111
1682
        ...         [[sub section]]
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)
 
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)'
2116
1687
        ...     '''.split('\\n')
2117
1688
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2118
1689
        >>> default_test
2355
1926
        1
2356
1927
        
2357
1928
        Test that interpolation is preserved for validated string values.
2358
 
        Also check that interpolation works in configspecs.
2359
1929
        >>> t = ConfigObj()
2360
1930
        >>> t['DEFAULT'] = {}
2361
1931
        >>> t['DEFAULT']['test'] = 'a'
2369
1939
        >>> t.interpolation = False
2370
1940
        >>> t
2371
1941
        {'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'
2382
1942
        
2383
1943
        FIXME: Above tests will fail if we couldn't import Validator (the ones
2384
1944
        that don't raise errors will produce different output and still fail as
2387
1947
        if section is None:
2388
1948
            if self.configspec is None:
2389
1949
                raise ValueError, 'No configspec supplied.'
2390
 
            if preserve_errors:
2391
 
                if VdtMissingValue is None:
2392
 
                    raise ImportError('Missing validate module.')
2393
1950
            section = self
2394
1951
        #
2395
1952
        spec_section = section.configspec
2417
1974
            try:
2418
1975
                check = validator.check(spec_section[entry],
2419
1976
                                        val,
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
 
1977
                                        missing=missing)
 
1978
            except validator.baseErrorClass:
 
1979
                out[entry] = False
2429
1980
                ret_true = False
 
1981
            # MIKE: we want to raise all other exceptions, not just print ?
 
1982
##            except Exception, err:
 
1983
##                print err
2430
1984
            else:
2431
1985
                ret_false = False
2432
1986
                out[entry] = True
2436
1990
                    if not self.stringify:
2437
1991
                        if isinstance(check, (list, tuple)):
2438
1992
                            # preserve lists
2439
 
                            check = [self._str(item) for item in check]
 
1993
                            check = [str(item) for item in check]
2440
1994
                        elif missing and check is None:
2441
1995
                            # convert the None from a default to a ''
2442
1996
                            check = ''
2443
1997
                        else:
2444
 
                            check = self._str(check)
 
1998
                            check = str(check)
2445
1999
                    if (check != val) or missing:
2446
2000
                        section[entry] = check
2447
2001
                if missing and entry not in section.defaults:
2448
2002
                    section.defaults.append(entry)
2449
2003
        #
2450
 
        # FIXME: Will this miss missing sections ?
2451
2004
        for entry in section.sections:
2452
 
            if section is self and entry == 'DEFAULT':
2453
 
                continue
2454
 
            check = self.validate(validator, preserve_errors=preserve_errors,
2455
 
                section=section[entry])
 
2005
            check = self.validate(validator, section[entry])
2456
2006
            out[entry] = check
2457
2007
            if check == False:
2458
2008
                ret_true = False
2530
2080
            raise self.baseErrorClass
2531
2081
        return member
2532
2082
 
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
 
 
2658
2083
# FIXME: test error code for badly built multiline values
2659
2084
# FIXME: test handling of StringIO
2660
2085
# FIXME: test interpolation with writing
2721
2146
    >>> t2.inline_comments['b'] = ''
2722
2147
    >>> del t2['a']
2723
2148
    >>> 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
2865
2149
    """
2866
2150
 
2867
2151
if __name__ == '__main__':
2951
2235
    BUGS
2952
2236
    ====
2953
2237
    
2954
 
    None known.
 
2238
    With list values off, ConfigObj can incorrectly unquote values. (This makes
 
2239
    it impossible to use listquote to handle your list values for you - for
 
2240
    nested lists. Not handling quotes at all would be better for this)
2955
2241
    
2956
2242
    TODO
2957
2243
    ====
2958
2244
    
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
 
    
2972
2245
    A method to optionally remove uniform indentation from multiline values.
2973
2246
    (do as an example of using ``walk`` - along with string-escape)
2974
2247
    
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
 
    
2983
2248
    INCOMPATIBLE CHANGES
2984
2249
    ====================
2985
2250
    
3039
2304
    ISSUES
3040
2305
    ======
3041
2306
    
3042
 
    ``validate`` doesn't report *extra* values or sections.
3043
 
    
3044
2307
    You can't have a keyword with the same name as a section (in the same
3045
2308
    section). They are both dictionary keys - so they would overlap.
3046
2309
    
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
 
    
3051
2310
    Interpolation checks first the 'DEFAULT' subsection of the current
3052
2311
    section, next it checks the 'DEFAULT' section of the parent section,
3053
2312
    last it checks the 'DEFAULT' section of the main section.
3066
2325
    Does it matter that we don't support the ':' divider, which is supported
3067
2326
    by ``ConfigParser`` ?
3068
2327
    
 
2328
    Following error with "list_values=False" : ::
 
2329
    
 
2330
        >>> a = ["a='hello', 'goodbye'"]
 
2331
        >>>
 
2332
        >>> c(a, list_values=False)
 
2333
        {'a': "hello', 'goodbye"}
 
2334
    
3069
2335
    The regular expression correctly removes the value -
3070
2336
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
3071
2337
    back quotes (called from ``_handle_value``). What should we do ??
3079
2345
    If the value is unchanged by validation (it's a string) - but other types
3080
2346
    will be.
3081
2347
    
3082
 
    
3083
2348
    List Value Syntax
3084
2349
    =================
3085
2350
    
3110
2375
    CHANGELOG
3111
2376
    =========
3112
2377
    
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
 
    
3199
2378
    2005/10/09
3200
2379
    ----------
3201
2380
    
3202
2381
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3203
2382
    ``interpolation``).
3204
 
 
3205
 
    4.0.0 Final
3206
2383
    
3207
2384
    2005/09/16
3208
2385
    ----------
3233
2410
    2005/09/03
3234
2411
    ----------
3235
2412
    
3236
 
    Fixed bug in ``Section.__delitem__`` oops.
 
2413
    Fixed bug in ``Section__delitem__`` oops.
3237
2414
    
3238
2415
    2005/08/28
3239
2416
    ----------