~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-13 08:12:22 UTC
  • mfrom: (1185.63.5 bzr.patches)
  • Revision ID: mbp@sourcefrog.net-20060113081222-6b572004a2ade0cc
[merge] test_hashcache_raise from Denys

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# configobj.py
 
2
# A config file reader/writer that supports nested sections in config files.
 
3
# Copyright (C) 2005 Michael Foord, Nicola Larosa
 
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
 
5
#         nico AT tekNico DOT net
 
6
 
 
7
# ConfigObj 4
 
8
 
 
9
# Released subject to the BSD License
 
10
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
 
11
 
 
12
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
 
13
# For information about bugfixes, updates and support, please join the
 
14
# ConfigObj mailing list:
 
15
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
 
16
# Comments, suggestions and bug reports welcome.
 
17
 
 
18
"""
 
19
    >>> z = ConfigObj()
 
20
    >>> z['a'] = 'a'
 
21
    >>> z['sect'] = {
 
22
    ...    'subsect': {
 
23
    ...         'a': 'fish',
 
24
    ...         'b': 'wobble',
 
25
    ...     },
 
26
    ...     'member': 'value',
 
27
    ... }
 
28
    >>> x = ConfigObj(z.write())
 
29
    >>> z == x
 
30
    1
 
31
"""
 
32
 
 
33
import sys
 
34
INTP_VER = sys.version_info[:2]
 
35
if INTP_VER < (2, 2):
 
36
    raise RuntimeError("Python v.2.2 or later needed")
 
37
 
 
38
import os, re
 
39
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 $'
 
47
 
 
48
__docformat__ = "restructuredtext en"
 
49
 
 
50
__all__ = (
 
51
    '__version__',
 
52
    'BOM_UTF8',
 
53
    'DEFAULT_INDENT_TYPE',
 
54
    'NUM_INDENT_SPACES',
 
55
    'MAX_INTERPOL_DEPTH',
 
56
    'ConfigObjError',
 
57
    'NestingError',
 
58
    'ParseError',
 
59
    'DuplicateError',
 
60
    'ConfigspecError',
 
61
    'ConfigObj',
 
62
    'SimpleVal',
 
63
    'InterpolationError',
 
64
    'InterpolationDepthError',
 
65
    'MissingInterpolationOption',
 
66
    'RepeatSectionError',
 
67
    '__docformat__',
 
68
)
 
69
 
 
70
DEFAULT_INDENT_TYPE = ' '
 
71
NUM_INDENT_SPACES = 4
 
72
MAX_INTERPOL_DEPTH = 10
 
73
 
 
74
OPTION_DEFAULTS = {
 
75
    'interpolation': True,
 
76
    'raise_errors': False,
 
77
    'list_values': True,
 
78
    'create_empty': False,
 
79
    'file_error': False,
 
80
    'configspec': None,
 
81
    'stringify': True,
 
82
    # option may be set to one of ('', ' ', '\t')
 
83
    'indent_type': None,
 
84
}
 
85
 
 
86
class ConfigObjError(SyntaxError):
 
87
    """
 
88
    This is the base class for all errors that ConfigObj raises.
 
89
    It is a subclass of SyntaxError.
 
90
    
 
91
    >>> raise ConfigObjError
 
92
    Traceback (most recent call last):
 
93
    ConfigObjError
 
94
    """
 
95
    def __init__(self, message='', line_number=None, line=''):
 
96
        self.line = line
 
97
        self.line_number = line_number
 
98
        self.message = message
 
99
        SyntaxError.__init__(self, message)
 
100
 
 
101
class NestingError(ConfigObjError):
 
102
    """
 
103
    This error indicates a level of nesting that doesn't match.
 
104
    
 
105
    >>> raise NestingError
 
106
    Traceback (most recent call last):
 
107
    NestingError
 
108
    """
 
109
 
 
110
class ParseError(ConfigObjError):
 
111
    """
 
112
    This error indicates that a line is badly written.
 
113
    It is neither a valid ``key = value`` line,
 
114
    nor a valid section marker line.
 
115
    
 
116
    >>> raise ParseError
 
117
    Traceback (most recent call last):
 
118
    ParseError
 
119
    """
 
120
 
 
121
class DuplicateError(ConfigObjError):
 
122
    """
 
123
    The keyword or section specified already exists.
 
124
    
 
125
    >>> raise DuplicateError
 
126
    Traceback (most recent call last):
 
127
    DuplicateError
 
128
    """
 
129
 
 
130
class ConfigspecError(ConfigObjError):
 
131
    """
 
132
    An error occured whilst parsing a configspec.
 
133
    
 
134
    >>> raise ConfigspecError
 
135
    Traceback (most recent call last):
 
136
    ConfigspecError
 
137
    """
 
138
 
 
139
class InterpolationError(ConfigObjError):
 
140
    """Base class for the two interpolation errors."""
 
141
 
 
142
class InterpolationDepthError(InterpolationError):
 
143
    """Maximum interpolation depth exceeded in string interpolation."""
 
144
 
 
145
    def __init__(self, option):
 
146
        """
 
147
        >>> raise InterpolationDepthError('yoda')
 
148
        Traceback (most recent call last):
 
149
        InterpolationDepthError: max interpolation depth exceeded in value "yoda".
 
150
        """
 
151
        InterpolationError.__init__(
 
152
            self,
 
153
            'max interpolation depth exceeded in value "%s".' % option)
 
154
 
 
155
class RepeatSectionError(ConfigObjError):
 
156
    """
 
157
    This error indicates additional sections in a section with a
 
158
    ``__many__`` (repeated) section.
 
159
    
 
160
    >>> raise RepeatSectionError
 
161
    Traceback (most recent call last):
 
162
    RepeatSectionError
 
163
    """
 
164
 
 
165
class MissingInterpolationOption(InterpolationError):
 
166
    """A value specified for interpolation was missing."""
 
167
 
 
168
    def __init__(self, option):
 
169
        """
 
170
        >>> raise MissingInterpolationOption('yoda')
 
171
        Traceback (most recent call last):
 
172
        MissingInterpolationOption: missing option "yoda" in interpolation.
 
173
        """
 
174
        InterpolationError.__init__(
 
175
            self,
 
176
            'missing option "%s" in interpolation.' % option)
 
177
 
 
178
class Section(dict):
 
179
    """
 
180
    A dictionary-like object that represents a section in a config file.
 
181
    
 
182
    It does string interpolation if the 'interpolate' attribute
 
183
    of the 'main' object is set to True.
 
184
    
 
185
    Interpolation is tried first from the 'DEFAULT' section of this object,
 
186
    next from the 'DEFAULT' section of the parent, lastly the main object.
 
187
    
 
188
    A Section will behave like an ordered dictionary - following the
 
189
    order of the ``scalars`` and ``sections`` attributes.
 
190
    You can use this to change the order of members.
 
191
    
 
192
    Iteration follows the order: scalars, then sections.
 
193
    """
 
194
 
 
195
    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
 
196
 
 
197
    def __init__(self, parent, depth, main, indict=None, name=None):
 
198
        """
 
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
 
203
        """
 
204
        if indict is None:
 
205
            indict = {}
 
206
        dict.__init__(self)
 
207
        # used for nesting level *and* interpolation
 
208
        self.parent = parent
 
209
        # used for the interpolation attribute
 
210
        self.main = main
 
211
        # level of nesting depth of this Section
 
212
        self.depth = depth
 
213
        # the sequence of scalar values in this Section
 
214
        self.scalars = []
 
215
        # the sequence of sections in this Section
 
216
        self.sections = []
 
217
        # purely for information
 
218
        self.name = name
 
219
        # for comments :-)
 
220
        self.comments = {}
 
221
        self.inline_comments = {}
 
222
        # for the configspec
 
223
        self.configspec = {}
 
224
        # for defaults
 
225
        self.defaults = []
 
226
        #
 
227
        # we do this explicitly so that __setitem__ is used properly
 
228
        # (rather than just passing to ``dict.__init__``)
 
229
        for entry in indict:
 
230
            self[entry] = indict[entry]
 
231
 
 
232
    def _interpolate(self, value):
 
233
        """Nicked from ConfigParser."""
 
234
        depth = MAX_INTERPOL_DEPTH
 
235
        # loop through this until it's done
 
236
        while depth:
 
237
            depth -= 1
 
238
            if value.find("%(") != -1:
 
239
                value = self._KEYCRE.sub(self._interpolation_replace, value)
 
240
            else:
 
241
                break
 
242
        else:
 
243
            raise InterpolationDepthError(value)
 
244
        return value
 
245
 
 
246
    def _interpolation_replace(self, match):
 
247
        """ """
 
248
        s = match.group(1)
 
249
        if s is None:
 
250
            return match.group()
 
251
        else:
 
252
            # switch off interpolation before we try and fetch anything !
 
253
            self.main.interpolation = False
 
254
            # try the 'DEFAULT' member of *this section* first
 
255
            val = self.get('DEFAULT', {}).get(s)
 
256
            # try the 'DEFAULT' member of the *parent section* next
 
257
            if val is None:
 
258
                val = self.parent.get('DEFAULT', {}).get(s)
 
259
            # last, try the 'DEFAULT' member of the *main section*
 
260
            if val is None:
 
261
                val = self.main.get('DEFAULT', {}).get(s)
 
262
            self.main.interpolation = True
 
263
            if val is None:
 
264
                raise MissingInterpolationOption(s)
 
265
            return val
 
266
 
 
267
    def __getitem__(self, key):
 
268
        """Fetch the item and do string interpolation."""
 
269
        val = dict.__getitem__(self, key)
 
270
        if self.main.interpolation and isinstance(val, StringTypes):
 
271
            return self._interpolate(val)
 
272
        return val
 
273
 
 
274
    def __setitem__(self, key, value):
 
275
        """
 
276
        Correctly set a value.
 
277
        
 
278
        Making dictionary values Section instances.
 
279
        (We have to special case 'Section' instances - which are also dicts)
 
280
        
 
281
        Keys must be strings.
 
282
        Values need only be strings (or lists of strings) if
 
283
        ``main.stringify`` is set.
 
284
        """
 
285
        if not isinstance(key, StringTypes):
 
286
            raise ValueError, 'The key "%s" is not a string.' % key
 
287
##        if self.depth is None:
 
288
##            self.depth = 0
 
289
        # add the comment
 
290
        if not self.comments.has_key(key):
 
291
            self.comments[key] = []
 
292
            self.inline_comments[key] = ''
 
293
        # remove the entry from defaults
 
294
        if key in self.defaults:
 
295
            self.defaults.remove(key)
 
296
        #
 
297
        if isinstance(value, Section):
 
298
            if not self.has_key(key):
 
299
                self.sections.append(key)
 
300
            dict.__setitem__(self, key, value)
 
301
        elif isinstance(value, dict):
 
302
            # First create the new depth level,
 
303
            # then create the section
 
304
            if not self.has_key(key):
 
305
                self.sections.append(key)
 
306
            new_depth = self.depth + 1
 
307
            dict.__setitem__(
 
308
                self,
 
309
                key,
 
310
                Section(
 
311
                    self,
 
312
                    new_depth,
 
313
                    self.main,
 
314
                    indict=value,
 
315
                    name=key))
 
316
        else:
 
317
            if not self.has_key(key):
 
318
                self.scalars.append(key)
 
319
            if not self.main.stringify:
 
320
                if isinstance(value, StringTypes):
 
321
                    pass
 
322
                elif isinstance(value, (list, tuple)):
 
323
                    for entry in value:
 
324
                        if not isinstance(entry, StringTypes):
 
325
                            raise TypeError, (
 
326
                                'Value is not a string "%s".' % entry)
 
327
                else:
 
328
                    raise TypeError, 'Value is not a string "%s".' % value
 
329
            dict.__setitem__(self, key, value)
 
330
 
 
331
    def __delitem__(self, key):
 
332
        """Remove items from the sequence when deleting."""
 
333
        dict. __delitem__(self, key)
 
334
        if key in self.scalars:
 
335
            self.scalars.remove(key)
 
336
        else:
 
337
            self.sections.remove(key)
 
338
        del self.comments[key]
 
339
        del self.inline_comments[key]
 
340
 
 
341
    def get(self, key, default=None):
 
342
        """A version of ``get`` that doesn't bypass string interpolation."""
 
343
        try:
 
344
            return self[key]
 
345
        except KeyError:
 
346
            return default
 
