~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2005-05-02 04:32:56 UTC
  • Revision ID: mbp@sourcefrog.net-20050502043256-ca265e354b2c4e54
- better error message for broken pipe

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