347
 
 
348
    def update(self, indict):
 
349
        """A version of update that uses our ``__setitem__``."""
 
350
        for entry in indict:
 
351
            self[entry] = indict[entry]
 
352
 
 
353
    def pop(self, key, *args):
 
354
        """ """
 
355
        val = dict.pop(self, key, *args)
 
356
        if key in self.scalars:
 
357
            del self.comments[key]
 
358
            del self.inline_comments[key]
 
359
            self.scalars.remove(key)
 
360
        elif key in self.sections:
 
361
            del self.comments[key]
 
362
            del self.inline_comments[key]
 
363
            self.sections.remove(key)
 
364
        if self.main.interpolation and isinstance(val, StringTypes):
 
365
            return self._interpolate(val)
 
366
        return val
 
367
 
 
368
    def popitem(self):
 
369
        """Pops the first (key,val)"""
 
370
        sequence = (self.scalars + self.sections)
 
371
        if not sequence:
 
372
            raise KeyError, ": 'popitem(): dictionary is empty'"
 
373
        key = sequence[0]
 
374
        val =  self[key]
 
375
        del self[key]
 
376
        return key, val
 
377
 
 
378
    def clear(self):
 
379
        """
 
380
        A version of clear that also affects scalars/sections
 
381
        Also clears comments and configspec.
 
382
        
 
383
        Leaves other attributes alone :
 
384
            depth/main/parent are not affected
 
385
        """
 
386
        dict.clear(self)
 
387
        self.scalars = []
 
388
        self.sections = []
 
389
        self.comments = {}
 
390
        self.inline_comments = {}
 
391
        self.configspec = {}
 
392
 
 
393
    def setdefault(self, key, default=None):
 
394
        """A version of setdefault that sets sequence if appropriate."""
 
395
        try:
 
396
            return self[key]
 
397
        except KeyError:
 
398
            self[key] = default
 
399
            return self[key]
 
400
 
 
401
    def items(self):
 
402
        """ """
 
403
        return zip((self.scalars + self.sections), self.values())
 
404
 
 
405
    def keys(self):
 
406
        """ """
 
407
        return (self.scalars + self.sections)
 
408
 
 
409
    def values(self):
 
410
        """ """
 
411
        return [self[key] for key in (self.scalars + self.sections)]
 
412
 
 
413
    def iteritems(self):
 
414
        """ """
 
415
        return iter(self.items())
 
416
 
 
417
    def iterkeys(self):
 
418
        """ """
 
419
        return iter((self.scalars + self.sections))
 
420
 
 
421
    __iter__ = iterkeys
 
422
 
 
423
    def itervalues(self):
 
424
        """ """
 
425
        return iter(self.values())
 
426
 
 
427
    def __repr__(self):
 
428
        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
 
429
            for key in (self.scalars + self.sections)])
 
430
 
 
431
    __str__ = __repr__
 
432
 
 
433
    # Extra methods - not in a normal dictionary
 
434
 
 
435
    def dict(self):
 
436
        """
 
437
        Return a deepcopy of self as a dictionary.
 
438
        
 
439
        All members that are ``Section`` instances are recursively turned to
 
440
        ordinary dictionaries - by calling their ``dict`` method.
 
441
        
 
442
        >>> n = a.dict()
 
443
        >>> n == a
 
444
        1
 
445
        >>> n is a
 
446
        0
 
447
        """
 
448
        newdict = {}
 
449
        for entry in self:
 
450
            this_entry = self[entry]
 
451
            if isinstance(this_entry, Section):
 
452
                this_entry = this_entry.dict()
 
453
            elif isinstance(this_entry, (list, tuple)):
 
454
                # create a copy rather than a reference
 
455
                this_entry = list(this_entry)
 
456
            newdict[entry] = this_entry
 
457
        return newdict
 
458
 
 
459
    def rename(self, oldkey, newkey):
 
460
        """
 
461
        Change a keyname to another, without changing position in sequence.
 
462
        
 
463
        Implemented so that transformations can be made on keys,
 
464
        as well as on values. (used by encode and decode)
 
465
        
 
466
        Also renames comments.
 
467
        """
 
468
        if oldkey in self.scalars:
 
469
            the_list = self.scalars
 
470
        elif oldkey in self.sections:
 
471
            the_list = self.sections
 
472
        else:
 
473
            raise KeyError, 'Key "%s" not found.' % oldkey
 
474
        pos = the_list.index(oldkey)
 
475
        #
 
476
        val = self[oldkey]
 
477
        dict.__delitem__(self, oldkey)
 
478
        dict.__setitem__(self, newkey, val)
 
479
        the_list.remove(oldkey)
 
480
        the_list.insert(pos, newkey)
 
481
        comm = self.comments[oldkey]
 
482
        inline_comment = self.inline_comments[oldkey]
 
483
        del self.comments[oldkey]
 
484
        del self.inline_comments[oldkey]
 
485
        self.comments[newkey] = comm
 
486
        self.inline_comments[newkey] = inline_comment
 
487
 
 
488
    def walk(self, function, raise_errors=True,
 
489
            call_on_sections=False, **keywargs):
 
490
        """
 
491
        Walk every member and call a function on the keyword and value.
 
492
        
 
493
        Return a dictionary of the return values
 
494
        
 
495
        If the function raises an exception, raise the errror
 
496
        unless ``raise_errors=False``, in which case set the return value to
 
497
        ``False``.
 
498
        
 
499
        Any unrecognised keyword arguments you pass to walk, will be pased on
 
500
        to the function you pass in.
 
501
        
 
502
        Note: if ``call_on_sections`` is ``True`` then - on encountering a
 
503
        subsection, *first* the function is called for the *whole* subsection,
 
504
        and then recurses into it's members. This means your function must be
 
505
        able to handle strings, dictionaries and lists. This allows you
 
506
        to change the key of subsections as well as for ordinary members. The
 
507
        return value when called on the whole subsection has to be discarded.
 
508
        
 
509
        See  the encode and decode methods for examples, including functions.
 
510
        """
 
511
        out = {}
 
512
        # scalars first
 
513
        for entry in self.scalars[:]:
 
514
            try:
 
515
                out[entry] = function(self, entry, **keywargs)
 
516
            except Exception:
 
517
                if raise_errors:
 
518
                    raise
 
519
                else:
 
520
                    out[entry] = False
 
521
        # then sections
 
522
        for entry in self.sections[:]:
 
523
            if call_on_sections:
 
524
                try:
 
525
                    function(self, entry, **keywargs)
 
526
                except Exception:
 
527
                    if raise_errors:
 
528
                        raise
 
529
                    else:
 
530
                        out[entry] = False
 
531
            # previous result is discarded
 
532
            out[entry] = self[entry].walk(
 
533
                function,
 
534
                raise_errors=raise_errors,
 
535
                call_on_sections=call_on_sections,
 
536
                **keywargs)
 
537
        return out
 
538
 
 
539
    def decode(self, encoding):
 
540
        """
 
541
        Decode all strings and values to unicode, using the specified encoding.
 
542
        
 
543
        Works with subsections and list values.
 
544
        
 
545
        Uses the ``walk`` method.
 
546
        
 
547
        Testing ``encode`` and ``decode``.
 
548
        >>> m = ConfigObj(a)
 
549
        >>> m.decode('ascii')
 
550
        >>> def testuni(val):
 
551
        ...     for entry in val:
 
552
        ...         if not isinstance(entry, unicode):
 
553
        ...             print >> sys.stderr, type(entry)
 
554
        ...             raise AssertionError, 'decode failed.'
 
555
        ...         if isinstance(val[entry], dict):
 
556
        ...             testuni(val[entry])
 
557
        ...         elif not isinstance(val[entry], unicode):
 
558
        ...             raise AssertionError, 'decode failed.'
 
559
        >>> testuni(m)
 
560
        >>> m.encode('ascii')
 
561
        >>> a == m
 
562
        1
 
563
        """
 
564
        def decode(section, key, encoding=encoding):
 
565
            """ """
 
566
            val = section[key]
 
567
            if isinstance(val, (list, tuple)):
 
568
                newval = []
 
569
                for entry in val:
 
570
                    newval.append(entry.decode(encoding))
 
571
            elif isinstance(val, dict):
 
572
                newval = val
 
573
            else:
 
574
                newval = val.decode(encoding)
 
575
            newkey = key.decode(encoding)
 
576
            section.rename(key, newkey)
 
577
            section[newkey] = newval
 
578
        # using ``call_on_sections`` allows us to modify section names
 
579
        self.walk(decode, call_on_sections=True)
 
580
 
 
581
    def encode(self, encoding):
 
582
        """
 
583
        Encode all strings and values from unicode,
 
584
        using the specified encoding.
 
585
        
 
586
        Works with subsections and list values.
 
587
        Uses the ``walk`` method.
 
588
        """
 
589
        def encode(section, key, encoding=encoding):
 
590
            """ """
 
591
            val = section[key]
 
592
            if isinstance(val, (list, tuple)):
 
593
                newval = []
 
594
                for entry in val:
 
595
                    newval.append(entry.encode(encoding))
 
596
            elif isinstance(val, dict):
 
597
                newval = val
 
598
            else:
 
599
                newval = val.encode(encoding)
 
600
            newkey = key.encode(encoding)
 
601
            section.rename(key, newkey)
 
602
            section[newkey] = newval
 
603
        self.walk(encode, call_on_sections=True)
 
604
 
 
605
class ConfigObj(Section):
 
606
    """
 
607
    An object to read, create, and write config files.
 
608
    
 
609
    Testing with duplicate keys and sections.
 
610
    
 
611
    >>> c = '''
 
612
    ... [hello]
 
613
    ... member = value
 
614
    ... [hello again]
 
615
    ... member = value
 
616
    ... [ "hello" ]
 
617
    ... member = value
 
618
    ... '''
 
619
    >>> ConfigObj(c.split('\\n'), raise_errors = True)
 
620
    Traceback (most recent call last):
 
621
    DuplicateError: Duplicate section name at line 5.
 
622
    
 
623
    >>> d = '''
 
624
    ... [hello]
 
625
    ... member = value
 
626
    ... [hello again]
 
627
    ... member1 = value
 
628
    ... member2 = value
 
629
    ... 'member1' = value
 
630
    ... [ "and again" ]
 
631
    ... member = value
 
632
    ... '''
 
633
    >>> ConfigObj(d.split('\\n'), raise_errors = True)
 
634
    Traceback (most recent call last):
 
635
    DuplicateError: Duplicate keyword name at line 6.
 
636
    """
 
637
 
 
638
    _keyword = re.compile(r'''^ # line start
 
639
        (\s*)                   # indentation
 
640
        (                       # keyword
 
641
            (?:".*?")|          # double quotes
 
642
            (?:'.*?')|          # single quotes
 
643
            (?:[^'"=].*?)       # no quotes
 
644
        )
 
645
        \s*=\s*                 # divider
 
646
        (.*)                    # value (including list values and comments)
 
647
        $   # line end
 
648
        ''',
 
649
        re.VERBOSE)
 
650
 
 
651
    _sectionmarker = re.compile(r'''^
 
652
        (\s*)                     # 1: indentation
 
653
        ((?:\[\s*)+)              # 2: section marker open
 
654
        (                         # 3: section name open
 
655
            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
 
656
            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
 
657
            (?:[^'"\s].*?)        # at least one non-space unquoted
 
658
        )                         # section name close
 
659
        ((?:\s*\])+)              # 4: section marker close
 
660
        \s*(\#.*)?                # 5: optional comment
 
661
        $''',
 
662
        re.VERBOSE)
 
663
 
 
664
    # this regexp pulls list values out as a single string
 
665
    # or single values and comments
 
666
    _valueexp = re.compile(r'''^
 
667
        (?:
 
668
            (?:
 
669
                (
 
670
                    (?:
 
671
                        (?:
 
672
                            (?:".*?")|              # double quotes
 
673
                            (?:'.*?')|              # single quotes
 
674
                            (?:[^'",\#][^,\#]*?)       # unquoted
 
675
                        )
 
676
                        \s*,\s*                     # comma
 
677
                    )*      # match all list items ending in a comma (if any)
 
678
                )
 
679
                (
 
680
                    (?:".*?")|                      # double quotes
 
681
                    (?:'.*?')|                      # single quotes
 
682
                    (?:[^'",\#\s][^,]*?)             # unquoted
 
683
                )?          # last item in a list - or string value
 
684
            )|
 
685
            (,)             # alternatively a single comma - empty list
 
686
        )
 
687
        \s*(\#.*)?          # optional comment
 
688
        $''',
 
689
        re.VERBOSE)
 
690
 
 
691
    # use findall to get the members of a list value
 
692
    _listvalueexp = re.compile(r'''
 
693
        (
 
694
            (?:".*?")|          # double quotes
 
695
            (?:'.*?')|          # single quotes
 
696
            (?:[^'",\#].*?)       # unquoted
 
697
        )
 
698
        \s*,\s*                 # comma
 
699
        ''',
 
700
        re.VERBOSE)
 
701
 
 
702
    # this regexp is used for the value
 
703
    # when lists are switched off
 
704
    _nolistvalue = re.compile(r'''^
 
705
        (
 
706
            (?:".*?")|          # double quotes
 
707
            (?:'.*?')|          # single quotes
 
708
            (?:[^'"\#].*?)      # unquoted
 
709
        )
 
710
        \s*(\#.*)?              # optional comment
 
711
        $''',
 
712
        re.VERBOSE)
 
713
 
 
714
    # regexes for finding triple quoted values on one line
 
715
    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
 
716
    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
 
717
    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
 
718
    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
 
719
 
 
720
    _triple_quote = {
 
721
        "'''": (_single_line_single, _multi_line_single),
 
722
        '"""': (_single_line_double, _multi_line_double),
 
723
    }
 
724
 
 
725
    def __init__(self, infile=None, options=None, **kwargs):
 
726
        """
 
727
        Parse or create a config file object.
 
728
        
 
729
        ``ConfigObj(infile=None, options=None, **kwargs)``
 
730
        """
 
731
        if infile is None:
 
732
            infile = []
 
733
        if options is None:
 
734
            options = {}
 
735
        # keyword arguments take precedence over an options dictionary
 
736
        options.update(kwargs)
 
737
        # init the superclass
 
738
        Section.__init__(self, self, 0, self)
 
739
        #
 
740
        defaults = OPTION_DEFAULTS.copy()
 
741
        for entry in options.keys():
 
742
            if entry not in defaults.keys():
 
743
                raise TypeError, 'Unrecognised option "%s".' % entry
 
744
        # TODO: check the values too
 
745
        # add the explicit options to the defaults
 
746
        defaults.update(options)
 
747
        #
 
748
        # initialise a few variables
 
749
        self._errors = []
 
750
        self.raise_errors = defaults['raise_errors']
 
751
        self.interpolation = defaults['interpolation']
 
752
        self.list_values = defaults['list_values']
 
753
        self.create_empty = defaults['create_empty']
 
754
        self.file_error = defaults['file_error']
 
755
        self.stringify = defaults['stringify']
 
756
        self.indent_type = defaults['indent_type']
 
757
        # used by the write method
 
758
        self.BOM = None
 
759
        #
 
760
        self.initial_comment = []
 
761
        self.final_comment = []
 
762
        #
 
763
        if isinstance(infile, StringTypes):
 
764
            self.filename = os.path.abspath(infile)
 
765
            if os.path.isfile(self.filename):
 
766
                infile = open(self.filename).readlines()
 
767
            elif self.file_error:
 
768
                # raise an error if the file doesn't exist
 
769
                raise IOError, 'Config file not found: "%s".' % self.filename
 
770
            else:
 
771
                # file doesn't already exist
 
772
                if self.create_empty:
 
773
                    # this is a good test that the filename specified
 
774
                    # isn't impossible - like on a non existent device
 
775
                    h = open(self.filename)
 
776
                    h.write('')
 
777
                    h.close()
 
778
                infile = []
 
779
        elif isinstance(infile, (list, tuple)):
 
780
            self.filename = None
 
781
        elif isinstance(infile, dict):
 
782
            # initialise self
 
783
            # the Section class handles creating subsections
 
784
            if isinstance(infile, ConfigObj):
 
785
                # get a copy of our ConfigObj
 
786
                infile = infile.dict()
 
787
            for entry in infile:
 
788
                self[entry] = infile[entry]
 
789
            self.filename = None
 
790
            del self._errors
 
791
            if defaults['configspec'] is not None:
 
792
                self._handle_configspec(defaults['configspec'])
 
793
            else:
 
794
                self.configspec = None
 
795
            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)
 
802
        else:
 
803
            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
 
816
        #
 
817
        self._parse(infile)
 
818
        # if we had any errors, now is the time to raise them
 
819
        if self._errors:
 
820
            error = ConfigObjError("Parsing failed.")
 
821
            # set the errors attribute; it's a list of tuples:
 
822
            # (error_type, message, line_number)
 
823
            error.errors = self._errors
 
824
            # set the config attribute
 
825
            error.config = self
 
826
            raise error
 
827
        # delete private attributes
 
828
        del self._errors
 
829
        #
 
830
        if defaults['configspec'] is None:
 
831
            self.configspec = None
 
832
        else:
 
833
            self._handle_configspec(defaults['configspec'])
 
834
 
 
835
    def _parse(self, infile):
 
836
        """
 
837
        Actually parse the config file
 
838
        
 
839
        Testing Interpolation
 
840
        
 
841
        >>> c = ConfigObj()
 
842
        >>> c['DEFAULT'] = {
 
843
        ...     'b': 'goodbye',
 
844
        ...     'userdir': 'c:\\\\home',
 
845
        ...     'c': '%(d)s',
 
846
        ...     'd': '%(c)s'
 
847
        ... }
 
848
        >>> c['section'] = {
 
849
        ...     'a': '%(datadir)s\\\\some path\\\\file.py',
 
850
        ...     'b': '%(userdir)s\\\\some path\\\\file.py',
 
851
        ...     'c': 'Yo %(a)s',
 
852
        ...     'd': '%(not_here)s',
 
853
        ...     'e': '%(c)s',
 
854
        ... }
 
855
        >>> c['section']['DEFAULT'] = {
 
856
        ...     'datadir': 'c:\\\\silly_test',
 
857
        ...     'a': 'hello - %(b)s',
 
858
        ... }
 
859
        >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
 
860
        1
 
861
        >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
 
862
        1
 
863
        >>> c['section']['c'] == 'Yo hello - goodbye'
 
864
        1
 
865
        
 
866
        Switching Interpolation Off
 
867
        
 
868
        >>> c.interpolation = False
 
869
        >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
 
870
        1
 
871
        >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
 
872
        1
 
873
        >>> c['section']['c'] == 'Yo %(a)s'
 
874
        1
 
875
        
 
876
        Testing the interpolation errors.
 
877
        
 
878
        >>> c.interpolation = True
 
879
        >>> c['section']['d']
 
880
        Traceback (most recent call last):
 
881
        MissingInterpolationOption: missing option "not_here" in interpolation.
 
882
        >>> c['section']['e']
 
883
        Traceback (most recent call last):
 
884
        InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
 
885
        
 
886
        Testing our quoting.
 
887
        
 
888
        >>> i._quote('\"""\'\'\'')
 
889
        Traceback (most recent call last):
 
890
        SyntaxError: EOF while scanning triple-quoted string
 
891
        >>> try:
 
892
        ...     i._quote('\\n', multiline=False)
 
893
        ... except ConfigObjError, e:
 
894
        ...    e.msg
 
895
        'Value "\\n" cannot be safely quoted.'
 
896
        >>> k._quote(' "\' ', multiline=False)
 
897
        Traceback (most recent call last):
 
898
        SyntaxError: EOL while scanning single-quoted string
 
899
        
 
900
        Testing with "stringify" off.
 
901
        >>> c.stringify = False
 
902
        >>> c['test'] = 1
 
903
        Traceback (most recent call last):
 
904
        TypeError: Value is not a string "1".
 
905
        """
 
906
        comment_list = []
 
907
        done_start = False
 
908
        this_section = self
 
909
        maxline = len(infile) - 1
 
910
        cur_index = -1
 
911
        reset_comment = False
 
912
        while cur_index < maxline:
 
913
            if reset_comment:
 
914
                comment_list = []
 
915
            cur_index += 1
 
916
            line = infile[cur_index]
 
917
            sline = line.strip()
 
918
            # do we have anything on the line ?
 
919
            if not sline or sline.startswith('#'):
 
920
                reset_comment = False
 
921
                comment_list.append(line)
 
922
                continue
 
923
            if not done_start:
 
924
                # preserve initial comment
 
925
                self.initial_comment = comment_list
 
926
                comment_list = []
 
927
                done_start = True
 
928
            reset_comment = True
 
929
            # first we check if it's a section marker
 
930
            mat = self._sectionmarker.match(line)
 
931
##            print >> sys.stderr, sline, mat
 
932
            if mat is not None:
 
933
                # is a section line
 
934
                (indent, sect_open, sect_name, sect_close, comment) = (
 
935
                    mat.groups())
 
936
                if indent and (self.indent_type is None):
 
937
                    self.indent_type = indent[0]
 
938
                cur_depth = sect_open.count('[')
 
939
                if cur_depth != sect_close.count(']'):
 
940
                    self._handle_error(
 
941
                        "Cannot compute the section depth at line %s.",
 
942
                        NestingError, infile, cur_index)
 
943
                    continue
 
944
                if cur_depth < this_section.depth:
 
945
                    # the new section is dropping back to a previous level
 
946
                    try:
 
947
                        parent = self._match_depth(
 
948
                            this_section,
 
949
                            cur_depth).parent
 
950
                    except SyntaxError:
 
951
                        self._handle_error(
 
952
                            "Cannot compute nesting level at line %s.",
 
953
                            NestingError, infile, cur_index)
 
954
                        continue
 
955
                elif cur_depth == this_section.depth:
 
956
                    # the new section is a sibling of the current section
 
957
                    parent = this_section.parent
 
958
                elif cur_depth == this_section.depth + 1:
 
959
                    # the new section is a child the current section
 
960
                    parent = this_section
 
961
                else:
 
962
                    self._handle_error(
 
963
                        "Section too nested at line %s.",
 
964
                        NestingError, infile, cur_index)
 
965
                #
 
966
                sect_name = self._unquote(sect_name)
 
967
                if parent.has_key(sect_name):
 
968
##                    print >> sys.stderr, sect_name
 
969
                    self._handle_error(
 
970
                        'Duplicate section name at line %s.',
 
971
                        DuplicateError, infile, cur_index)
 
972
                    continue
 
973
                # create the new section
 
974
                this_section = Section(
 
975
                    parent,
 
976
                    cur_depth,
 
977
                    self,
 
978
                    name=sect_name)
 
979
                parent[sect_name] = this_section
 
980
                parent.inline_comments[sect_name] = comment
 
981
                parent.comments[sect_name] = comment_list
 
982
##                print >> sys.stderr, parent[sect_name] is this_section
 
983
                continue
 
984
            #
 
985
            # it's not a section marker,
 
986
            # so it should be a valid ``key = value`` line
 
987
            mat = self._keyword.match(line)
 
988
##            print >> sys.stderr, sline, mat
 
989
            if mat is not None:
 
990
                # is a keyword value
 
991
                # value will include any inline comment
 
992
                (indent, key, value) = mat.groups()
 
993
                if indent and (self.indent_type is None):
 
994
                    self.indent_type = indent[0]
 
995
                # check for a multiline value
 
996
                if value[:3] in ['"""', "'''"]:
 
997
                    try:
 
998
                        (value, comment, cur_index) = self._multiline(
 
999
                            value, infile, cur_index, maxline)
 
1000
                    except SyntaxError:
 
1001
                        self._handle_error(
 
1002
                            'Parse error in value at line %s.',
 
1003
                            ParseError, infile, cur_index)
 
1004
                        continue
 
1005
                else:
 
1006
                    # extract comment and lists
 
1007
                    try:
 
1008
                        (value, comment) = self._handle_value(value)
 
1009
                    except SyntaxError:
 
1010
                        self._handle_error(
 
1011
                            'Parse error in value at line %s.',
 
1012
                            ParseError, infile, cur_index)
 
1013
                        continue
 
1014
                #
 
1015
##                print >> sys.stderr, sline
 
1016
                key = self._unquote(key)
 
1017
                if this_section.has_key(key):
 
1018
                    self._handle_error(
 
1019
                        'Duplicate keyword name at line %s.',
 
1020
                        DuplicateError, infile, cur_index)
 
1021
                    continue
 
1022
                # add the key
 
1023
##                print >> sys.stderr, this_section.name
 
1024
                this_section[key] = value
 
1025
                this_section.inline_comments[key] = comment
 
1026
                this_section.comments[key] = comment_list
 
1027
##                print >> sys.stderr, key, this_section[key]
 
1028
##                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]
 
1032
                continue
 
1033
            #
 
1034
            # it neither matched as a keyword
 
1035
            # or a section marker
 
1036
            self._handle_error(
 
1037
                'Invalid line at line "%s".',
 
1038
                ParseError, infile, cur_index)
 
1039
        if self.indent_type is None:
 
1040
            # no indentation used, set the type accordingly
 
1041
            self.indent_type = ''
 
1042
        # preserve the final comment
 
1043
        self.final_comment = comment_list
 
1044
 
 
1045
    def _match_depth(self, sect, depth):
 
1046
        """
 
1047
        Given a section and a depth level, walk back through the sections
 
1048
        parents to see if the depth level matches a previous section.
 
1049
        
 
1050
        Return a reference to the right section,
 
1051
        or raise a SyntaxError.
 
1052
        """
 
1053
        while depth < sect.depth:
 
1054
            if sect is sect.parent:
 
1055
                # we've reached the top level already
 
1056
                raise SyntaxError
 
1057
            sect = sect.parent
 
1058
        if sect.depth == depth:
 
1059
            return sect
 
1060
        # shouldn't get here
 
1061
        raise SyntaxError
 
1062
 
 
1063
    def _handle_error(self, text, ErrorClass, infile, cur_index):
 
1064
        """
 
1065
        Handle an error according to the error settings.
 
1066
        
 
1067
        Either raise the error or store it.
 
1068
        The error will have occured at ``cur_index``
 
1069
        """
 
1070
        line = infile[cur_index]
 
1071
        message = text % cur_index
 
1072
        error = ErrorClass(message, cur_index, line)
 
1073
        if self.raise_errors:
 
1074
            # raise the error - parsing stops here
 
1075
            raise error
 
1076
        # store the error
 
1077
        # reraise when parsing has finished
 
1078
        self._errors.append(error)
 
1079
 
 
1080
    def _unquote(self, value):
 
1081
        """Return an unquoted version of a value"""
 
1082
        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
 
1083
            value = value[1:-1]
 
1084
        return value
 
1085
 
 
1086
    def _quote(self, value, multiline=True):
 
1087
        """
 
1088
        Return a safely quoted version of a value.
 
1089
        
 
1090
        Raise a ConfigObjError if the value cannot be safely quoted.
 
1091
        If multiline is ``True`` (default) then use triple quotes
 
1092
        if necessary.
 
1093
        
 
1094
        Don't quote values that don't need it.
 
1095
        Recursively quote members of a list and return a comma joined list.
 
1096
        Multiline is ``False`` for lists.
 
1097
        Obey list syntax for empty and single member lists.
 
1098
        """
 
1099
        if isinstance(value, (list, tuple)):
 
1100
            if not value:
 
1101
                return ','
 
1102
            elif len(value) == 1:
 
1103
                return self._quote(value[0], multiline=False) + ','
 
1104
            return ','.join([self._quote(val, multiline=False)
 
1105
                for val in value])
 
1106
        if not isinstance(value, StringTypes):
 
1107
            if self.stringify:
 
1108
                value = str(value)
 
1109
            else:
 
1110
                raise TypeError, 'Value "%s" is not a string.' % value
 
1111
        squot = "'%s'"
 
1112
        dquot = '"%s"'
 
1113
        noquot = "%s"
 
1114
        wspace_plus = ' \r\t\n\v\t\'"'
 
1115
        tsquot = '"""%s"""'
 
1116
        tdquot = "'''%s'''"
 
1117
        if not value:
 
1118
            return '""'
 
1119
        if not (multiline and
 
1120
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
 
1121
            # for normal values either single or double quotes will do
 
1122
            if '\n' in value:
 
1123
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
 
1124
                    value)
 
1125
            if ((value[0] not in wspace_plus) and
 
1126
                    (value[-1] not in wspace_plus) and
 
1127
                    (',' not in value)):
 
1128
                quot = noquot
 
1129
            else:
 
1130
                if ("'" in value) and ('"' in value):
 
1131
                    raise ConfigObjError, (
 
1132
                        'Value "%s" cannot be safely quoted.' % value)
 
1133
                elif '"' in value:
 
1134
                    quot = squot
 
1135
                else:
 
1136
                    quot = dquot
 
1137
        else:
 
1138
            # if value has '\n' or "'" *and* '"', it will need triple quotes
 
1139
            if (value.find('"""') != -1) and (value.find("'''") != -1):
 
1140
                raise ConfigObjError, (
 
1141
                    'Value "%s" cannot be safely quoted.' % value)
 
1142
            if value.find('"""') == -1:
 
1143
                quot = tdquot
 
1144
            else:
 
1145
                quot = tsquot
 
1146
        return quot % value
 
1147
 
 
1148
    def _handle_value(self, value):
 
1149
        """
 
1150
        Given a value string, unquote, remove comment,
 
1151
        handle lists. (including empty and single member lists)
 
1152
        
 
1153
        Testing list values.
 
1154
        
 
1155
        >>> testconfig3 = '''
 
1156
        ... a = ,
 
1157
        ... b = test,
 
1158
        ... c = test1, test2   , test3
 
1159
        ... d = test1, test2, test3,
 
1160
        ... '''
 
1161
        >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
 
1162
        >>> d['a'] == []
 
1163
        1
 
1164
        >>> d['b'] == ['test']
 
1165
        1
 
1166
        >>> d['c'] == ['test1', 'test2', 'test3']
 
1167
        1
 
1168
        >>> d['d'] == ['test1', 'test2', 'test3']
 
1169
        1
 
1170
        
 
1171
        Testing with list values off.
 
1172
        
 
1173
        >>> e = ConfigObj(
 
1174
        ...     testconfig3.split('\\n'),
 
1175
        ...     raise_errors=True,
 
1176
        ...     list_values=False)
 
1177
        >>> e['a'] == ','
 
1178
        1
 
1179
        >>> e['b'] == 'test,'
 
1180
        1
 
1181
        >>> e['c'] == 'test1, test2   , test3'
 
1182
        1
 
1183
        >>> e['d'] == 'test1, test2, test3,'
 
1184
        1
 
1185
        
 
1186
        Testing creating from a dictionary.
 
1187
        
 
1188
        >>> f = {
 
1189
        ...     'key1': 'val1',
 
1190
        ...     'key2': 'val2',
 
1191
        ...     'section 1': {
 
1192
        ...         'key1': 'val1',
 
1193
        ...         'key2': 'val2',
 
1194
        ...         'section 1b': {
 
1195
        ...             'key1': 'val1',
 
1196
        ...             'key2': 'val2',
 
1197
        ...         },
 
1198
        ...     },
 
1199
        ...     'section 2': {
 
1200
        ...         'key1': 'val1',
 
1201
        ...         'key2': 'val2',
 
1202
        ...         'section 2b': {
 
1203
        ...             'key1': 'val1',
 
1204
        ...             'key2': 'val2',
 
1205
        ...         },
 
1206
        ...     },
 
1207
        ...      'key3': 'val3',
 
1208
        ... }
 
1209
        >>> g = ConfigObj(f)
 
1210
        >>> f == g
 
1211
        1
 
1212
        
 
1213
        Testing we correctly detect badly built list values (4 of them).
 
1214
        
 
1215
        >>> testconfig4 = '''
 
1216
        ... config = 3,4,,
 
1217
        ... test = 3,,4
 
1218
        ... fish = ,,
 
1219
        ... dummy = ,,hello, goodbye
 
1220
        ... '''
 
1221
        >>> try:
 
1222
        ...     ConfigObj(testconfig4.split('\\n'))
 
1223
        ... except ConfigObjError, e:
 
1224
        ...     len(e.errors)
 
1225
        4
 
1226
        
 
1227
        Testing we correctly detect badly quoted values (4 of them).
 
1228
        
 
1229
        >>> testconfig5 = '''
 
1230
        ... config = "hello   # comment
 
1231
        ... test = 'goodbye
 
1232
        ... fish = 'goodbye   # comment
 
1233
        ... dummy = "hello again
 
1234
        ... '''
 
1235
        >>> try:
 
1236
        ...     ConfigObj(testconfig5.split('\\n'))
 
1237
        ... except ConfigObjError, e:
 
1238
        ...     len(e.errors)
 
1239
        4
 
1240
        """
 
1241
        # do we look for lists in values ?
 
1242
        if not self.list_values:
 
1243
            mat = self._nolistvalue.match(value)
 
1244
            if mat is None:
 
1245
                raise SyntaxError
 
1246
            (value, comment) = mat.groups()
 
1247
            # FIXME: unquoting here can be a source of error
 
1248
            return (self._unquote(value), comment)
 
1249
        mat = self._valueexp.match(value)
 
1250
        if mat is None:
 
1251
            # the value is badly constructed, probably badly quoted,
 
1252
            # or an invalid list
 
1253
            raise SyntaxError
 
1254
        (list_values, single, empty_list, comment) = mat.groups()
 
1255
        if (list_values == '') and (single is None):
 
1256
            # change this if you want to accept empty values
 
1257
            raise SyntaxError
 
1258
        # NOTE: note there is no error handling from here if the regex
 
1259
        # is wrong: then incorrect values will slip through
 
1260
        if empty_list is not None:
 
1261
            # the single comma - meaning an empty list
 
1262
            return ([], comment)
 
1263
        if single is not None:
 
1264
            single = self._unquote(single)
 
1265
        if list_values == '':
 
1266
            # not a list value
 
1267
            return (single, comment)
 
1268
        the_list = self._listvalueexp.findall(list_values)
 
1269
        the_list = [self._unquote(val) for val in the_list]
 
1270
        if single is not None:
 
1271
            the_list += [single]
 
1272
        return (the_list, comment)
 
1273
 
 
1274
    def _multiline(self, value, infile, cur_index, maxline):
 
1275
        """
 
1276
        Extract the value, where we are in a multiline situation
 
1277
        
 
1278
        Testing multiline values.
 
1279
        
 
1280
        >>> i == {
 
1281
        ...     'name4': ' another single line value ',
 
1282
        ...     'multi section': {
 
1283
        ...         'name4': '\\n        Well, this is a\\n        multiline '
 
1284
        ...             'value\\n        ',
 
1285
        ...         'name2': '\\n        Well, this is a\\n        multiline '
 
1286
        ...             'value\\n        ',
 
1287
        ...         'name3': '\\n        Well, this is a\\n        multiline '
 
1288
        ...             'value\\n        ',
 
1289
        ...         'name1': '\\n        Well, this is a\\n        multiline '
 
1290
        ...             'value\\n        ',
 
1291
        ...     },
 
1292
        ...     'name2': ' another single line value ',
 
1293
        ...     'name3': ' a single line value ',
 
1294
        ...     'name1': ' a single line value ',
 
1295
        ... }
 
1296
        1
 
1297
        """
 
1298
        quot = value[:3]
 
1299
        newvalue = value[3:]
 
1300
        single_line = self._triple_quote[quot][0]
 
1301
        multi_line = self._triple_quote[quot][1]
 
1302
        mat = single_line.match(value)
 
1303
        if mat is not None:
 
1304
            retval = list(mat.groups())
 
1305
            retval.append(cur_index)
 
1306
            return retval
 
1307
        elif newvalue.find(quot) != -1:
 
1308
            # somehow the triple quote is missing
 
1309
            raise SyntaxError
 
1310
        #
 
1311
        while cur_index < maxline:
 
1312
            cur_index += 1
 
1313
            newvalue += '\n'
 
1314
            line = infile[cur_index]
 
1315
            if line.find(quot) == -1:
 
1316
                newvalue += line
 
1317
            else:
 
1318
                # end of multiline, process it
 
1319
                break
 
1320
        else:
 
1321
            # we've got to the end of the config, oops...
 
1322
            raise SyntaxError
 
1323
        mat = multi_line.match(line)
 
1324
        if mat is None:
 
1325
            # a badly formed line
 
1326
            raise SyntaxError
 
1327
        (value, comment) = mat.groups()
 
1328
        return (newvalue + value, comment, cur_index)
 
1329
 
 
1330
    def _handle_configspec(self, configspec):
 
1331
        """Parse the configspec."""
 
1332
        try:
 
1333
            configspec = ConfigObj(
 
1334
                configspec,
 
1335
                raise_errors=True,
 
1336
                file_error=True,
 
1337
                list_values=False)
 
1338
        except ConfigObjError, e:
 
1339
            # FIXME: Should these errors have a reference
 
1340
            # to the already parsed ConfigObj ?
 
1341
            raise ConfigspecError('Parsing configspec failed: %s' % e)
 
1342
        except IOError, e:
 
1343
            raise IOError('Reading configspec failed: %s' % e)
 
1344
        self._set_configspec_value(configspec, self)
 
1345
 
 
1346
    def _set_configspec_value(self, configspec, section):
 
1347
        """Used to recursively set configspec values."""
 
1348
        if '__many__' in configspec.sections:
 
1349
            section.configspec['__many__'] = configspec['__many__']
 
1350
            if len(configspec.sections) > 1:
 
1351
                # FIXME: can we supply any useful information here ?
 
1352
                raise RepeatSectionError
 
1353
        for entry in configspec.scalars:
 
1354
            section.configspec[entry] = configspec[entry]
 
1355
        for entry in configspec.sections:
 
1356
            if entry == '__many__':
 
1357
                continue
 
1358
            if not section.has_key(entry):
 
1359
                section[entry] = {}
 
1360
            self._set_configspec_value(configspec[entry], section[entry])
 
1361
 
 
1362
    def _handle_repeat(self, section, configspec):
 
1363
        """Dynamically assign configspec for repeated section."""
 
1364
        try:
 
1365
            section_keys = configspec.sections
 
1366
            scalar_keys = configspec.scalars
 
1367
        except AttributeError:
 
1368
            section_keys = [entry for entry in configspec 
 
1369
                                if isinstance(configspec[entry], dict)]
 
1370
            scalar_keys = [entry for entry in configspec 
 
1371
                                if not isinstance(configspec[entry], dict)]
 
1372
        if '__many__' in section_keys and len(section_keys) > 1:
 
1373
            # FIXME: can we supply any useful information here ?
 
1374
            raise RepeatSectionError
 
1375
        scalars = {}
 
1376
        sections = {}
 
1377
        for entry in scalar_keys:
 
1378
            val = configspec[entry]
 
1379
            scalars[entry] = val
 
1380
        for entry in section_keys:
 
1381
            val = configspec[entry]
 
1382
            if entry == '__many__':
 
1383
                scalars[entry] = val
 
1384
                continue
 
1385
            sections[entry] = val
 
1386
        #
 
1387
        section.configspec = scalars
 
1388
        for entry in sections:
 
1389
            if not section.has_key(entry):
 
1390
                section[entry] = {}
 
1391
            self._handle_repeat(section[entry], sections[entry])
 
1392
 
 
1393
    def _write_line(self, indent_string, entry, this_entry, comment):
 
1394
        """Write an individual line, for the write method"""
 
1395
        return '%s%s = %s%s' % (
 
1396
            indent_string,
 
1397
            self._quote(entry, multiline=False),
 
1398
            self._quote(this_entry),
 
1399
            comment)
 
1400
 
 
1401
    def _write_marker(self, indent_string, depth, entry, comment):
 
1402
        """Write a section marker line"""
 
1403
        return '%s%s%s%s%s' % (
 
1404
            indent_string,
 
1405
            '[' * depth,
 
1406
            self._quote(entry, multiline=False),
 
1407
            ']' * depth,
 
1408
            comment)
 
1409
 
 
1410
    def _handle_comment(self, comment):
 
1411
        """
 
1412
        Deal with a comment.
 
1413
        
 
1414
        >>> filename = a.filename
 
1415
        >>> a.filename = None
 
1416
        >>> values = a.write()
 
1417
        >>> index = 0
 
1418
        >>> while index < 23:
 
1419
        ...     index += 1
 
1420
        ...     line = values[index-1]
 
1421
        ...     assert line.endswith('# comment ' + str(index))
 
1422
        >>> a.filename = filename
 
1423
        
 
1424
        >>> start_comment = ['# Initial Comment', '', '#']
 
1425
        >>> end_comment = ['', '#', '# Final Comment']
 
1426
        >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
 
1427
        >>> nc = ConfigObj(newconfig)
 
1428
        >>> nc.initial_comment
 
1429
        ['# Initial Comment', '', '#']
 
1430
        >>> nc.final_comment
 
1431
        ['', '#', '# Final Comment']
 
1432
        >>> nc.initial_comment == start_comment
 
1433
        1
 
1434
        >>> nc.final_comment == end_comment
 
1435
        1
 
1436
        """
 
1437
        if not comment:
 
1438
            return ''
 
1439
        if self.indent_type == '\t':
 
1440
            start = '\t'
 
1441
        else:
 
1442
            start = ' ' * NUM_INDENT_SPACES
 
1443
        if not comment.startswith('#'):
 
1444
            start += '# '
 
1445
        return (start + comment)
 
1446
 
 
1447
    def _compute_indent_string(self, depth):
 
1448
        """
 
1449
        Compute the indent string, according to current indent_type and depth
 
1450
        """
 
1451
        if self.indent_type == '':
 
1452
            # no indentation at all
 
1453
            return ''
 
1454
        if self.indent_type == '\t':
 
1455
            return '\t' * depth
 
1456
        if self.indent_type == ' ':
 
1457
            return ' ' * NUM_INDENT_SPACES * depth
 
1458
        raise SyntaxError
 
1459
 
 
1460
    # Public methods
 
1461
 
 
1462
    def write(self, section=None):
 
1463
        """
 
1464
        Write the current ConfigObj as a file
 
1465
        
 
1466
        tekNico: FIXME: use StringIO instead of real files
 
1467
        
 
1468
        >>> filename = a.filename
 
1469
        >>> a.filename = 'test.ini'
 
1470
        >>> a.write()
 
1471
        >>> a.filename = filename
 
1472
        >>> a == ConfigObj('test.ini', raise_errors=True)
 
1473
        1
 
1474
        >>> os.remove('test.ini')
 
1475
        >>> b.filename = 'test.ini'
 
1476
        >>> b.write()
 
1477
        >>> b == ConfigObj('test.ini', raise_errors=True)
 
1478
        1
 
1479
        >>> os.remove('test.ini')
 
1480
        >>> i.filename = 'test.ini'
 
1481
        >>> i.write()
 
1482
        >>> i == ConfigObj('test.ini', raise_errors=True)
 
1483
        1
 
1484
        >>> os.remove('test.ini')
 
1485
        >>> a = ConfigObj()
 
1486
        >>> a['DEFAULT'] = {'a' : 'fish'}
 
1487
        >>> a['a'] = '%(a)s'
 
1488
        >>> a.write()
 
1489
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
 
1490
        """
 
1491
        int_val = 'test'
 
1492
        if self.indent_type is None:
 
1493
            # this can be true if initialised from a dictionary
 
1494
            self.indent_type = DEFAULT_INDENT_TYPE
 
1495
        #
 
1496
        out = []
 
1497
        return_list = True
 
1498
        if section is None:
 
1499
            int_val = self.interpolation
 
1500
            self.interpolation = False
 
1501
            section = self
 
1502
            return_list = False
 
1503
            for line in self.initial_comment:
 
1504
                stripped_line = line.strip()
 
1505
                if stripped_line and not stripped_line.startswith('#'):
 
1506
                    line = '# ' + line
 
1507
                out.append(line)
 
1508
        #
 
1509
        indent_string = self._compute_indent_string(section.depth)
 
1510
        for entry in (section.scalars + section.sections):
 
1511
            if entry in section.defaults:
 
1512
                # don't write out default values
 
1513
                continue
 
1514
            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
 
1518
                out.append(indent_string + comment_line)
 
1519
            this_entry = section[entry]
 
1520
            comment = self._handle_comment(section.inline_comments[entry])
 
1521
            #
 
1522
            if isinstance(this_entry, dict):
 
1523
                # a section
 
1524
                out.append(self._write_marker(
 
1525
                    indent_string,
 
1526
                    this_entry.depth,
 
1527
                    entry,
 
1528
                    comment))
 
1529
                out.extend(self.write(this_entry))
 
1530
            else:
 
1531
                out.append(self._write_line(
 
1532
                    indent_string,
 
1533
                    entry,
 
1534
                    this_entry,
 
1535
                    comment))
 
1536
        #
 
1537
        if not return_list:
 
1538
            for line in self.final_comment:
 
1539
                stripped_line = line.strip()
 
1540
                if stripped_line and not stripped_line.startswith('#'):
 
1541
                    line = '# ' + line
 
1542
                out.append(line)
 
1543
        #
 
1544
        if int_val != 'test':
 
1545
            self.interpolation = int_val
 
1546
        #
 
1547
        if (return_list) or (self.filename is None):
 
1548
            return out
 
1549
        #
 
1550
        if isinstance(self.filename, StringTypes):
 
1551
            h = open(self.filename, 'w')
 
1552
            h.write(self.BOM or '')
 
1553
            h.write('\n'.join(out))
 
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
 
1561
 
 
1562
    def validate(self, validator, section=None):
 
1563
        """
 
1564
        Test the ConfigObj against a configspec.
 
1565
        
 
1566
        It uses the ``validator`` object from *validate.py*.
 
1567
        
 
1568
        To run ``validate`` on the current ConfigObj, call: ::
 
1569
        
 
1570
            test = config.validate(validator)
 
1571
        
 
1572
        (Normally having previously passed in the configspec when the ConfigObj
 
1573
        was created - you can dynamically assign a dictionary of checks to the
 
1574
        ``configspec`` attribute of a section though).
 
1575
        
 
1576
        It returns ``True`` if everything passes, or a dictionary of
 
1577
        pass/fails (True/False). If every member of a subsection passes, it
 
1578
        will just have the value ``True``. (It also returns ``False`` if all
 
1579
        members fail).
 
1580
        
 
1581
        In addition, it converts the values from strings to their native
 
1582
        types if their checks pass (and ``stringify`` is set).
 
1583
        
 
1584
        >>> try:
 
1585
        ...     from validate import Validator
 
1586
        ... except ImportError:
 
1587
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
 
1588
        ... else:
 
1589
        ...     config = '''
 
1590
        ...     test1=40
 
1591
        ...     test2=hello
 
1592
        ...     test3=3
 
1593
        ...     test4=5.0
 
1594
        ...     [section]
 
1595
        ...         test1=40
 
1596
        ...         test2=hello
 
1597
        ...         test3=3
 
1598
        ...         test4=5.0
 
1599
        ...         [[sub section]]
 
1600
        ...             test1=40
 
1601
        ...             test2=hello
 
1602
        ...             test3=3
 
1603
        ...             test4=5.0
 
1604
        ... '''.split('\\n')
 
1605
        ...     configspec = '''
 
1606
        ...     test1='integer(30,50)'
 
1607
        ...     test2='string'
 
1608
        ...     test3='integer'
 
1609
        ...     test4='float(6.0)'
 
1610
        ...     [section ]
 
1611
        ...         test1='integer(30,50)'
 
1612
        ...         test2='string'
 
1613
        ...         test3='integer'
 
1614
        ...         test4='float(6.0)'
 
1615
        ...         [[sub section]]
 
1616
        ...             test1='integer(30,50)'
 
1617
        ...             test2='string'
 
1618
        ...             test3='integer'
 
1619
        ...             test4='float(6.0)'
 
1620
        ...     '''.split('\\n')
 
1621
        ...     val = Validator()
 
1622
        ...     c1 = ConfigObj(config, configspec=configspec)
 
1623
        ...     test = c1.validate(val)
 
1624
        ...     test == {
 
1625
        ...         'test1': True,
 
1626
        ...         'test2': True,
 
1627
        ...         'test3': True,
 
1628
        ...         'test4': False,
 
1629
        ...         'section': {
 
1630
        ...             'test1': True,
 
1631
        ...             'test2': True,
 
1632
        ...             'test3': True,
 
1633
        ...             'test4': False,
 
1634
        ...             'sub section': {
 
1635
        ...                 'test1': True,
 
1636
        ...                 'test2': True,
 
1637
        ...                 'test3': True,
 
1638
        ...                 'test4': False,
 
1639
        ...             },
 
1640
        ...         },
 
1641
        ...     }
 
1642
        1
 
1643
        >>> val.check(c1.configspec['test4'], c1['test4'])
 
1644
        Traceback (most recent call last):
 
1645
        VdtValueTooSmallError: the value "5.0" is too small.
 
1646
        
 
1647
        >>> val_test_config = '''
 
1648
        ...     key = 0
 
1649
        ...     key2 = 1.1
 
1650
        ...     [section]
 
1651
        ...     key = some text
 
1652
        ...     key2 = 1.1, 3.0, 17, 6.8
 
1653
        ...         [[sub-section]]
 
1654
        ...         key = option1
 
1655
        ...         key2 = True'''.split('\\n')
 
1656
        >>> val_test_configspec = '''
 
1657
        ...     key = integer
 
1658
        ...     key2 = float
 
1659
        ...     [section]
 
1660
        ...     key = string
 
1661
        ...     key2 = float_list(4)
 
1662
        ...        [[sub-section]]
 
1663
        ...        key = option(option1, option2)
 
1664
        ...        key2 = boolean'''.split('\\n')
 
1665
        >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
 
1666
        >>> val_test.validate(val)
 
1667
        1
 
1668
        >>> val_test['key'] = 'text not a digit'
 
1669
        >>> val_res = val_test.validate(val)
 
1670
        >>> val_res == {'key2': True, 'section': True, 'key': False}
 
1671
        1
 
1672
        >>> 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)'
 
1677
        ...     [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)'
 
1682
        ...         [[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)'
 
1687
        ...     '''.split('\\n')
 
1688
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
 
1689
        >>> default_test
 
1690
        {'test1': '30', 'section': {'sub section': {}}}
 
1691
        >>> default_test.validate(val)
 
1692
        1
 
1693
        >>> default_test == {
 
1694
        ...     'test1': 30,
 
1695
        ...     'test2': 'hello',
 
1696
        ...     'test3': 3,
 
1697
        ...     'test4': 6.0,
 
1698
        ...     'section': {
 
1699
        ...         'test1': 40,
 
1700
        ...         'test2': 'hello',
 
1701
        ...         'test3': 3,
 
1702
        ...         'test4': 6.0,
 
1703
        ...         'sub section': {
 
1704
        ...             'test1': 40,
 
1705
        ...             'test3': 3,
 
1706
        ...             'test2': 'hello',
 
1707
        ...             'test4': 6.0,
 
1708
        ...         },
 
1709
        ...     },
 
1710
        ... }
 
1711
        1
 
1712
        
 
1713
        Now testing with repeated sections : BIG TEST
 
1714
        
 
1715
        >>> repeated_1 = '''
 
1716
        ... [dogs]
 
1717
        ...     [[__many__]] # spec for a dog
 
1718
        ...         fleas = boolean(default=True)
 
1719
        ...         tail = option(long, short, default=long)
 
1720
        ...         name = string(default=rover)
 
1721
        ...         [[[__many__]]]  # spec for a puppy
 
1722
        ...             name = string(default="son of rover")
 
1723
        ...             age = float(default=0.0)
 
1724
        ... [cats]
 
1725
        ...     [[__many__]] # spec for a cat
 
1726
        ...         fleas = boolean(default=True)
 
1727
        ...         tail = option(long, short, default=short)
 
1728
        ...         name = string(default=pussy)
 
1729
        ...         [[[__many__]]] # spec for a kitten
 
1730
        ...             name = string(default="son of pussy")
 
1731
        ...             age = float(default=0.0)
 
1732
        ...         '''.split('\\n')
 
1733
        >>> repeated_2 = '''
 
1734
        ... [dogs]
 
1735
        ... 
 
1736
        ...     # blank dogs with puppies
 
1737
        ...     # should be filled in by the configspec
 
1738
        ...     [[dog1]]
 
1739
        ...         [[[puppy1]]]
 
1740
        ...         [[[puppy2]]]
 
1741
        ...         [[[puppy3]]]
 
1742
        ...     [[dog2]]
 
1743
        ...         [[[puppy1]]]
 
1744
        ...         [[[puppy2]]]
 
1745
        ...         [[[puppy3]]]
 
1746
        ...     [[dog3]]
 
1747
        ...         [[[puppy1]]]
 
1748
        ...         [[[puppy2]]]
 
1749
        ...         [[[puppy3]]]
 
1750
        ... [cats]
 
1751
        ... 
 
1752
        ...     # blank cats with kittens
 
1753
        ...     # should be filled in by the configspec
 
1754
        ...     [[cat1]]
 
1755
        ...         [[[kitten1]]]
 
1756
        ...         [[[kitten2]]]
 
1757
        ...         [[[kitten3]]]
 
1758
        ...     [[cat2]]
 
1759
        ...         [[[kitten1]]]
 
1760
        ...         [[[kitten2]]]
 
1761
        ...         [[[kitten3]]]
 
1762
        ...     [[cat3]]
 
1763
        ...         [[[kitten1]]]
 
1764
        ...         [[[kitten2]]]
 
1765
        ...         [[[kitten3]]]
 
1766
        ... '''.split('\\n')
 
1767
        >>> repeated_3 = '''
 
1768
        ... [dogs]
 
1769
        ... 
 
1770
        ...     [[dog1]]
 
1771
        ...     [[dog2]]
 
1772
        ...     [[dog3]]
 
1773
        ... [cats]
 
1774
        ... 
 
1775
        ...     [[cat1]]
 
1776
        ...     [[cat2]]
 
1777
        ...     [[cat3]]
 
1778
        ... '''.split('\\n')
 
1779
        >>> repeated_4 = '''
 
1780
        ... [__many__]
 
1781
        ... 
 
1782
        ...     name = string(default=Michael)
 
1783
        ...     age = float(default=0.0)
 
1784
        ...     sex = option(m, f, default=m)
 
1785
        ... '''.split('\\n')
 
1786
        >>> repeated_5 = '''
 
1787
        ... [cats]
 
1788
        ... [[__many__]]
 
1789
        ...     fleas = boolean(default=True)
 
1790
        ...     tail = option(long, short, default=short)
 
1791
        ...     name = string(default=pussy)
 
1792
        ...     [[[description]]]
 
1793
        ...         height = float(default=3.3)
 
1794
        ...         weight = float(default=6)
 
1795
        ...         [[[[coat]]]]
 
1796
        ...             fur = option(black, grey, brown, "tortoise shell", default=black)
 
1797
        ...             condition = integer(0,10, default=5)
 
1798
        ... '''.split('\\n')
 
1799
        >>> from validate import Validator
 
1800
        >>> val= Validator()
 
1801
        >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
 
1802
        >>> repeater.validate(val)
 
1803
        1
 
1804
        >>> repeater == {
 
1805
        ...     'dogs': {
 
1806
        ...         'dog1': {
 
1807
        ...             'fleas': True,
 
1808
        ...             'tail': 'long',
 
1809
        ...             'name': 'rover',
 
1810
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
1811
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
1812
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
1813
        ...         },
 
1814
        ...         'dog2': {
 
1815
        ...             'fleas': True,
 
1816
        ...             'tail': 'long',
 
1817
        ...             'name': 'rover',
 
1818
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
1819
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
1820
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
1821
        ...         },
 
1822
        ...         'dog3': {
 
1823
        ...             'fleas': True,
 
1824
        ...             'tail': 'long',
 
1825
        ...             'name': 'rover',
 
1826
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
1827
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
1828
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
1829
        ...         },
 
1830
        ...     },
 
1831
        ...     'cats': {
 
1832
        ...         'cat1': {
 
1833
        ...             'fleas': True,
 
1834
        ...             'tail': 'short',
 
1835
        ...             'name': 'pussy',
 
1836
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
1837
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
1838
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
1839
        ...         },
 
1840
        ...         'cat2': {
 
1841
        ...             'fleas': True,
 
1842
        ...             'tail': 'short',
 
1843
        ...             'name': 'pussy',
 
1844
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
1845
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
1846
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
1847
        ...         },
 
1848
        ...         'cat3': {
 
1849
        ...             'fleas': True,
 
1850
        ...             'tail': 'short',
 
1851
        ...             'name': 'pussy',
 
1852
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
1853
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
1854
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
1855
        ...         },
 
1856
        ...     },
 
1857
        ... }
 
1858
        1
 
1859
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
 
1860
        >>> repeater.validate(val)
 
1861
        1
 
1862
        >>> repeater == {
 
1863
        ...     'cats': {
 
1864
        ...         'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
1865
        ...         'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
1866
        ...         'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
1867
        ...     },
 
1868
        ...     'dogs': {
 
1869
        ...         'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
1870
        ...         'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
1871
        ...         'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
1872
        ...     },
 
1873
        ... }
 
1874
        1
 
1875
        >>> repeater = ConfigObj(configspec=repeated_4)
 
1876
        >>> repeater['Michael'] = {}
 
1877
        >>> repeater.validate(val)
 
1878
        1
 
1879
        >>> repeater == {
 
1880
        ...     'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
 
1881
        ... }
 
1882
        1
 
1883
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
 
1884
        >>> repeater == {
 
1885
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
1886
        ...     'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
 
1887
        ... }
 
1888
        1
 
1889
        >>> repeater.validate(val)
 
1890
        1
 
1891
        >>> repeater == {
 
1892
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
1893
        ...     'cats': {
 
1894
        ...         'cat1': {
 
1895
        ...             'fleas': True,
 
1896
        ...             'tail': 'short',
 
1897
        ...             'name': 'pussy',
 
1898
        ...             'description': {
 
1899
        ...                 'weight': 6.0,
 
1900
        ...                 'height': 3.2999999999999998,
 
1901
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
1902
        ...             },
 
1903
        ...         },
 
1904
        ...         'cat2': {
 
1905
        ...             'fleas': True,
 
1906
        ...             'tail': 'short',
 
1907
        ...             'name': 'pussy',
 
1908
        ...             'description': {
 
1909
        ...                 'weight': 6.0,
 
1910
        ...                 'height': 3.2999999999999998,
 
1911
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
1912
        ...             },
 
1913
        ...         },
 
1914
        ...         'cat3': {
 
1915
        ...             'fleas': True,
 
1916
        ...             'tail': 'short',
 
1917
        ...             'name': 'pussy',
 
1918
        ...             'description': {
 
1919
        ...                 'weight': 6.0,
 
1920
        ...                 'height': 3.2999999999999998,
 
1921
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
1922
        ...             },
 
1923
        ...         },
 
1924
        ...     },
 
1925
        ... }
 
1926
        1
 
1927
        
 
1928
        Test that interpolation is preserved for validated string values.
 
1929
        >>> t = ConfigObj()
 
1930
        >>> t['DEFAULT'] = {}
 
1931
        >>> t['DEFAULT']['test'] = 'a'
 
1932
        >>> t['test'] = '%(test)s'
 
1933
        >>> t['test']
 
1934
        'a'
 
1935
        >>> v = Validator()
 
1936
        >>> t.configspec = {'test': 'string'}
 
1937
        >>> t.validate(v)
 
1938
        1
 
1939
        >>> t.interpolation = False
 
1940
        >>> t
 
1941
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 
1942
        
 
1943
        FIXME: Above tests will fail if we couldn't import Validator (the ones
 
1944
        that don't raise errors will produce different output and still fail as
 
1945
        tests)
 
1946
        """
 
1947
        if section is None:
 
1948
            if self.configspec is None:
 
1949
                raise ValueError, 'No configspec supplied.'
 
1950
            section = self
 
1951
        #
 
1952
        spec_section = section.configspec
 
1953
        if '__many__' in section.configspec:
 
1954
            many = spec_section['__many__']
 
1955
            # dynamically assign the configspecs
 
1956
            # for the sections below
 
1957
            for entry in section.sections:
 
1958
                self._handle_repeat(section[entry], many)
 
1959
        #
 
1960
        out = {}
 
1961
        ret_true = True
 
1962
        ret_false = True
 
1963
        for entry in spec_section:
 
1964
            if entry == '__many__':
 
1965
                continue
 
1966
            if (not entry in section.scalars) or (entry in section.defaults):
 
1967
                # missing entries
 
1968
                # or entries from defaults
 
1969
                missing = True
 
1970
                val = None
 
1971
            else:
 
1972
                missing = False
 
1973
                val = section[entry]
 
1974
            try:
 
1975
                check = validator.check(spec_section[entry],
 
1976
                                        val,
 
1977
                                        missing=missing)
 
1978
            except validator.baseErrorClass:
 
1979
                out[entry] = False
 
1980
                ret_true = False
 
1981
            # MIKE: we want to raise all other exceptions, not just print ?
 
1982
##            except Exception, err:
 
1983
##                print err
 
1984
            else:
 
1985
                ret_false = False
 
1986
                out[entry] = True
 
1987
                if self.stringify or missing:
 
1988
                    # if we are doing type conversion
 
1989
                    # or the value is a supplied default
 
1990
                    if not self.stringify:
 
1991
                        if isinstance(check, (list, tuple)):
 
1992
                            # preserve lists
 
1993
                            check = [str(item) for item in check]
 
1994
                        elif missing and check is None:
 
1995
                            # convert the None from a default to a ''
 
1996
                            check = ''
 
1997
                        else:
 
1998
                            check = str(check)
 
1999
                    if (check != val) or missing:
 
2000
                        section[entry] = check
 
2001
                if missing and entry not in section.defaults:
 
2002
                    section.defaults.append(entry)
 
2003
        #
 
2004
        for entry in section.sections:
 
2005
            check = self.validate(validator, section[entry])
 
2006
            out[entry] = check
 
2007
            if check == False:
 
2008
                ret_true = False
 
2009
            elif check == True:
 
2010
                ret_false = False
 
2011
            else:
 
2012
                ret_true = False
 
2013
                ret_false = False
 
2014
        #
 
2015
        if ret_true:
 
2016
            return True
 
2017
        elif ret_false:
 
2018
            return False
 
2019
        else:
 
2020
            return out
 
2021
 
 
2022
class SimpleVal(object):
 
2023
    """
 
2024
    A simple validator.
 
2025
    Can be used to check that all members expected are present.
 
2026
    
 
2027
    To use it, provide a configspec with all your members in (the value given
 
2028
    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
 
2029
    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
 
2030
    members are present, or a dictionary with True/False meaning
 
2031
    present/missing. (Whole missing sections will be replaced with ``False``)
 
2032
    
 
2033
    >>> val = SimpleVal()
 
2034
    >>> config = '''
 
2035
    ... test1=40
 
2036
    ... test2=hello
 
2037
    ... test3=3
 
2038
    ... test4=5.0
 
2039
    ... [section]
 
2040
    ... test1=40
 
2041
    ... test2=hello
 
2042
    ... test3=3
 
2043
    ... test4=5.0
 
2044
    ...     [[sub section]]
 
2045
    ...     test1=40
 
2046
    ...     test2=hello
 
2047
    ...     test3=3
 
2048
    ...     test4=5.0
 
2049
    ... '''.split('\\n')
 
2050
    >>> configspec = '''
 
2051
    ... test1=''
 
2052
    ... test2=''
 
2053
    ... test3=''
 
2054
    ... test4=''
 
2055
    ... [section]
 
2056
    ... test1=''
 
2057
    ... test2=''
 
2058
    ... test3=''
 
2059
    ... test4=''
 
2060
    ...     [[sub section]]
 
2061
    ...     test1=''
 
2062
    ...     test2=''
 
2063
    ...     test3=''
 
2064
    ...     test4=''
 
2065
    ... '''.split('\\n')
 
2066
    >>> o = ConfigObj(config, configspec=configspec)
 
2067
    >>> o.validate(val)
 
2068
    1
 
2069
    >>> o = ConfigObj(configspec=configspec)
 
2070
    >>> o.validate(val)
 
2071
    0
 
2072
    """
 
2073
    
 
2074
    def __init__(self):
 
2075
        self.baseErrorClass = ConfigObjError
 
2076
    
 
2077
    def check(self, check, member, missing=False):
 
2078
        """A dummy check method, always returns the value unchanged."""
 
2079
        if missing:
 
2080
            raise self.baseErrorClass
 
2081
        return member
 
2082
 
 
2083
# FIXME: test error code for badly built multiline values
 
2084
# FIXME: test handling of StringIO
 
2085
# FIXME: test interpolation with writing
 
2086
 
 
2087
def _doctest():
 
2088
    """
 
2089
    Dummy function to hold some of the doctests.
 
2090
    
 
2091
    >>> a.depth
 
2092
    0
 
2093
    >>> a == {
 
2094
    ...     'key2': 'val',
 
2095
    ...     'key1': 'val',
 
2096
    ...     'lev1c': {
 
2097
    ...         'lev2c': {
 
2098
    ...             'lev3c': {
 
2099
    ...                 'key1': 'val',
 
2100
    ...             },
 
2101
    ...         },
 
2102
    ...     },
 
2103
    ...     'lev1b': {
 
2104
    ...         'key2': 'val',
 
2105
    ...         'key1': 'val',
 
2106
    ...         'lev2ba': {
 
2107
    ...             'key1': 'val',
 
2108
    ...         },
 
2109
    ...         'lev2bb': {
 
2110
    ...             'key1': 'val',
 
2111
    ...         },
 
2112
    ...     },
 
2113
    ...     'lev1a': {
 
2114
    ...         'key2': 'val',
 
2115
    ...         'key1': 'val',
 
2116
    ...     },
 
2117
    ... }
 
2118
    1
 
2119
    >>> b.depth
 
2120
    0
 
2121
    >>> b == {
 
2122
    ...     'key3': 'val3',
 
2123
    ...     'key2': 'val2',
 
2124
    ...     'key1': 'val1',
 
2125
    ...     'section 1': {
 
2126
    ...         'keys11': 'val1',
 
2127
    ...         'keys13': 'val3',
 
2128
    ...         'keys12': 'val2',
 
2129
    ...     },
 
2130
    ...     'section 2': {
 
2131
    ...         'section 2 sub 1': {
 
2132
    ...             'fish': '3',
 
2133
    ...     },
 
2134
    ...     'keys21': 'val1',
 
2135
    ...     'keys22': 'val2',
 
2136
    ...     'keys23': 'val3',
 
2137
    ...     },
 
2138
    ... }
 
2139
    1
 
2140
    >>> t = '''
 
2141
    ... 'a' = b # !"$%^&*(),::;'@~#= 33
 
2142
    ... "b" = b #= 6, 33
 
2143
    ... ''' .split('\\n')
 
2144
    >>> t2 = ConfigObj(t)
 
2145
    >>> assert t2 == {'a': 'b', 'b': 'b'}
 
2146
    >>> t2.inline_comments['b'] = ''
 
2147
    >>> del t2['a']
 
2148
    >>> assert t2.write() == ['','b = b', '']
 
2149
    """
 
2150
 
 
2151
if __name__ == '__main__':
 
2152
    # run the code tests in doctest format
 
2153
    #
 
2154
    testconfig1 = """\
 
2155
    key1= val    # comment 1
 
2156
    key2= val    # comment 2
 
2157
    # comment 3
 
2158
    [lev1a]     # comment 4
 
2159
    key1= val    # comment 5
 
2160
    key2= val    # comment 6
 
2161
    # comment 7
 
2162
    [lev1b]    # comment 8
 
2163
    key1= val    # comment 9
 
2164
    key2= val    # comment 10
 
2165
    # comment 11
 
2166
        [[lev2ba]]    # comment 12
 
2167
        key1= val    # comment 13
 
2168
        # comment 14
 
2169
        [[lev2bb]]    # comment 15
 
2170
        key1= val    # comment 16
 
2171
    # comment 17
 
2172
    [lev1c]    # comment 18
 
2173
    # comment 19
 
2174
        [[lev2c]]    # comment 20
 
2175
        # comment 21
 
2176
            [[[lev3c]]]    # comment 22
 
2177
            key1 = val    # comment 23"""
 
2178
    #
 
2179
    testconfig2 = """\
 
2180
                        key1 = 'val1'
 
2181
                        key2 =   "val2"
 
2182
                        key3 = val3
 
2183
                        ["section 1"] # comment
 
2184
                        keys11 = val1
 
2185
                        keys12 = val2
 
2186
                        keys13 = val3
 
2187
                        [section 2]
 
2188
                        keys21 = val1
 
2189
                        keys22 = val2
 
2190
                        keys23 = val3
 
2191
                        
 
2192
                            [['section 2 sub 1']]
 
2193
                            fish = 3
 
2194
    """
 
2195
    #
 
2196
    testconfig6 = '''
 
2197
    name1 = """ a single line value """ # comment
 
2198
    name2 = \''' another single line value \''' # comment
 
2199
    name3 = """ a single line value """
 
2200
    name4 = \''' another single line value \'''
 
2201
        [ "multi section" ]
 
2202
        name1 = """
 
2203
        Well, this is a
 
2204
        multiline value
 
2205
        """
 
2206
        name2 = \'''
 
2207
        Well, this is a
 
2208
        multiline value
 
2209
        \'''
 
2210
        name3 = """
 
2211
        Well, this is a
 
2212
        multiline value
 
2213
        """     # a comment
 
2214
        name4 = \'''
 
2215
        Well, this is a
 
2216
        multiline value
 
2217
        \'''  # I guess this is a comment too
 
2218
    '''
 
2219
    #
 
2220
    import doctest
 
2221
    m = sys.modules.get('__main__')
 
2222
    globs = m.__dict__.copy()
 
2223
    a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
 
2224
    b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
 
2225
    i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
 
2226
    globs.update({
 
2227
        'INTP_VER': INTP_VER,
 
2228
        'a': a,
 
2229
        'b': b,
 
2230
        'i': i,
 
2231
    })
 
2232
    doctest.testmod(m, globs=globs)
 
2233
 
 
2234
"""
 
2235
    BUGS
 
2236
    ====
 
2237
    
 
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)
 
2241
    
 
2242
    TODO
 
2243
    ====
 
2244
    
 
2245
    A method to optionally remove uniform indentation from multiline values.
 
2246
    (do as an example of using ``walk`` - along with string-escape)
 
2247
    
 
2248
    INCOMPATIBLE CHANGES
 
2249
    ====================
 
2250
    
 
2251
    (I have removed a lot of needless complications - this list is probably not
 
2252
    conclusive, many option/attribute/method names have changed)
 
2253
    
 
2254
    Case sensitive
 
2255
    
 
2256
    The only valid divider is '='
 
2257
    
 
2258
    We've removed line continuations with '\'
 
2259
    
 
2260
    No recursive lists in values
 
2261
    
 
2262
    No empty section
 
2263
    
 
2264
    No distinction between flatfiles and non flatfiles
 
2265
    
 
2266
    Change in list syntax - use commas to indicate list, not parentheses
 
2267
    (square brackets and parentheses are no longer recognised as lists)
 
2268
    
 
2269
    ';' is no longer valid for comments and no multiline comments
 
2270
    
 
2271
    No attribute access
 
2272
    
 
2273
    We don't allow empty values - have to use '' or ""
 
2274
    
 
2275
    In ConfigObj 3 - setting a non-flatfile member to ``None`` would
 
2276
    initialise it as an empty section.
 
2277
    
 
2278
    The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
 
2279
    replaced by triple quote, multiple line values.
 
2280
    
 
2281
    The ``newline``, ``force_return``, and ``default`` options have gone
 
2282
    
 
2283
    The ``encoding`` and ``backup_encoding`` methods have gone - replaced
 
2284
    with the ``encode`` and ``decode`` methods.
 
2285
    
 
2286
    ``fileerror`` and ``createempty`` options have become ``file_error`` and
 
2287
    ``create_empty``
 
2288
    
 
2289
    Partial configspecs (for specifying the order members should be written
 
2290
    out and which should be present) have gone. The configspec is no longer
 
2291
    used to specify order for the ``write`` method.
 
2292
    
 
2293
    Exceeding the maximum depth of recursion in string interpolation now
 
2294
    raises an error ``InterpolationDepthError``.
 
2295
    
 
2296
    Specifying a value for interpolation which doesn't exist now raises an
 
2297
    error ``MissingInterpolationOption`` (instead of merely being ignored).
 
2298
    
 
2299
    The ``writein`` method has been removed.
 
2300
    
 
2301
    The comments attribute is now a list (``inline_comments`` equates to the
 
2302
    old comments attribute)
 
2303
    
 
2304
    ISSUES
 
2305
    ======
 
2306
    
 
2307
    You can't have a keyword with the same name as a section (in the same
 
2308
    section). They are both dictionary keys - so they would overlap.
 
2309
    
 
2310
    Interpolation checks first the 'DEFAULT' subsection of the current
 
2311
    section, next it checks the 'DEFAULT' section of the parent section,
 
2312
    last it checks the 'DEFAULT' section of the main section.
 
2313
    
 
2314
    Logically a 'DEFAULT' section should apply to all subsections of the *same
 
2315
    parent* - this means that checking the 'DEFAULT' subsection in the
 
2316
    *current section* is not necessarily logical ?
 
2317
    
 
2318
    In order to simplify unicode support (which is possibly of limited value
 
2319
    in a config file) I have removed automatic support and added the
 
2320
    ``encode`` and ``decode methods, which can be used to transform keys and
 
2321
    entries. Because the regex looks for specific values on inital parsing
 
2322
    (i.e. the quotes and the equals signs) it can only read ascii compatible
 
2323
    encodings. For unicode use ``UTF8``, which is ASCII compatible.
 
2324
    
 
2325
    Does it matter that we don't support the ':' divider, which is supported
 
2326
    by ``ConfigParser`` ?
 
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
    
 
2335
    The regular expression correctly removes the value -
 
2336
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
 
2337
    back quotes (called from ``_handle_value``). What should we do ??
 
2338
    (*ought* to raise exception because it's an invalid value if lists are
 
2339
    off *sigh*. This is not what you want if you want to do your own list
 
2340
    processing - would be *better* in this case not to unquote.)
 
2341
    
 
2342
    String interpolation and validation don't play well together. When
 
2343
    validation changes type it sets the value. This will correctly fetch the
 
2344
    value using interpolation - but then overwrite the interpolation reference.
 
2345
    If the value is unchanged by validation (it's a string) - but other types
 
2346
    will be.
 
2347
    
 
2348
    List Value Syntax
 
2349
    =================
 
2350
    
 
2351
    List values allow you to specify multiple values for a keyword. This
 
2352
    maps to a list as the resulting Python object when parsed.
 
2353
    
 
2354
    The syntax for lists is easy. A list is a comma separated set of values.
 
2355
    If these values contain quotes, the hash mark, or commas, then the values
 
2356
    can be surrounded by quotes. e.g. : ::
 
2357
    
 
2358
        keyword = value1, 'value 2', "value 3"
 
2359
    
 
2360
    If a value needs to be a list, but only has one member, then you indicate
 
2361
    this with a trailing comma. e.g. : ::
 
2362
    
 
2363
        keyword = "single value",
 
2364
    
 
2365
    If a value needs to be a list, but it has no members, then you indicate
 
2366
    this with a single comma. e.g. : ::
 
2367
    
 
2368
        keyword = ,     # an empty list
 
2369
    
 
2370
    Using triple quotes it will be possible for single values to contain
 
2371
    newlines and *both* single quotes and double quotes. Triple quotes aren't
 
2372
    allowed in list values. This means that the members of list values can't
 
2373
    contain carriage returns (or line feeds :-) or both quote values.
 
2374
      
 
2375
    CHANGELOG
 
2376
    =========
 
2377
    
 
2378
    2005/10/09
 
2379
    ----------
 
2380
    
 
2381
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
 
2382
    ``interpolation``).
 
2383
    
 
2384
    2005/09/16
 
2385
    ----------
 
2386
    
 
2387
    Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
 
2388
    a reference to the new section.
 
2389
    
 
2390
    2005/09/09
 
2391
    ----------
 
2392
    
 
2393
    Removed ``PositionError``.
 
2394
    
 
2395
    Allowed quotes around keys as documented.
 
2396
    
 
2397
    Fixed bug with commas in comments. (matched as a list value)
 
2398
    
 
2399
    Beta 5
 
2400
    
 
2401
    2005/09/07
 
2402
    ----------
 
2403
    
 
2404
    Fixed bug in initialising ConfigObj from a ConfigObj.
 
2405
    
 
2406
    Changed the mailing list address.
 
2407
    
 
2408
    Beta 4
 
2409
    
 
2410
    2005/09/03
 
2411
    ----------
 
2412
    
 
2413
    Fixed bug in ``Section__delitem__`` oops.
 
2414
    
 
2415
    2005/08/28
 
2416
    ----------
 
2417
    
 
2418
    Interpolation is switched off before writing out files.
 
2419
    
 
2420
    Fixed bug in handling ``StringIO`` instances. (Thanks to report from
 
2421
    "Gustavo Niemeyer" <gustavo@niemeyer.net>)
 
2422
    
 
2423
    Moved the doctests from the ``__init__`` method to a separate function.
 
2424
    (For the sake of IDE calltips).
 
2425
    
 
2426
    Beta 3
 
2427
    
 
2428
    2005/08/26
 
2429
    ----------
 
2430
    
 
2431
    String values unchanged by validation *aren't* reset. This preserves
 
2432
    interpolation in string values.
 
2433
    
 
2434
    2005/08/18
 
2435
    ----------
 
2436
    
 
2437
    None from a default is turned to '' if stringify is off - because setting 
 
2438
    a value to None raises an error.
 
2439
    
 
2440
    Version 4.0.0-beta2
 
2441
    
 
2442
    2005/08/16
 
2443
    ----------
 
2444
    
 
2445
    By Nicola Larosa
 
2446
    
 
2447
    Actually added the RepeatSectionError class ;-)
 
2448
    
 
2449
    2005/08/15
 
2450
    ----------
 
2451
    
 
2452
    If ``stringify`` is off - list values are preserved by the ``validate``
 
2453
    method. (Bugfix)
 
2454
    
 
2455
    2005/08/14
 
2456
    ----------
 
2457
    
 
2458
    By Michael Foord
 
2459
    
 
2460
    Fixed ``simpleVal``.
 
2461
    
 
2462
    Added ``RepeatSectionError`` error if you have additional sections in a
 
2463
    section with a ``__many__`` (repeated) section.
 
2464
    
 
2465
    By Nicola Larosa
 
2466
    
 
2467
    Reworked the ConfigObj._parse, _handle_error and _multiline methods:
 
2468
    mutated the self._infile, self._index and self._maxline attributes into
 
2469
    local variables and method parameters
 
2470
    
 
2471
    Reshaped the ConfigObj._multiline method to better reflect its semantics
 
2472
    
 
2473
    Changed the "default_test" test in ConfigObj.validate to check the fix for
 
2474
    the bug in validate.Validator.check
 
2475
    
 
2476
    2005/08/13
 
2477
    ----------
 
2478
    
 
2479
    By Nicola Larosa
 
2480
    
 
2481
    Updated comments at top
 
2482
    
 
2483
    2005/08/11
 
2484
    ----------
 
2485
    
 
2486
    By Michael Foord
 
2487
    
 
2488
    Implemented repeated sections.
 
2489
    
 
2490
    By Nicola Larosa
 
2491
    
 
2492
    Added test for interpreter version: raises RuntimeError if earlier than
 
2493
    2.2
 
2494
    
 
2495
    2005/08/10
 
2496
    ----------
 
2497
   
 
2498
    By Michael Foord
 
2499
     
 
2500
    Implemented default values in configspecs.
 
2501
    
 
2502
    By Nicola Larosa
 
2503
    
 
2504
    Fixed naked except: clause in validate that was silencing the fact
 
2505
    that Python2.2 does not have dict.pop
 
2506
    
 
2507
    2005/08/08
 
2508
    ----------
 
2509
    
 
2510
    By Michael Foord
 
2511
    
 
2512
    Bug fix causing error if file didn't exist.
 
2513
    
 
2514
    2005/08/07
 
2515
    ----------
 
2516
    
 
2517
    By Nicola Larosa
 
2518
    
 
2519
    Adjusted doctests for Python 2.2.3 compatibility
 
2520
    
 
2521
    2005/08/04
 
2522
    ----------
 
2523
    
 
2524
    By Michael Foord
 
2525
    
 
2526
    Added the inline_comments attribute
 
2527
    
 
2528
    We now preserve and rewrite all comments in the config file
 
2529
    
 
2530
    configspec is now a section attribute
 
2531
    
 
2532
    The validate method changes values in place
 
2533
    
 
2534
    Added InterpolationError
 
2535
    
 
2536
    The errors now have line number, line, and message attributes. This
 
2537
    simplifies error handling
 
2538
    
 
2539
    Added __docformat__
 
2540
    
 
2541
    2005/08/03
 
2542
    ----------
 
2543
    
 
2544
    By Michael Foord
 
2545
    
 
2546
    Fixed bug in Section.pop (now doesn't raise KeyError if a default value
 
2547
    is specified)
 
2548
    
 
2549
    Replaced ``basestring`` with ``types.StringTypes``
 
2550
    
 
2551
    Removed the ``writein`` method
 
2552
    
 
2553
    Added __version__
 
2554
    
 
2555
    2005/07/29
 
2556
    ----------
 
2557
    
 
2558
    By Nicola Larosa
 
2559
    
 
2560
    Indentation in config file is not significant anymore, subsections are
 
2561
    designated by repeating square brackets
 
2562
    
 
2563
    Adapted all tests and docs to the new format
 
2564
    
 
2565
    2005/07/28
 
2566
    ----------
 
2567
    
 
2568
    By Nicola Larosa
 
2569
    
 
2570
    Added more tests
 
2571
    
 
2572
    2005/07/23
 
2573
    ----------
 
2574
    
 
2575
    By Nicola Larosa
 
2576
    
 
2577
    Reformatted final docstring in ReST format, indented it for easier folding
 
2578
    
 
2579
    Code tests converted to doctest format, and scattered them around
 
2580
    in various docstrings
 
2581
    
 
2582
    Walk method rewritten using scalars and sections attributes
 
2583
    
 
2584
    2005/07/22
 
2585
    ----------
 
2586
    
 
2587
    By Nicola Larosa
 
2588
    
 
2589
    Changed Validator and SimpleVal "test" methods to "check"
 
2590
    
 
2591
    More code cleanup
 
2592
    
 
2593
    2005/07/21
 
2594
    ----------
 
2595
    
 
2596
    Changed Section.sequence to Section.scalars and Section.sections
 
2597
    
 
2598
    Added Section.configspec
 
2599
    
 
2600
    Sections in the root section now have no extra indentation
 
2601
    
 
2602
    Comments now better supported in Section and preserved by ConfigObj
 
2603
    
 
2604
    Comments also written out
 
2605
    
 
2606
    Implemented initial_comment and final_comment
 
2607
    
 
2608
    A scalar value after a section will now raise an error
 
2609
    
 
2610
    2005/07/20
 
2611
    ----------
 
2612
    
 
2613
    Fixed a couple of bugs
 
2614
    
 
2615
    Can now pass a tuple instead of a list
 
2616
    
 
2617
    Simplified dict and walk methods
 
2618
    
 
2619
    Added __str__ to Section
 
2620
    
 
2621
    2005/07/10
 
2622
    ----------
 
2623
    
 
2624
    By Nicola Larosa
 
2625
    
 
2626
    More code cleanup
 
2627
    
 
2628
    2005/07/08
 
2629
    ----------
 
2630
    
 
2631
    The stringify option implemented. On by default.
 
2632
    
 
2633
    2005/07/07
 
2634
    ----------
 
2635
    
 
2636
    Renamed private attributes with a single underscore prefix.
 
2637
    
 
2638
    Changes to interpolation - exceeding recursion depth, or specifying a
 
2639
    missing value, now raise errors.
 
2640
    
 
2641
    Changes for Python 2.2 compatibility. (changed boolean tests - removed
 
2642
    ``is True`` and ``is False``)
 
2643
    
 
2644
    Added test for duplicate section and member (and fixed bug)
 
2645
    
 
2646
    2005/07/06
 
2647
    ----------
 
2648
    
 
2649
    By Nicola Larosa
 
2650
    
 
2651
    Code cleanup
 
2652
    
 
2653
    2005/07/02
 
2654
    ----------
 
2655
    
 
2656
    Version 0.1.0
 
2657
    
 
2658
    Now properly handles values including comments and lists.
 
2659
    
 
2660
    Better error handling.
 
2661
    
 
2662
    String interpolation.
 
2663
    
 
2664
    Some options implemented.
 
2665
    
 
2666
    You can pass a Section a dictionary to initialise it.
 
2667
    
 
2668
    Setting a Section member to a dictionary will create a Section instance.
 
2669
    
 
2670
    2005/06/26
 
2671
    ----------
 
2672
    
 
2673
    Version 0.0.1
 
2674
    
 
2675
    Experimental reader.
 
2676
    
 
2677
    A reasonably elegant implementation - a basic reader in 160 lines of code.
 
2678
    
 
2679
    *A programming language is a medium of expression.* - Paul Graham
 
2680
"""
 
2681