~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Alexander Belchenko
  • Date: 2007-01-24 13:03:32 UTC
  • mto: This revision was merged to the branch mainline in revision 2242.
  • Revision ID: bialix@ukr.net-20070124130332-ane2eqz3eqrtm9u1
Use new API for testing

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
# http://www.voidspace.org.uk/python/configobj.html
 
9
 
 
10
# Released subject to the BSD License
 
11
# Please see http://www.voidspace.org.uk/python/license.shtml
 
12
 
 
13
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
 
14
# For information about bugfixes, updates and support, please join the
 
15
# ConfigObj mailing list:
 
16
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
 
17
# Comments, suggestions and bug reports welcome.
 
18
 
 
19
from __future__ import generators
 
20
 
 
21
"""
 
22
    >>> z = ConfigObj()
 
23
    >>> z['a'] = 'a'
 
24
    >>> z['sect'] = {
 
25
    ...    'subsect': {
 
26
    ...         'a': 'fish',
 
27
    ...         'b': 'wobble',
 
28
    ...     },
 
29
    ...     'member': 'value',
 
30
    ... }
 
31
    >>> x = ConfigObj(z.write())
 
32
    >>> z == x
 
33
    1
 
34
"""
 
35
 
 
36
import sys
 
37
INTP_VER = sys.version_info[:2]
 
38
if INTP_VER < (2, 2):
 
39
    raise RuntimeError("Python v.2.2 or later needed")
 
40
 
 
41
import os, re
 
42
from types import StringTypes
 
43
from warnings import warn
 
44
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
 
45
 
 
46
# A dictionary mapping BOM to
 
47
# the encoding to decode with, and what to set the
 
48
# encoding attribute to.
 
49
BOMS = {
 
50
    BOM_UTF8: ('utf_8', None),
 
51
    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
 
52
    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
 
53
    BOM_UTF16: ('utf_16', 'utf_16'),
 
54
    }
 
55
# All legal variants of the BOM codecs.
 
56
# TODO: the list of aliases is not meant to be exhaustive, is there a
 
57
#   better way ?
 
58
BOM_LIST = {
 
59
    'utf_16': 'utf_16',
 
60
    'u16': 'utf_16',
 
61
    'utf16': 'utf_16',
 
62
    'utf-16': 'utf_16',
 
63
    'utf16_be': 'utf16_be',
 
64
    'utf_16_be': 'utf16_be',
 
65
    'utf-16be': 'utf16_be',
 
66
    'utf16_le': 'utf16_le',
 
67
    'utf_16_le': 'utf16_le',
 
68
    'utf-16le': 'utf16_le',
 
69
    'utf_8': 'utf_8',
 
70
    'u8': 'utf_8',
 
71
    'utf': 'utf_8',
 
72
    'utf8': 'utf_8',
 
73
    'utf-8': 'utf_8',
 
74
    }
 
75
 
 
76
# Map of encodings to the BOM to write.
 
77
BOM_SET = {
 
78
    'utf_8': BOM_UTF8,
 
79
    'utf_16': BOM_UTF16,
 
80
    'utf16_be': BOM_UTF16_BE,
 
81
    'utf16_le': BOM_UTF16_LE,
 
82
    None: BOM_UTF8
 
83
    }
 
84
 
 
85
try:
 
86
    from validate import VdtMissingValue
 
87
except ImportError:
 
88
    VdtMissingValue = None
 
89
 
 
90
try:
 
91
    enumerate
 
92
except NameError:
 
93
    def enumerate(obj):
 
94
        """enumerate for Python 2.2."""
 
95
        i = -1
 
96
        for item in obj:
 
97
            i += 1
 
98
            yield i, item
 
99
 
 
100
try:
 
101
    True, False
 
102
except NameError:
 
103
    True, False = 1, 0
 
104
 
 
105
 
 
106
__version__ = '4.2.0beta2'
 
107
 
 
108
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
 
109
 
 
110
__docformat__ = "restructuredtext en"
 
111
 
 
112
# NOTE: Does it make sense to have the following in __all__ ?
 
113
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
 
114
# NOTE: If used as from configobj import...
 
115
# NOTE: They are effectively read only
 
116
__all__ = (
 
117
    '__version__',
 
118
    'DEFAULT_INDENT_TYPE',
 
119
    'NUM_INDENT_SPACES',
 
120
    'MAX_INTERPOL_DEPTH',
 
121
    'ConfigObjError',
 
122
    'NestingError',
 
123
    'ParseError',
 
124
    'DuplicateError',
 
125
    'ConfigspecError',
 
126
    'ConfigObj',
 
127
    'SimpleVal',
 
128
    'InterpolationError',
 
129
    'InterpolationDepthError',
 
130
    'MissingInterpolationOption',
 
131
    'RepeatSectionError',
 
132
    '__docformat__',
 
133
    'flatten_errors',
 
134
)
 
135
 
 
136
DEFAULT_INDENT_TYPE = ' '
 
137
NUM_INDENT_SPACES = 4
 
138
MAX_INTERPOL_DEPTH = 10
 
139
 
 
140
OPTION_DEFAULTS = {
 
141
    'interpolation': True,
 
142
    'raise_errors': False,
 
143
    'list_values': True,
 
144
    'create_empty': False,
 
145
    'file_error': False,
 
146
    'configspec': None,
 
147
    'stringify': True,
 
148
    # option may be set to one of ('', ' ', '\t')
 
149
    'indent_type': None,
 
150
    'encoding': None,
 
151
    'default_encoding': None,
 
152
}
 
153
 
 
154
class ConfigObjError(SyntaxError):
 
155
    """
 
156
    This is the base class for all errors that ConfigObj raises.
 
157
    It is a subclass of SyntaxError.
 
158
    
 
159
    >>> raise ConfigObjError
 
160
    Traceback (most recent call last):
 
161
    ConfigObjError
 
162
    """
 
163
    def __init__(self, message='', line_number=None, line=''):
 
164
        self.line = line
 
165
        self.line_number = line_number
 
166
        self.message = message
 
167
        SyntaxError.__init__(self, message)
 
168
 
 
169
class NestingError(ConfigObjError):
 
170
    """
 
171
    This error indicates a level of nesting that doesn't match.
 
172
    
 
173
    >>> raise NestingError
 
174
    Traceback (most recent call last):
 
175
    NestingError
 
176
    """
 
177
 
 
178
class ParseError(ConfigObjError):
 
179
    """
 
180
    This error indicates that a line is badly written.
 
181
    It is neither a valid ``key = value`` line,
 
182
    nor a valid section marker line.
 
183
    
 
184
    >>> raise ParseError
 
185
    Traceback (most recent call last):
 
186
    ParseError
 
187
    """
 
188
 
 
189
class DuplicateError(ConfigObjError):
 
190
    """
 
191
    The keyword or section specified already exists.
 
192
    
 
193
    >>> raise DuplicateError
 
194
    Traceback (most recent call last):
 
195
    DuplicateError
 
196
    """
 
197
 
 
198
class ConfigspecError(ConfigObjError):
 
199
    """
 
200
    An error occured whilst parsing a configspec.
 
201
    
 
202
    >>> raise ConfigspecError
 
203
    Traceback (most recent call last):
 
204
    ConfigspecError
 
205
    """
 
206
 
 
207
class InterpolationError(ConfigObjError):
 
208
    """Base class for the two interpolation errors."""
 
209
 
 
210
class InterpolationDepthError(InterpolationError):
 
211
    """Maximum interpolation depth exceeded in string interpolation."""
 
212
 
 
213
    def __init__(self, option):
 
214
        """
 
215
        >>> raise InterpolationDepthError('yoda')
 
216
        Traceback (most recent call last):
 
217
        InterpolationDepthError: max interpolation depth exceeded in value "yoda".
 
218
        """
 
219
        InterpolationError.__init__(
 
220
            self,
 
221
            'max interpolation depth exceeded in value "%s".' % option)
 
222
 
 
223
class RepeatSectionError(ConfigObjError):
 
224
    """
 
225
    This error indicates additional sections in a section with a
 
226
    ``__many__`` (repeated) section.
 
227
    
 
228
    >>> raise RepeatSectionError
 
229
    Traceback (most recent call last):
 
230
    RepeatSectionError
 
231
    """
 
232
 
 
233
class MissingInterpolationOption(InterpolationError):
 
234
    """A value specified for interpolation was missing."""
 
235
 
 
236
    def __init__(self, option):
 
237
        """
 
238
        >>> raise MissingInterpolationOption('yoda')
 
239
        Traceback (most recent call last):
 
240
        MissingInterpolationOption: missing option "yoda" in interpolation.
 
241
        """
 
242
        InterpolationError.__init__(
 
243
            self,
 
244
            'missing option "%s" in interpolation.' % option)
 
245
 
 
246
class Section(dict):
 
247
    """
 
248
    A dictionary-like object that represents a section in a config file.
 
249
    
 
250
    It does string interpolation if the 'interpolate' attribute
 
251
    of the 'main' object is set to True.
 
252
    
 
253
    Interpolation is tried first from the 'DEFAULT' section of this object,
 
254
    next from the 'DEFAULT' section of the parent, lastly the main object.
 
255
    
 
256
    A Section will behave like an ordered dictionary - following the
 
257
    order of the ``scalars`` and ``sections`` attributes.
 
258
    You can use this to change the order of members.
 
259
    
 
260
    Iteration follows the order: scalars, then sections.
 
261
    """
 
262
 
 
263
    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
 
264
 
 
265
    def __init__(self, parent, depth, main, indict=None, name=None):
 
266
        """
 
267
        * parent is the section above
 
268
        * depth is the depth level of this section
 
269
        * main is the main ConfigObj
 
270
        * indict is a dictionary to initialise the section with
 
271
        """
 
272
        if indict is None:
 
273
            indict = {}
 
274
        dict.__init__(self)
 
275
        # used for nesting level *and* interpolation
 
276
        self.parent = parent
 
277
        # used for the interpolation attribute
 
278
        self.main = main
 
279
        # level of nesting depth of this Section
 
280
        self.depth = depth
 
281
        # the sequence of scalar values in this Section
 
282
        self.scalars = []
 
283
        # the sequence of sections in this Section
 
284
        self.sections = []
 
285
        # purely for information
 
286
        self.name = name
 
287
        # for comments :-)
 
288
        self.comments = {}
 
289
        self.inline_comments = {}
 
290
        # for the configspec
 
291
        self.configspec = {}
 
292
        # for defaults
 
293
        self.defaults = []
 
294
        #
 
295
        # we do this explicitly so that __setitem__ is used properly
 
296
        # (rather than just passing to ``dict.__init__``)
 
297
        for entry in indict:
 
298
            self[entry] = indict[entry]
 
299
 
 
300
    def _interpolate(self, value):
 
301
        """Nicked from ConfigParser."""
 
302
        depth = MAX_INTERPOL_DEPTH
 
303
        # loop through this until it's done
 
304
        while depth:
 
305
            depth -= 1
 
306
            if value.find("%(") != -1:
 
307
                value = self._KEYCRE.sub(self._interpolation_replace, value)
 
308
            else:
 
309
                break
 
310
        else:
 
311
            raise InterpolationDepthError(value)
 
312
        return value
 
313
 
 
314
    def _interpolation_replace(self, match):
 
315
        """ """
 
316
        s = match.group(1)
 
317
        if s is None:
 
318
            return match.group()
 
319
        else:
 
320
            # switch off interpolation before we try and fetch anything !
 
321
            self.main.interpolation = False
 
322
            # try the 'DEFAULT' member of *this section* first
 
323
            val = self.get('DEFAULT', {}).get(s)
 
324
            # try the 'DEFAULT' member of the *parent section* next
 
325
            if val is None:
 
326
                val = self.parent.get('DEFAULT', {}).get(s)
 
327
            # last, try the 'DEFAULT' member of the *main section*
 
328
            if val is None:
 
329
                val = self.main.get('DEFAULT', {}).get(s)
 
330
            self.main.interpolation = True
 
331
            if val is None:
 
332
                raise MissingInterpolationOption(s)
 
333
            return val
 
334
 
 
335
    def __getitem__(self, key):
 
336
        """Fetch the item and do string interpolation."""
 
337
        val = dict.__getitem__(self, key)
 
338
        if self.main.interpolation and isinstance(val, StringTypes):
 
339
            return self._interpolate(val)
 
340
        return val
 
341
 
 
342
    def __setitem__(self, key, value):
 
343
        """
 
344
        Correctly set a value.
 
345
        
 
346
        Making dictionary values Section instances.
 
347
        (We have to special case 'Section' instances - which are also dicts)
 
348
        
 
349
        Keys must be strings.
 
350
        Values need only be strings (or lists of strings) if
 
351
        ``main.stringify`` is set.
 
352
        """
 
353
        if not isinstance(key, StringTypes):
 
354
            raise ValueError, 'The key "%s" is not a string.' % key
 
355
        # add the comment
 
356
        if key not in self.comments:
 
357
            self.comments[key] = []
 
358
            self.inline_comments[key] = ''
 
359
        # remove the entry from defaults
 
360
        if key in self.defaults:
 
361
            self.defaults.remove(key)
 
362
        #
 
363
        if isinstance(value, Section):
 
364
            if key not in self:
 
365
                self.sections.append(key)
 
366
            dict.__setitem__(self, key, value)
 
367
        elif isinstance(value, dict):
 
368
            # First create the new depth level,
 
369
            # then create the section
 
370
            if key not in self:
 
371
                self.sections.append(key)
 
372
            new_depth = self.depth + 1
 
373
            dict.__setitem__(
 
374
                self,
 
375
                key,
 
376
                Section(
 
377
                    self,
 
378
                    new_depth,
 
379
                    self.main,
 
380
                    indict=value,
 
381
                    name=key))
 
382
        else:
 
383
            if key not in self:
 
384
                self.scalars.append(key)
 
385
            if not self.main.stringify:
 
386
                if isinstance(value, StringTypes):
 
387
                    pass
 
388
                elif isinstance(value, (list, tuple)):
 
389
                    for entry in value:
 
390
                        if not isinstance(entry, StringTypes):
 
391
                            raise TypeError, (
 
392
                                'Value is not a string "%s".' % entry)
 
393
                else:
 
394
                    raise TypeError, 'Value is not a string "%s".' % value
 
395
            dict.__setitem__(self, key, value)
 
396
 
 
397
    def __delitem__(self, key):
 
398
        """Remove items from the sequence when deleting."""
 
399
        dict. __delitem__(self, key)
 
400
        if key in self.scalars:
 
401
            self.scalars.remove(key)
 
402
        else:
 
403
            self.sections.remove(key)
 
404
        del self.comments[key]
 
405
        del self.inline_comments[key]
 
406
 
 
407
    def get(self, key, default=None):
 
408
        """A version of ``get`` that doesn't bypass string interpolation."""
 
409
        try:
 
410
            return self[key]
 
411
        except KeyError:
 
412
            return default
 
413
 
 
414
    def update(self, indict):
 
415
        """
 
416
        A version of update that uses our ``__setitem__``.
 
417
        """
 
418
        for entry in indict:
 
419
            self[entry] = indict[entry]
 
420
 
 
421
 
 
422
    def pop(self, key, *args):
 
423
        """ """
 
424
        val = dict.pop(self, key, *args)
 
425
        if key in self.scalars:
 
426
            del self.comments[key]
 
427
            del self.inline_comments[key]
 
428
            self.scalars.remove(key)
 
429
        elif key in self.sections:
 
430
            del self.comments[key]
 
431
            del self.inline_comments[key]
 
432
            self.sections.remove(key)
 
433
        if self.main.interpolation and isinstance(val, StringTypes):
 
434
            return self._interpolate(val)
 
435
        return val
 
436
 
 
437
    def popitem(self):
 
438
        """Pops the first (key,val)"""
 
439
        sequence = (self.scalars + self.sections)
 
440
        if not sequence:
 
441
            raise KeyError, ": 'popitem(): dictionary is empty'"
 
442
        key = sequence[0]
 
443
        val =  self[key]
 
444
        del self[key]
 
445
        return key, val
 
446
 
 
447
    def clear(self):
 
448
        """
 
449
        A version of clear that also affects scalars/sections
 
450
        Also clears comments and configspec.
 
451
        
 
452
        Leaves other attributes alone :
 
453
            depth/main/parent are not affected
 
454
        """
 
455
        dict.clear(self)
 
456
        self.scalars = []
 
457
        self.sections = []
 
458
        self.comments = {}
 
459
        self.inline_comments = {}
 
460
        self.configspec = {}
 
461
 
 
462
    def setdefault(self, key, default=None):
 
463
        """A version of setdefault that sets sequence if appropriate."""
 
464
        try:
 
465
            return self[key]
 
466
        except KeyError:
 
467
            self[key] = default
 
468
            return self[key]
 
469
 
 
470
    def items(self):
 
471
        """ """
 
472
        return zip((self.scalars + self.sections), self.values())
 
473
 
 
474
    def keys(self):
 
475
        """ """
 
476
        return (self.scalars + self.sections)
 
477
 
 
478
    def values(self):
 
479
        """ """
 
480
        return [self[key] for key in (self.scalars + self.sections)]
 
481
 
 
482
    def iteritems(self):
 
483
        """ """
 
484
        return iter(self.items())
 
485
 
 
486
    def iterkeys(self):
 
487
        """ """
 
488
        return iter((self.scalars + self.sections))
 
489
 
 
490
    __iter__ = iterkeys
 
491
 
 
492
    def itervalues(self):
 
493
        """ """
 
494
        return iter(self.values())
 
495
 
 
496
    def __repr__(self):
 
497
        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
 
498
            for key in (self.scalars + self.sections)])
 
499
 
 
500
    __str__ = __repr__
 
501
 
 
502
    # Extra methods - not in a normal dictionary
 
503
 
 
504
    def dict(self):
 
505
        """
 
506
        Return a deepcopy of self as a dictionary.
 
507
        
 
508
        All members that are ``Section`` instances are recursively turned to
 
509
        ordinary dictionaries - by calling their ``dict`` method.
 
510
        
 
511
        >>> n = a.dict()
 
512
        >>> n == a
 
513
        1
 
514
        >>> n is a
 
515
        0
 
516
        """
 
517
        newdict = {}
 
518
        for entry in self:
 
519
            this_entry = self[entry]
 
520
            if isinstance(this_entry, Section):
 
521
                this_entry = this_entry.dict()
 
522
            elif isinstance(this_entry, (list, tuple)):
 
523
                # create a copy rather than a reference
 
524
                this_entry = list(this_entry)
 
525
            newdict[entry] = this_entry
 
526
        return newdict
 
527
 
 
528
    def merge(self, indict):
 
529
        """
 
530
        A recursive update - useful for merging config files.
 
531
        
 
532
        >>> a = '''[section1]
 
533
        ...     option1 = True
 
534
        ...     [[subsection]]
 
535
        ...     more_options = False
 
536
        ...     # end of file'''.splitlines()
 
537
        >>> b = '''# File is user.ini
 
538
        ...     [section1]
 
539
        ...     option1 = False
 
540
        ...     # end of file'''.splitlines()
 
541
        >>> c1 = ConfigObj(b)
 
542
        >>> c2 = ConfigObj(a)
 
543
        >>> c2.merge(c1)
 
544
        >>> c2
 
545
        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
 
546
        """
 
547
        for key, val in indict.items():
 
548
            if (key in self and isinstance(self[key], dict) and
 
549
                                isinstance(val, dict)):
 
550
                self[key].merge(val)
 
551
            else:   
 
552
                self[key] = val
 
553
 
 
554
    def rename(self, oldkey, newkey):
 
555
        """
 
556
        Change a keyname to another, without changing position in sequence.
 
557
        
 
558
        Implemented so that transformations can be made on keys,
 
559
        as well as on values. (used by encode and decode)
 
560
        
 
561
        Also renames comments.
 
562
        """
 
563
        if oldkey in self.scalars:
 
564
            the_list = self.scalars
 
565
        elif oldkey in self.sections:
 
566
            the_list = self.sections
 
567
        else:
 
568
            raise KeyError, 'Key "%s" not found.' % oldkey
 
569
        pos = the_list.index(oldkey)
 
570
        #
 
571
        val = self[oldkey]
 
572
        dict.__delitem__(self, oldkey)
 
573
        dict.__setitem__(self, newkey, val)
 
574
        the_list.remove(oldkey)
 
575
        the_list.insert(pos, newkey)
 
576
        comm = self.comments[oldkey]
 
577
        inline_comment = self.inline_comments[oldkey]
 
578
        del self.comments[oldkey]
 
579
        del self.inline_comments[oldkey]
 
580
        self.comments[newkey] = comm
 
581
        self.inline_comments[newkey] = inline_comment
 
582
 
 
583
    def walk(self, function, raise_errors=True,
 
584
            call_on_sections=False, **keywargs):
 
585
        """
 
586
        Walk every member and call a function on the keyword and value.
 
587
        
 
588
        Return a dictionary of the return values
 
589
        
 
590
        If the function raises an exception, raise the errror
 
591
        unless ``raise_errors=False``, in which case set the return value to
 
592
        ``False``.
 
593
        
 
594
        Any unrecognised keyword arguments you pass to walk, will be pased on
 
595
        to the function you pass in.
 
596
        
 
597
        Note: if ``call_on_sections`` is ``True`` then - on encountering a
 
598
        subsection, *first* the function is called for the *whole* subsection,
 
599
        and then recurses into it's members. This means your function must be
 
600
        able to handle strings, dictionaries and lists. This allows you
 
601
        to change the key of subsections as well as for ordinary members. The
 
602
        return value when called on the whole subsection has to be discarded.
 
603
        
 
604
        See  the encode and decode methods for examples, including functions.
 
605
        
 
606
        .. caution::
 
607
        
 
608
            You can use ``walk`` to transform the names of members of a section
 
609
            but you mustn't add or delete members.
 
610
        
 
611
        >>> config = '''[XXXXsection]
 
612
        ... XXXXkey = XXXXvalue'''.splitlines()
 
613
        >>> cfg = ConfigObj(config)
 
614
        >>> cfg
 
615
        {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
 
616
        >>> def transform(section, key):
 
617
        ...     val = section[key]
 
618
        ...     newkey = key.replace('XXXX', 'CLIENT1')
 
619
        ...     section.rename(key, newkey)
 
620
        ...     if isinstance(val, (tuple, list, dict)):
 
621
        ...         pass
 
622
        ...     else:
 
623
        ...         val = val.replace('XXXX', 'CLIENT1')
 
624
        ...         section[newkey] = val
 
625
        >>> cfg.walk(transform, call_on_sections=True)
 
626
        {'CLIENT1section': {'CLIENT1key': None}}
 
627
        >>> cfg
 
628
        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
 
629
        """
 
630
        out = {}
 
631
        # scalars first
 
632
        for i in range(len(self.scalars)):
 
633
            entry = self.scalars[i]
 
634
            try:
 
635
                val = function(self, entry, **keywargs)
 
636
                # bound again in case name has changed
 
637
                entry = self.scalars[i]
 
638
                out[entry] = val
 
639
            except Exception:
 
640
                if raise_errors:
 
641
                    raise
 
642
                else:
 
643
                    entry = self.scalars[i]
 
644
                    out[entry] = False
 
645
        # then sections
 
646
        for i in range(len(self.sections)):
 
647
            entry = self.sections[i]
 
648
            if call_on_sections:
 
649
                try:
 
650
                    function(self, entry, **keywargs)
 
651
                except Exception:
 
652
                    if raise_errors:
 
653
                        raise
 
654
                    else:
 
655
                        entry = self.sections[i]
 
656
                        out[entry] = False
 
657
                # bound again in case name has changed
 
658
                entry = self.sections[i]
 
659
            # previous result is discarded
 
660
            out[entry] = self[entry].walk(
 
661
                function,
 
662
                raise_errors=raise_errors,
 
663
                call_on_sections=call_on_sections,
 
664
                **keywargs)
 
665
        return out
 
666
 
 
667
    def decode(self, encoding):
 
668
        """
 
669
        Decode all strings and values to unicode, using the specified encoding.
 
670
        
 
671
        Works with subsections and list values.
 
672
        
 
673
        Uses the ``walk`` method.
 
674
        
 
675
        Testing ``encode`` and ``decode``.
 
676
        >>> m = ConfigObj(a)
 
677
        >>> m.decode('ascii')
 
678
        >>> def testuni(val):
 
679
        ...     for entry in val:
 
680
        ...         if not isinstance(entry, unicode):
 
681
        ...             print >> sys.stderr, type(entry)
 
682
        ...             raise AssertionError, 'decode failed.'
 
683
        ...         if isinstance(val[entry], dict):
 
684
        ...             testuni(val[entry])
 
685
        ...         elif not isinstance(val[entry], unicode):
 
686
        ...             raise AssertionError, 'decode failed.'
 
687
        >>> testuni(m)
 
688
        >>> m.encode('ascii')
 
689
        >>> a == m
 
690
        1
 
691
        """
 
692
        def decode(section, key, encoding=encoding):
 
693
            """ """
 
694
            val = section[key]
 
695
            if isinstance(val, (list, tuple)):
 
696
                newval = []
 
697
                for entry in val:
 
698
                    newval.append(entry.decode(encoding))
 
699
            elif isinstance(val, dict):
 
700
                newval = val
 
701
            else:
 
702
                newval = val.decode(encoding)
 
703
            newkey = key.decode(encoding)
 
704
            section.rename(key, newkey)
 
705
            section[newkey] = newval
 
706
        # using ``call_on_sections`` allows us to modify section names
 
707
        self.walk(decode, call_on_sections=True)
 
708
 
 
709
    def encode(self, encoding):
 
710
        """
 
711
        Encode all strings and values from unicode,
 
712
        using the specified encoding.
 
713
        
 
714
        Works with subsections and list values.
 
715
        Uses the ``walk`` method.
 
716
        """
 
717
        def encode(section, key, encoding=encoding):
 
718
            """ """
 
719
            val = section[key]
 
720
            if isinstance(val, (list, tuple)):
 
721
                newval = []
 
722
                for entry in val:
 
723
                    newval.append(entry.encode(encoding))
 
724
            elif isinstance(val, dict):
 
725
                newval = val
 
726
            else:
 
727
                newval = val.encode(encoding)
 
728
            newkey = key.encode(encoding)
 
729
            section.rename(key, newkey)
 
730
            section[newkey] = newval
 
731
        self.walk(encode, call_on_sections=True)
 
732
 
 
733
    def istrue(self, key):
 
734
        """A deprecated version of ``as_bool``."""
 
735
        warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
 
736
                'instead.', DeprecationWarning)
 
737
        return self.as_bool(key)
 
738
 
 
739
    def as_bool(self, key):
 
740
        """
 
741
        Accepts a key as input. The corresponding value must be a string or
 
742
        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
 
743
        retain compatibility with Python 2.2.
 
744
        
 
745
        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
 
746
        ``True``.
 
747
        
 
748
        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
 
749
        ``False``.
 
750
        
 
751
        ``as_bool`` is not case sensitive.
 
752
        
 
753
        Any other input will raise a ``ValueError``.
 
754
        
 
755
        >>> a = ConfigObj()
 
756
        >>> a['a'] = 'fish'
 
757
        >>> a.as_bool('a')
 
758
        Traceback (most recent call last):
 
759
        ValueError: Value "fish" is neither True nor False
 
760
        >>> a['b'] = 'True'
 
761
        >>> a.as_bool('b')
 
762
        1
 
763
        >>> a['b'] = 'off'
 
764
        >>> a.as_bool('b')
 
765
        0
 
766
        """
 
767
        val = self[key]
 
768
        if val == True:
 
769
            return True
 
770
        elif val == False:
 
771
            return False
 
772
        else:
 
773
            try:
 
774
                if not isinstance(val, StringTypes):
 
775
                    raise KeyError
 
776
                else:
 
777
                    return self.main._bools[val.lower()]
 
778
            except KeyError:
 
779
                raise ValueError('Value "%s" is neither True nor False' % val)
 
780
 
 
781
    def as_int(self, key):
 
782
        """
 
783
        A convenience method which coerces the specified value to an integer.
 
784
        
 
785
        If the value is an invalid literal for ``int``, a ``ValueError`` will
 
786
        be raised.
 
787
        
 
788
        >>> a = ConfigObj()
 
789
        >>> a['a'] = 'fish'
 
790
        >>> a.as_int('a')
 
791
        Traceback (most recent call last):
 
792
        ValueError: invalid literal for int(): fish
 
793
        >>> a['b'] = '1'
 
794
        >>> a.as_int('b')
 
795
        1
 
796
        >>> a['b'] = '3.2'
 
797
        >>> a.as_int('b')
 
798
        Traceback (most recent call last):
 
799
        ValueError: invalid literal for int(): 3.2
 
800
        """
 
801
        return int(self[key])
 
802
 
 
803
    def as_float(self, key):
 
804
        """
 
805
        A convenience method which coerces the specified value to a float.
 
806
        
 
807
        If the value is an invalid literal for ``float``, a ``ValueError`` will
 
808
        be raised.
 
809
        
 
810
        >>> a = ConfigObj()
 
811
        >>> a['a'] = 'fish'
 
812
        >>> a.as_float('a')
 
813
        Traceback (most recent call last):
 
814
        ValueError: invalid literal for float(): fish
 
815
        >>> a['b'] = '1'
 
816
        >>> a.as_float('b')
 
817
        1.0
 
818
        >>> a['b'] = '3.2'
 
819
        >>> a.as_float('b')
 
820
        3.2000000000000002
 
821
        """
 
822
        return float(self[key])
 
823
    
 
824
 
 
825
class ConfigObj(Section):
 
826
    """
 
827
    An object to read, create, and write config files.
 
828
    
 
829
    Testing with duplicate keys and sections.
 
830
    
 
831
    >>> c = '''
 
832
    ... [hello]
 
833
    ... member = value
 
834
    ... [hello again]
 
835
    ... member = value
 
836
    ... [ "hello" ]
 
837
    ... member = value
 
838
    ... '''
 
839
    >>> ConfigObj(c.split('\\n'), raise_errors = True)
 
840
    Traceback (most recent call last):
 
841
    DuplicateError: Duplicate section name at line 5.
 
842
    
 
843
    >>> d = '''
 
844
    ... [hello]
 
845
    ... member = value
 
846
    ... [hello again]
 
847
    ... member1 = value
 
848
    ... member2 = value
 
849
    ... 'member1' = value
 
850
    ... [ "and again" ]
 
851
    ... member = value
 
852
    ... '''
 
853
    >>> ConfigObj(d.split('\\n'), raise_errors = True)
 
854
    Traceback (most recent call last):
 
855
    DuplicateError: Duplicate keyword name at line 6.
 
856
    """
 
857
 
 
858
    _keyword = re.compile(r'''^ # line start
 
859
        (\s*)                   # indentation
 
860
        (                       # keyword
 
861
            (?:".*?")|          # double quotes
 
862
            (?:'.*?')|          # single quotes
 
863
            (?:[^'"=].*?)       # no quotes
 
864
        )
 
865
        \s*=\s*                 # divider
 
866
        (.*)                    # value (including list values and comments)
 
867
        $   # line end
 
868
        ''',
 
869
        re.VERBOSE)
 
870
 
 
871
    _sectionmarker = re.compile(r'''^
 
872
        (\s*)                     # 1: indentation
 
873
        ((?:\[\s*)+)              # 2: section marker open
 
874
        (                         # 3: section name open
 
875
            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
 
876
            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
 
877
            (?:[^'"\s].*?)        # at least one non-space unquoted
 
878
        )                         # section name close
 
879
        ((?:\s*\])+)              # 4: section marker close
 
880
        \s*(\#.*)?                # 5: optional comment
 
881
        $''',
 
882
        re.VERBOSE)
 
883
 
 
884
    # this regexp pulls list values out as a single string
 
885
    # or single values and comments
 
886
    _valueexp = re.compile(r'''^
 
887
        (?:
 
888
            (?:
 
889
                (
 
890
                    (?:
 
891
                        (?:
 
892
                            (?:".*?")|              # double quotes
 
893
                            (?:'.*?')|              # single quotes
 
894
                            (?:[^'",\#][^,\#]*?)       # unquoted
 
895
                        )
 
896
                        \s*,\s*                     # comma
 
897
                    )*      # match all list items ending in a comma (if any)
 
898
                )
 
899
                (
 
900
                    (?:".*?")|                      # double quotes
 
901
                    (?:'.*?')|                      # single quotes
 
902
                    (?:[^'",\#\s][^,]*?)             # unquoted
 
903
                )?          # last item in a list - or string value
 
904
            )|
 
905
            (,)             # alternatively a single comma - empty list
 
906
        )
 
907
        \s*(\#.*)?          # optional comment
 
908
        $''',
 
909
        re.VERBOSE)
 
910
 
 
911
    # use findall to get the members of a list value
 
912
    _listvalueexp = re.compile(r'''
 
913
        (
 
914
            (?:".*?")|          # double quotes
 
915
            (?:'.*?')|          # single quotes
 
916
            (?:[^'",\#].*?)       # unquoted
 
917
        )
 
918
        \s*,\s*                 # comma
 
919
        ''',
 
920
        re.VERBOSE)
 
921
 
 
922
    # this regexp is used for the value
 
923
    # when lists are switched off
 
924
    _nolistvalue = re.compile(r'''^
 
925
        (
 
926
            (?:".*?")|          # double quotes
 
927
            (?:'.*?')|          # single quotes
 
928
            (?:[^'"\#].*?)      # unquoted
 
929
        )
 
930
        \s*(\#.*)?              # optional comment
 
931
        $''',
 
932
        re.VERBOSE)
 
933
 
 
934
    # regexes for finding triple quoted values on one line
 
935
    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
 
936
    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
 
937
    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
 
938
    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
 
939
 
 
940
    _triple_quote = {
 
941
        "'''": (_single_line_single, _multi_line_single),
 
942
        '"""': (_single_line_double, _multi_line_double),
 
943
    }
 
944
 
 
945
    # Used by the ``istrue`` Section method
 
946
    _bools = {
 
947
        'yes': True, 'no': False,
 
948
        'on': True, 'off': False,
 
949
        '1': True, '0': False,
 
950
        'true': True, 'false': False,
 
951
        }
 
952
 
 
953
    def __init__(self, infile=None, options=None, **kwargs):
 
954
        """
 
955
        Parse or create a config file object.
 
956
        
 
957
        ``ConfigObj(infile=None, options=None, **kwargs)``
 
958
        """
 
959
        if infile is None:
 
960
            infile = []
 
961
        if options is None:
 
962
            options = {}
 
963
        # keyword arguments take precedence over an options dictionary
 
964
        options.update(kwargs)
 
965
        # init the superclass
 
966
        Section.__init__(self, self, 0, self)
 
967
        #
 
968
        defaults = OPTION_DEFAULTS.copy()
 
969
        for entry in options.keys():
 
970
            if entry not in defaults.keys():
 
971
                raise TypeError, 'Unrecognised option "%s".' % entry
 
972
        # TODO: check the values too.
 
973
        #
 
974
        # Add any explicit options to the defaults
 
975
        defaults.update(options)
 
976
        #
 
977
        # initialise a few variables
 
978
        self.filename = None
 
979
        self._errors = []
 
980
        self.raise_errors = defaults['raise_errors']
 
981
        self.interpolation = defaults['interpolation']
 
982
        self.list_values = defaults['list_values']
 
983
        self.create_empty = defaults['create_empty']
 
984
        self.file_error = defaults['file_error']
 
985
        self.stringify = defaults['stringify']
 
986
        self.indent_type = defaults['indent_type']
 
987
        self.encoding = defaults['encoding']
 
988
        self.default_encoding = defaults['default_encoding']
 
989
        self.BOM = False
 
990
        self.newlines = None
 
991
        #
 
992
        self.initial_comment = []
 
993
        self.final_comment = []
 
994
        #
 
995
        if isinstance(infile, StringTypes):
 
996
            self.filename = infile
 
997
            if os.path.isfile(infile):
 
998
                infile = open(infile).read() or []
 
999
            elif self.file_error:
 
1000
                # raise an error if the file doesn't exist
 
1001
                raise IOError, 'Config file not found: "%s".' % self.filename
 
1002
            else:
 
1003
                # file doesn't already exist
 
1004
                if self.create_empty:
 
1005
                    # this is a good test that the filename specified
 
1006
                    # isn't impossible - like on a non existent device
 
1007
                    h = open(infile, 'w')
 
1008
                    h.write('')
 
1009
                    h.close()
 
1010
                infile = []
 
1011
        elif isinstance(infile, (list, tuple)):
 
1012
            infile = list(infile)
 
1013
        elif isinstance(infile, dict):
 
1014
            # initialise self
 
1015
            # the Section class handles creating subsections
 
1016
            if isinstance(infile, ConfigObj):
 
1017
                # get a copy of our ConfigObj
 
1018
                infile = infile.dict()
 
1019
            for entry in infile:
 
1020
                self[entry] = infile[entry]
 
1021
            del self._errors
 
1022
            if defaults['configspec'] is not None:
 
1023
                self._handle_configspec(defaults['configspec'])
 
1024
            else:
 
1025
                self.configspec = None
 
1026
            return
 
1027
        elif getattr(infile, 'read', None) is not None:
 
1028
            # This supports file like objects
 
1029
            infile = infile.read() or []
 
1030
            # needs splitting into lines - but needs doing *after* decoding
 
1031
            # in case it's not an 8 bit encoding
 
1032
        else:
 
1033
            raise TypeError, ('infile must be a filename,'
 
1034
                ' file like object, or list of lines.')
 
1035
        #
 
1036
        if infile:
 
1037
            # don't do it for the empty ConfigObj
 
1038
            infile = self._handle_bom(infile)
 
1039
            # infile is now *always* a list
 
1040
            #
 
1041
            # Set the newlines attribute (first line ending it finds)
 
1042
            # and strip trailing '\n' or '\r' from lines
 
1043
            for line in infile:
 
1044
                if (not line) or (line[-1] not in '\r\n'):
 
1045
                    continue
 
1046
                for end in ('\r\n', '\n', '\r'):
 
1047
                    if line.endswith(end):
 
1048
                        self.newlines = end
 
1049
                        break
 
1050
                break
 
1051
            infile = [line.rstrip('\r\n') for line in infile]
 
1052
        #
 
1053
        self._parse(infile)
 
1054
        # if we had any errors, now is the time to raise them
 
1055
        if self._errors:
 
1056
            error = ConfigObjError("Parsing failed.")
 
1057
            # set the errors attribute; it's a list of tuples:
 
1058
            # (error_type, message, line_number)
 
1059
            error.errors = self._errors
 
1060
            # set the config attribute
 
1061
            error.config = self
 
1062
            raise error
 
1063
        # delete private attributes
 
1064
        del self._errors
 
1065
        #
 
1066
        if defaults['configspec'] is None:
 
1067
            self.configspec = None
 
1068
        else:
 
1069
            self._handle_configspec(defaults['configspec'])
 
1070
 
 
1071
    def _handle_bom(self, infile):
 
1072
        """
 
1073
        Handle any BOM, and decode if necessary.
 
1074
        
 
1075
        If an encoding is specified, that *must* be used - but the BOM should
 
1076
        still be removed (and the BOM attribute set).
 
1077
        
 
1078
        (If the encoding is wrongly specified, then a BOM for an alternative
 
1079
        encoding won't be discovered or removed.)
 
1080
        
 
1081
        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
 
1082
        removed. The BOM attribute will be set. UTF16 will be decoded to
 
1083
        unicode.
 
1084
        
 
1085
        NOTE: This method must not be called with an empty ``infile``.
 
1086
        
 
1087
        Specifying the *wrong* encoding is likely to cause a
 
1088
        ``UnicodeDecodeError``.
 
1089
        
 
1090
        ``infile`` must always be returned as a list of lines, but may be
 
1091
        passed in as a single string.
 
1092
        """
 
1093
        if ((self.encoding is not None) and
 
1094
            (self.encoding.lower() not in BOM_LIST)):
 
1095
            # No need to check for a BOM
 
1096
            # encoding specified doesn't have one
 
1097
            # just decode
 
1098
            return self._decode(infile, self.encoding)
 
1099
        #
 
1100
        if isinstance(infile, (list, tuple)):
 
1101
            line = infile[0]
 
1102
        else:
 
1103
            line = infile
 
1104
        if self.encoding is not None:
 
1105
            # encoding explicitly supplied
 
1106
            # And it could have an associated BOM
 
1107
            # TODO: if encoding is just UTF16 - we ought to check for both
 
1108
            # TODO: big endian and little endian versions.
 
1109
            enc = BOM_LIST[self.encoding.lower()]
 
1110
            if enc == 'utf_16':
 
1111
                # For UTF16 we try big endian and little endian
 
1112
                for BOM, (encoding, final_encoding) in BOMS.items():
 
1113
                    if not final_encoding:
 
1114
                        # skip UTF8
 
1115
                        continue
 
1116
                    if infile.startswith(BOM):
 
1117
                        ### BOM discovered
 
1118
                        ##self.BOM = True
 
1119
                        # Don't need to remove BOM
 
1120
                        return self._decode(infile, encoding)
 
1121
                #
 
1122
                # If we get this far, will *probably* raise a DecodeError
 
1123
                # As it doesn't appear to start with a BOM
 
1124
                return self._decode(infile, self.encoding)
 
1125
            #
 
1126
            # Must be UTF8
 
1127
            BOM = BOM_SET[enc]
 
1128
            if not line.startswith(BOM):
 
1129
                return self._decode(infile, self.encoding)
 
1130
            #
 
1131
            newline = line[len(BOM):]
 
1132
            #
 
1133
            # BOM removed
 
1134
            if isinstance(infile, (list, tuple)):
 
1135
                infile[0] = newline
 
1136
            else:
 
1137
                infile = newline
 
1138
            self.BOM = True
 
1139
            return self._decode(infile, self.encoding)
 
1140
        #
 
1141
        # No encoding specified - so we need to check for UTF8/UTF16
 
1142
        for BOM, (encoding, final_encoding) in BOMS.items():
 
1143
            if not line.startswith(BOM):
 
1144
                continue
 
1145
            else:
 
1146
                # BOM discovered
 
1147
                self.encoding = final_encoding
 
1148
                if not final_encoding:
 
1149
                    self.BOM = True
 
1150
                    # UTF8
 
1151
                    # remove BOM
 
1152
                    newline = line[len(BOM):]
 
1153
                    if isinstance(infile, (list, tuple)):
 
1154
                        infile[0] = newline
 
1155
                    else:
 
1156
                        infile = newline
 
1157
                    # UTF8 - don't decode
 
1158
                    if isinstance(infile, StringTypes):
 
1159
                        return infile.splitlines(True)
 
1160
                    else:
 
1161
                        return infile
 
1162
                # UTF16 - have to decode
 
1163
                return self._decode(infile, encoding)
 
1164
        #
 
1165
        # No BOM discovered and no encoding specified, just return
 
1166
        if isinstance(infile, StringTypes):
 
1167
            # infile read from a file will be a single string
 
1168
            return infile.splitlines(True)
 
1169
        else:
 
1170
            return infile
 
1171
 
 
1172
    def _a_to_u(self, string):
 
1173
        """Decode ascii strings to unicode if a self.encoding is specified."""
 
1174
        if not self.encoding:
 
1175
            return string
 
1176
        else:
 
1177
            return string.decode('ascii')
 
1178
 
 
1179
    def _decode(self, infile, encoding):
 
1180
        """
 
1181
        Decode infile to unicode. Using the specified encoding.
 
1182
        
 
1183
        if is a string, it also needs converting to a list.
 
1184
        """
 
1185
        if isinstance(infile, StringTypes):
 
1186
            # can't be unicode
 
1187
            # NOTE: Could raise a ``UnicodeDecodeError``
 
1188
            return infile.decode(encoding).splitlines(True)
 
1189
        for i, line in enumerate(infile):
 
1190
            if not isinstance(line, unicode):
 
1191
                # NOTE: The isinstance test here handles mixed lists of unicode/string
 
1192
                # NOTE: But the decode will break on any non-string values
 
1193
                # NOTE: Or could raise a ``UnicodeDecodeError``
 
1194
                infile[i] = line.decode(encoding)
 
1195
        return infile
 
1196
 
 
1197
    def _decode_element(self, line):
 
1198
        """Decode element to unicode if necessary."""
 
1199
        if not self.encoding:
 
1200
            return line
 
1201
        if isinstance(line, str) and self.default_encoding:
 
1202
            return line.decode(self.default_encoding)
 
1203
        return line
 
1204
 
 
1205
    def _str(self, value):
 
1206
        """
 
1207
        Used by ``stringify`` within validate, to turn non-string values
 
1208
        into strings.
 
1209
        """
 
1210
        if not isinstance(value, StringTypes):
 
1211
            return str(value)
 
1212
        else:
 
1213
            return value
 
1214
 
 
1215
    def _parse(self, infile):
 
1216
        """
 
1217
        Actually parse the config file
 
1218
        
 
1219
        Testing Interpolation
 
1220
        
 
1221
        >>> c = ConfigObj()
 
1222
        >>> c['DEFAULT'] = {
 
1223
        ...     'b': 'goodbye',
 
1224
        ...     'userdir': 'c:\\\\home',
 
1225
        ...     'c': '%(d)s',
 
1226
        ...     'd': '%(c)s'
 
1227
        ... }
 
1228
        >>> c['section'] = {
 
1229
        ...     'a': '%(datadir)s\\\\some path\\\\file.py',
 
1230
        ...     'b': '%(userdir)s\\\\some path\\\\file.py',
 
1231
        ...     'c': 'Yo %(a)s',
 
1232
        ...     'd': '%(not_here)s',
 
1233
        ...     'e': '%(c)s',
 
1234
        ... }
 
1235
        >>> c['section']['DEFAULT'] = {
 
1236
        ...     'datadir': 'c:\\\\silly_test',
 
1237
        ...     'a': 'hello - %(b)s',
 
1238
        ... }
 
1239
        >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
 
1240
        1
 
1241
        >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
 
1242
        1
 
1243
        >>> c['section']['c'] == 'Yo hello - goodbye'
 
1244
        1
 
1245
        
 
1246
        Switching Interpolation Off
 
1247
        
 
1248
        >>> c.interpolation = False
 
1249
        >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
 
1250
        1
 
1251
        >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
 
1252
        1
 
1253
        >>> c['section']['c'] == 'Yo %(a)s'
 
1254
        1
 
1255
        
 
1256
        Testing the interpolation errors.
 
1257
        
 
1258
        >>> c.interpolation = True
 
1259
        >>> c['section']['d']
 
1260
        Traceback (most recent call last):
 
1261
        MissingInterpolationOption: missing option "not_here" in interpolation.
 
1262
        >>> c['section']['e']
 
1263
        Traceback (most recent call last):
 
1264
        InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
 
1265
        
 
1266
        Testing our quoting.
 
1267
        
 
1268
        >>> i._quote('\"""\'\'\'')
 
1269
        Traceback (most recent call last):
 
1270
        SyntaxError: EOF while scanning triple-quoted string
 
1271
        >>> try:
 
1272
        ...     i._quote('\\n', multiline=False)
 
1273
        ... except ConfigObjError, e:
 
1274
        ...    e.msg
 
1275
        'Value "\\n" cannot be safely quoted.'
 
1276
        >>> k._quote(' "\' ', multiline=False)
 
1277
        Traceback (most recent call last):
 
1278
        SyntaxError: EOL while scanning single-quoted string
 
1279
        
 
1280
        Testing with "stringify" off.
 
1281
        >>> c.stringify = False
 
1282
        >>> c['test'] = 1
 
1283
        Traceback (most recent call last):
 
1284
        TypeError: Value is not a string "1".
 
1285
        """
 
1286
        comment_list = []
 
1287
        done_start = False
 
1288
        this_section = self
 
1289
        maxline = len(infile) - 1
 
1290
        cur_index = -1
 
1291
        reset_comment = False
 
1292
        while cur_index < maxline:
 
1293
            if reset_comment:
 
1294
                comment_list = []
 
1295
            cur_index += 1
 
1296
            line = infile[cur_index]
 
1297
            sline = line.strip()
 
1298
            # do we have anything on the line ?
 
1299
            if not sline or sline.startswith('#'):
 
1300
                reset_comment = False
 
1301
                comment_list.append(line)
 
1302
                continue
 
1303
            if not done_start:
 
1304
                # preserve initial comment
 
1305
                self.initial_comment = comment_list
 
1306
                comment_list = []
 
1307
                done_start = True
 
1308
            reset_comment = True
 
1309
            # first we check if it's a section marker
 
1310
            mat = self._sectionmarker.match(line)
 
1311
##            print >> sys.stderr, sline, mat
 
1312
            if mat is not None:
 
1313
                # is a section line
 
1314
                (indent, sect_open, sect_name, sect_close, comment) = (
 
1315
                    mat.groups())
 
1316
                if indent and (self.indent_type is None):
 
1317
                    self.indent_type = indent[0]
 
1318
                cur_depth = sect_open.count('[')
 
1319
                if cur_depth != sect_close.count(']'):
 
1320
                    self._handle_error(
 
1321
                        "Cannot compute the section depth at line %s.",
 
1322
                        NestingError, infile, cur_index)
 
1323
                    continue
 
1324
                if cur_depth < this_section.depth:
 
1325
                    # the new section is dropping back to a previous level
 
1326
                    try:
 
1327
                        parent = self._match_depth(
 
1328
                            this_section,
 
1329
                            cur_depth).parent
 
1330
                    except SyntaxError:
 
1331
                        self._handle_error(
 
1332
                            "Cannot compute nesting level at line %s.",
 
1333
                            NestingError, infile, cur_index)
 
1334
                        continue
 
1335
                elif cur_depth == this_section.depth:
 
1336
                    # the new section is a sibling of the current section
 
1337
                    parent = this_section.parent
 
1338
                elif cur_depth == this_section.depth + 1:
 
1339
                    # the new section is a child the current section
 
1340
                    parent = this_section
 
1341
                else:
 
1342
                    self._handle_error(
 
1343
                        "Section too nested at line %s.",
 
1344
                        NestingError, infile, cur_index)
 
1345
                #
 
1346
                sect_name = self._unquote(sect_name)
 
1347
                if sect_name in parent:
 
1348
##                    print >> sys.stderr, sect_name
 
1349
                    self._handle_error(
 
1350
                        'Duplicate section name at line %s.',
 
1351
                        DuplicateError, infile, cur_index)
 
1352
                    continue
 
1353
                # create the new section
 
1354
                this_section = Section(
 
1355
                    parent,
 
1356
                    cur_depth,
 
1357
                    self,
 
1358
                    name=sect_name)
 
1359
                parent[sect_name] = this_section
 
1360
                parent.inline_comments[sect_name] = comment
 
1361
                parent.comments[sect_name] = comment_list
 
1362
##                print >> sys.stderr, parent[sect_name] is this_section
 
1363
                continue
 
1364
            #
 
1365
            # it's not a section marker,
 
1366
            # so it should be a valid ``key = value`` line
 
1367
            mat = self._keyword.match(line)
 
1368
##            print >> sys.stderr, sline, mat
 
1369
            if mat is not None:
 
1370
                # is a keyword value
 
1371
                # value will include any inline comment
 
1372
                (indent, key, value) = mat.groups()
 
1373
                if indent and (self.indent_type is None):
 
1374
                    self.indent_type = indent[0]
 
1375
                # check for a multiline value
 
1376
                if value[:3] in ['"""', "'''"]:
 
1377
                    try:
 
1378
                        (value, comment, cur_index) = self._multiline(
 
1379
                            value, infile, cur_index, maxline)
 
1380
                    except SyntaxError:
 
1381
                        self._handle_error(
 
1382
                            'Parse error in value at line %s.',
 
1383
                            ParseError, infile, cur_index)
 
1384
                        continue
 
1385
                else:
 
1386
                    # extract comment and lists
 
1387
                    try:
 
1388
                        (value, comment) = self._handle_value(value)
 
1389
                    except SyntaxError:
 
1390
                        self._handle_error(
 
1391
                            'Parse error in value at line %s.',
 
1392
                            ParseError, infile, cur_index)
 
1393
                        continue
 
1394
                #
 
1395
##                print >> sys.stderr, sline
 
1396
                key = self._unquote(key)
 
1397
                if key in this_section:
 
1398
                    self._handle_error(
 
1399
                        'Duplicate keyword name at line %s.',
 
1400
                        DuplicateError, infile, cur_index)
 
1401
                    continue
 
1402
                # add the key
 
1403
##                print >> sys.stderr, this_section.name
 
1404
                this_section[key] = value
 
1405
                this_section.inline_comments[key] = comment
 
1406
                this_section.comments[key] = comment_list
 
1407
##                print >> sys.stderr, key, this_section[key]
 
1408
##                if this_section.name is not None:
 
1409
##                    print >> sys.stderr, this_section
 
1410
##                    print >> sys.stderr, this_section.parent
 
1411
##                    print >> sys.stderr, this_section.parent[this_section.name]
 
1412
                continue
 
1413
            #
 
1414
            # it neither matched as a keyword
 
1415
            # or a section marker
 
1416
            self._handle_error(
 
1417
                'Invalid line at line "%s".',
 
1418
                ParseError, infile, cur_index)
 
1419
        if self.indent_type is None:
 
1420
            # no indentation used, set the type accordingly
 
1421
            self.indent_type = ''
 
1422
        # preserve the final comment
 
1423
        if not self and not self.initial_comment:
 
1424
            self.initial_comment = comment_list
 
1425
        else:
 
1426
            self.final_comment = comment_list
 
1427
 
 
1428
    def _match_depth(self, sect, depth):
 
1429
        """
 
1430
        Given a section and a depth level, walk back through the sections
 
1431
        parents to see if the depth level matches a previous section.
 
1432
        
 
1433
        Return a reference to the right section,
 
1434
        or raise a SyntaxError.
 
1435
        """
 
1436
        while depth < sect.depth:
 
1437
            if sect is sect.parent:
 
1438
                # we've reached the top level already
 
1439
                raise SyntaxError
 
1440
            sect = sect.parent
 
1441
        if sect.depth == depth:
 
1442
            return sect
 
1443
        # shouldn't get here
 
1444
        raise SyntaxError
 
1445
 
 
1446
    def _handle_error(self, text, ErrorClass, infile, cur_index):
 
1447
        """
 
1448
        Handle an error according to the error settings.
 
1449
        
 
1450
        Either raise the error or store it.
 
1451
        The error will have occured at ``cur_index``
 
1452
        """
 
1453
        line = infile[cur_index]
 
1454
        message = text % cur_index
 
1455
        error = ErrorClass(message, cur_index, line)
 
1456
        if self.raise_errors:
 
1457
            # raise the error - parsing stops here
 
1458
            raise error
 
1459
        # store the error
 
1460
        # reraise when parsing has finished
 
1461
        self._errors.append(error)
 
1462
 
 
1463
    def _unquote(self, value):
 
1464
        """Return an unquoted version of a value"""
 
1465
        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
 
1466
            value = value[1:-1]
 
1467
        return value
 
1468
 
 
1469
    def _quote(self, value, multiline=True):
 
1470
        """
 
1471
        Return a safely quoted version of a value.
 
1472
        
 
1473
        Raise a ConfigObjError if the value cannot be safely quoted.
 
1474
        If multiline is ``True`` (default) then use triple quotes
 
1475
        if necessary.
 
1476
        
 
1477
        Don't quote values that don't need it.
 
1478
        Recursively quote members of a list and return a comma joined list.
 
1479
        Multiline is ``False`` for lists.
 
1480
        Obey list syntax for empty and single member lists.
 
1481
        
 
1482
        If ``list_values=False`` then the value is only quoted if it contains
 
1483
        a ``\n`` (is multiline).
 
1484
        """
 
1485
        if isinstance(value, (list, tuple)):
 
1486
            if not value:
 
1487
                return ','
 
1488
            elif len(value) == 1:
 
1489
                return self._quote(value[0], multiline=False) + ','
 
1490
            return ', '.join([self._quote(val, multiline=False)
 
1491
                for val in value])
 
1492
        if not isinstance(value, StringTypes):
 
1493
            if self.stringify:
 
1494
                value = str(value)
 
1495
            else:
 
1496
                raise TypeError, 'Value "%s" is not a string.' % value
 
1497
        squot = "'%s'"
 
1498
        dquot = '"%s"'
 
1499
        noquot = "%s"
 
1500
        wspace_plus = ' \r\t\n\v\t\'"'
 
1501
        tsquot = '"""%s"""'
 
1502
        tdquot = "'''%s'''"
 
1503
        if not value:
 
1504
            return '""'
 
1505
        if (not self.list_values and '\n' not in value) or not (multiline and
 
1506
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
 
1507
            if not self.list_values:
 
1508
                # we don't quote if ``list_values=False``
 
1509
                quot = noquot
 
1510
            # for normal values either single or double quotes will do
 
1511
            elif '\n' in value:
 
1512
                # will only happen if multiline is off - e.g. '\n' in key
 
1513
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
 
1514
                    value)
 
1515
            elif ((value[0] not in wspace_plus) and
 
1516
                    (value[-1] not in wspace_plus) and
 
1517
                    (',' not in value)):
 
1518
                quot = noquot
 
1519
            else:
 
1520
                if ("'" in value) and ('"' in value):
 
1521
                    raise ConfigObjError, (
 
1522
                        'Value "%s" cannot be safely quoted.' % value)
 
1523
                elif '"' in value:
 
1524
                    quot = squot
 
1525
                else:
 
1526
                    quot = dquot
 
1527
        else:
 
1528
            # if value has '\n' or "'" *and* '"', it will need triple quotes
 
1529
            if (value.find('"""') != -1) and (value.find("'''") != -1):
 
1530
                raise ConfigObjError, (
 
1531
                    'Value "%s" cannot be safely quoted.' % value)
 
1532
            if value.find('"""') == -1:
 
1533
                quot = tdquot
 
1534
            else:
 
1535
                quot = tsquot
 
1536
        return quot % value
 
1537
 
 
1538
    def _handle_value(self, value):
 
1539
        """
 
1540
        Given a value string, unquote, remove comment,
 
1541
        handle lists. (including empty and single member lists)
 
1542
        
 
1543
        Testing list values.
 
1544
        
 
1545
        >>> testconfig3 = '''
 
1546
        ... a = ,
 
1547
        ... b = test,
 
1548
        ... c = test1, test2   , test3
 
1549
        ... d = test1, test2, test3,
 
1550
        ... '''
 
1551
        >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
 
1552
        >>> d['a'] == []
 
1553
        1
 
1554
        >>> d['b'] == ['test']
 
1555
        1
 
1556
        >>> d['c'] == ['test1', 'test2', 'test3']
 
1557
        1
 
1558
        >>> d['d'] == ['test1', 'test2', 'test3']
 
1559
        1
 
1560
        
 
1561
        Testing with list values off.
 
1562
        
 
1563
        >>> e = ConfigObj(
 
1564
        ...     testconfig3.split('\\n'),
 
1565
        ...     raise_errors=True,
 
1566
        ...     list_values=False)
 
1567
        >>> e['a'] == ','
 
1568
        1
 
1569
        >>> e['b'] == 'test,'
 
1570
        1
 
1571
        >>> e['c'] == 'test1, test2   , test3'
 
1572
        1
 
1573
        >>> e['d'] == 'test1, test2, test3,'
 
1574
        1
 
1575
        
 
1576
        Testing creating from a dictionary.
 
1577
        
 
1578
        >>> f = {
 
1579
        ...     'key1': 'val1',
 
1580
        ...     'key2': 'val2',
 
1581
        ...     'section 1': {
 
1582
        ...         'key1': 'val1',
 
1583
        ...         'key2': 'val2',
 
1584
        ...         'section 1b': {
 
1585
        ...             'key1': 'val1',
 
1586
        ...             'key2': 'val2',
 
1587
        ...         },
 
1588
        ...     },
 
1589
        ...     'section 2': {
 
1590
        ...         'key1': 'val1',
 
1591
        ...         'key2': 'val2',
 
1592
        ...         'section 2b': {
 
1593
        ...             'key1': 'val1',
 
1594
        ...             'key2': 'val2',
 
1595
        ...         },
 
1596
        ...     },
 
1597
        ...      'key3': 'val3',
 
1598
        ... }
 
1599
        >>> g = ConfigObj(f)
 
1600
        >>> f == g
 
1601
        1
 
1602
        
 
1603
        Testing we correctly detect badly built list values (4 of them).
 
1604
        
 
1605
        >>> testconfig4 = '''
 
1606
        ... config = 3,4,,
 
1607
        ... test = 3,,4
 
1608
        ... fish = ,,
 
1609
        ... dummy = ,,hello, goodbye
 
1610
        ... '''
 
1611
        >>> try:
 
1612
        ...     ConfigObj(testconfig4.split('\\n'))
 
1613
        ... except ConfigObjError, e:
 
1614
        ...     len(e.errors)
 
1615
        4
 
1616
        
 
1617
        Testing we correctly detect badly quoted values (4 of them).
 
1618
        
 
1619
        >>> testconfig5 = '''
 
1620
        ... config = "hello   # comment
 
1621
        ... test = 'goodbye
 
1622
        ... fish = 'goodbye   # comment
 
1623
        ... dummy = "hello again
 
1624
        ... '''
 
1625
        >>> try:
 
1626
        ...     ConfigObj(testconfig5.split('\\n'))
 
1627
        ... except ConfigObjError, e:
 
1628
        ...     len(e.errors)
 
1629
        4
 
1630
        """
 
1631
        # do we look for lists in values ?
 
1632
        if not self.list_values:
 
1633
            mat = self._nolistvalue.match(value)
 
1634
            if mat is None:
 
1635
                raise SyntaxError
 
1636
            (value, comment) = mat.groups()
 
1637
            # NOTE: we don't unquote here
 
1638
            return (value, comment)
 
1639
        mat = self._valueexp.match(value)
 
1640
        if mat is None:
 
1641
            # the value is badly constructed, probably badly quoted,
 
1642
            # or an invalid list
 
1643
            raise SyntaxError
 
1644
        (list_values, single, empty_list, comment) = mat.groups()
 
1645
        if (list_values == '') and (single is None):
 
1646
            # change this if you want to accept empty values
 
1647
            raise SyntaxError
 
1648
        # NOTE: note there is no error handling from here if the regex
 
1649
        # is wrong: then incorrect values will slip through
 
1650
        if empty_list is not None:
 
1651
            # the single comma - meaning an empty list
 
1652
            return ([], comment)
 
1653
        if single is not None:
 
1654
            single = self._unquote(single)
 
1655
        if list_values == '':
 
1656
            # not a list value
 
1657
            return (single, comment)
 
1658
        the_list = self._listvalueexp.findall(list_values)
 
1659
        the_list = [self._unquote(val) for val in the_list]
 
1660
        if single is not None:
 
1661
            the_list += [single]
 
1662
        return (the_list, comment)
 
1663
 
 
1664
    def _multiline(self, value, infile, cur_index, maxline):
 
1665
        """
 
1666
        Extract the value, where we are in a multiline situation
 
1667
        
 
1668
        Testing multiline values.
 
1669
        
 
1670
        >>> i == {
 
1671
        ...     'name4': ' another single line value ',
 
1672
        ...     'multi section': {
 
1673
        ...         'name4': '\\n        Well, this is a\\n        multiline '
 
1674
        ...             'value\\n        ',
 
1675
        ...         'name2': '\\n        Well, this is a\\n        multiline '
 
1676
        ...             'value\\n        ',
 
1677
        ...         'name3': '\\n        Well, this is a\\n        multiline '
 
1678
        ...             'value\\n        ',
 
1679
        ...         'name1': '\\n        Well, this is a\\n        multiline '
 
1680
        ...             'value\\n        ',
 
1681
        ...     },
 
1682
        ...     'name2': ' another single line value ',
 
1683
        ...     'name3': ' a single line value ',
 
1684
        ...     'name1': ' a single line value ',
 
1685
        ... }
 
1686
        1
 
1687
        """
 
1688
        quot = value[:3]
 
1689
        newvalue = value[3:]
 
1690
        single_line = self._triple_quote[quot][0]
 
1691
        multi_line = self._triple_quote[quot][1]
 
1692
        mat = single_line.match(value)
 
1693
        if mat is not None:
 
1694
            retval = list(mat.groups())
 
1695
            retval.append(cur_index)
 
1696
            return retval
 
1697
        elif newvalue.find(quot) != -1:
 
1698
            # somehow the triple quote is missing
 
1699
            raise SyntaxError
 
1700
        #
 
1701
        while cur_index < maxline:
 
1702
            cur_index += 1
 
1703
            newvalue += '\n'
 
1704
            line = infile[cur_index]
 
1705
            if line.find(quot) == -1:
 
1706
                newvalue += line
 
1707
            else:
 
1708
                # end of multiline, process it
 
1709
                break
 
1710
        else:
 
1711
            # we've got to the end of the config, oops...
 
1712
            raise SyntaxError
 
1713
        mat = multi_line.match(line)
 
1714
        if mat is None:
 
1715
            # a badly formed line
 
1716
            raise SyntaxError
 
1717
        (value, comment) = mat.groups()
 
1718
        return (newvalue + value, comment, cur_index)
 
1719
 
 
1720
    def _handle_configspec(self, configspec):
 
1721
        """Parse the configspec."""
 
1722
        try:
 
1723
            configspec = ConfigObj(
 
1724
                configspec,
 
1725
                raise_errors=True,
 
1726
                file_error=True,
 
1727
                list_values=False)
 
1728
        except ConfigObjError, e:
 
1729
            # FIXME: Should these errors have a reference
 
1730
            # to the already parsed ConfigObj ?
 
1731
            raise ConfigspecError('Parsing configspec failed: %s' % e)
 
1732
        except IOError, e:
 
1733
            raise IOError('Reading configspec failed: %s' % e)
 
1734
        self._set_configspec_value(configspec, self)
 
1735
 
 
1736
    def _set_configspec_value(self, configspec, section):
 
1737
        """Used to recursively set configspec values."""
 
1738
        if '__many__' in configspec.sections:
 
1739
            section.configspec['__many__'] = configspec['__many__']
 
1740
            if len(configspec.sections) > 1:
 
1741
                # FIXME: can we supply any useful information here ?
 
1742
                raise RepeatSectionError
 
1743
        for entry in configspec.scalars:
 
1744
            section.configspec[entry] = configspec[entry]
 
1745
        for entry in configspec.sections:
 
1746
            if entry == '__many__':
 
1747
                continue
 
1748
            if entry not in section:
 
1749
                section[entry] = {}
 
1750
            self._set_configspec_value(configspec[entry], section[entry])
 
1751
 
 
1752
    def _handle_repeat(self, section, configspec):
 
1753
        """Dynamically assign configspec for repeated section."""
 
1754
        try:
 
1755
            section_keys = configspec.sections
 
1756
            scalar_keys = configspec.scalars
 
1757
        except AttributeError:
 
1758
            section_keys = [entry for entry in configspec 
 
1759
                                if isinstance(configspec[entry], dict)]
 
1760
            scalar_keys = [entry for entry in configspec 
 
1761
                                if not isinstance(configspec[entry], dict)]
 
1762
        if '__many__' in section_keys and len(section_keys) > 1:
 
1763
            # FIXME: can we supply any useful information here ?
 
1764
            raise RepeatSectionError
 
1765
        scalars = {}
 
1766
        sections = {}
 
1767
        for entry in scalar_keys:
 
1768
            val = configspec[entry]
 
1769
            scalars[entry] = val
 
1770
        for entry in section_keys:
 
1771
            val = configspec[entry]
 
1772
            if entry == '__many__':
 
1773
                scalars[entry] = val
 
1774
                continue
 
1775
            sections[entry] = val
 
1776
        #
 
1777
        section.configspec = scalars
 
1778
        for entry in sections:
 
1779
            if entry not in section:
 
1780
                section[entry] = {}
 
1781
            self._handle_repeat(section[entry], sections[entry])
 
1782
 
 
1783
    def _write_line(self, indent_string, entry, this_entry, comment):
 
1784
        """Write an individual line, for the write method"""
 
1785
        # NOTE: the calls to self._quote here handles non-StringType values.
 
1786
        return '%s%s%s%s%s' % (
 
1787
            indent_string,
 
1788
            self._decode_element(self._quote(entry, multiline=False)),
 
1789
            self._a_to_u(' = '),
 
1790
            self._decode_element(self._quote(this_entry)),
 
1791
            self._decode_element(comment))
 
1792
 
 
1793
    def _write_marker(self, indent_string, depth, entry, comment):
 
1794
        """Write a section marker line"""
 
1795
        return '%s%s%s%s%s' % (
 
1796
            indent_string,
 
1797
            self._a_to_u('[' * depth),
 
1798
            self._quote(self._decode_element(entry), multiline=False),
 
1799
            self._a_to_u(']' * depth),
 
1800
            self._decode_element(comment))
 
1801
 
 
1802
    def _handle_comment(self, comment):
 
1803
        """
 
1804
        Deal with a comment.
 
1805
        
 
1806
        >>> filename = a.filename
 
1807
        >>> a.filename = None
 
1808
        >>> values = a.write()
 
1809
        >>> index = 0
 
1810
        >>> while index < 23:
 
1811
        ...     index += 1
 
1812
        ...     line = values[index-1]
 
1813
        ...     assert line.endswith('# comment ' + str(index))
 
1814
        >>> a.filename = filename
 
1815
        
 
1816
        >>> start_comment = ['# Initial Comment', '', '#']
 
1817
        >>> end_comment = ['', '#', '# Final Comment']
 
1818
        >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
 
1819
        >>> nc = ConfigObj(newconfig)
 
1820
        >>> nc.initial_comment
 
1821
        ['# Initial Comment', '', '#']
 
1822
        >>> nc.final_comment
 
1823
        ['', '#', '# Final Comment']
 
1824
        >>> nc.initial_comment == start_comment
 
1825
        1
 
1826
        >>> nc.final_comment == end_comment
 
1827
        1
 
1828
        """
 
1829
        if not comment:
 
1830
            return ''
 
1831
        if self.indent_type == '\t':
 
1832
            start = self._a_to_u('\t')
 
1833
        else:
 
1834
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
 
1835
        if not comment.startswith('#'):
 
1836
            start += _a_to_u('# ')
 
1837
        return (start + comment)
 
1838
 
 
1839
    def _compute_indent_string(self, depth):
 
1840
        """
 
1841
        Compute the indent string, according to current indent_type and depth
 
1842
        """
 
1843
        if self.indent_type == '':
 
1844
            # no indentation at all
 
1845
            return ''
 
1846
        if self.indent_type == '\t':
 
1847
            return '\t' * depth
 
1848
        if self.indent_type == ' ':
 
1849
            return ' ' * NUM_INDENT_SPACES * depth
 
1850
        raise SyntaxError
 
1851
 
 
1852
    # Public methods
 
1853
 
 
1854
    def write(self, outfile=None, section=None):
 
1855
        """
 
1856
        Write the current ConfigObj as a file
 
1857
        
 
1858
        tekNico: FIXME: use StringIO instead of real files
 
1859
        
 
1860
        >>> filename = a.filename
 
1861
        >>> a.filename = 'test.ini'
 
1862
        >>> a.write()
 
1863
        >>> a.filename = filename
 
1864
        >>> a == ConfigObj('test.ini', raise_errors=True)
 
1865
        1
 
1866
        >>> os.remove('test.ini')
 
1867
        >>> b.filename = 'test.ini'
 
1868
        >>> b.write()
 
1869
        >>> b == ConfigObj('test.ini', raise_errors=True)
 
1870
        1
 
1871
        >>> os.remove('test.ini')
 
1872
        >>> i.filename = 'test.ini'
 
1873
        >>> i.write()
 
1874
        >>> i == ConfigObj('test.ini', raise_errors=True)
 
1875
        1
 
1876
        >>> os.remove('test.ini')
 
1877
        >>> a = ConfigObj()
 
1878
        >>> a['DEFAULT'] = {'a' : 'fish'}
 
1879
        >>> a['a'] = '%(a)s'
 
1880
        >>> a.write()
 
1881
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
 
1882
        """
 
1883
        if self.indent_type is None:
 
1884
            # this can be true if initialised from a dictionary
 
1885
            self.indent_type = DEFAULT_INDENT_TYPE
 
1886
        #
 
1887
        out = []
 
1888
        cs = self._a_to_u('#')
 
1889
        csp = self._a_to_u('# ')
 
1890
        if section is None:
 
1891
            int_val = self.interpolation
 
1892
            self.interpolation = False
 
1893
            section = self
 
1894
            for line in self.initial_comment:
 
1895
                line = self._decode_element(line)
 
1896
                stripped_line = line.strip()
 
1897
                if stripped_line and not stripped_line.startswith(cs):
 
1898
                    line = csp + line
 
1899
                out.append(line)
 
1900
        #
 
1901
        indent_string = self._a_to_u(
 
1902
            self._compute_indent_string(section.depth))
 
1903
        for entry in (section.scalars + section.sections):
 
1904
            if entry in section.defaults:
 
1905
                # don't write out default values
 
1906
                continue
 
1907
            for comment_line in section.comments[entry]:
 
1908
                comment_line = self._decode_element(comment_line.lstrip())
 
1909
                if comment_line and not comment_line.startswith(cs):
 
1910
                    comment_line = csp + comment_line
 
1911
                out.append(indent_string + comment_line)
 
1912
            this_entry = section[entry]
 
1913
            comment = self._handle_comment(section.inline_comments[entry])
 
1914
            #
 
1915
            if isinstance(this_entry, dict):
 
1916
                # a section
 
1917
                out.append(self._write_marker(
 
1918
                    indent_string,
 
1919
                    this_entry.depth,
 
1920
                    entry,
 
1921
                    comment))
 
1922
                out.extend(self.write(section=this_entry))
 
1923
            else:
 
1924
                out.append(self._write_line(
 
1925
                    indent_string,
 
1926
                    entry,
 
1927
                    this_entry,
 
1928
                    comment))
 
1929
        #
 
1930
        if section is self:
 
1931
            for line in self.final_comment:
 
1932
                line = self._decode_element(line)
 
1933
                stripped_line = line.strip()
 
1934
                if stripped_line and not stripped_line.startswith(cs):
 
1935
                    line = csp + line
 
1936
                out.append(line)
 
1937
            self.interpolation = int_val
 
1938
        #
 
1939
        if section is not self:
 
1940
            return out
 
1941
        #
 
1942
        if (self.filename is None) and (outfile is None):
 
1943
            # output a list of lines
 
1944
            # might need to encode
 
1945
            # NOTE: This will *screw* UTF16, each line will start with the BOM
 
1946
            if self.encoding:
 
1947
                out = [l.encode(self.encoding) for l in out]
 
1948
            if (self.BOM and ((self.encoding is None) or
 
1949
                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
1950
                # Add the UTF8 BOM
 
1951
                if not out:
 
1952
                    out.append('')
 
1953
                out[0] = BOM_UTF8 + out[0]
 
1954
            return out
 
1955
        #
 
1956
        # Turn the list to a string, joined with correct newlines
 
1957
        output = (self._a_to_u(self.newlines or os.linesep)
 
1958
            ).join(out)
 
1959
        if self.encoding:
 
1960
            output = output.encode(self.encoding)
 
1961
        if (self.BOM and ((self.encoding is None) or
 
1962
            (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
 
1963
            # Add the UTF8 BOM
 
1964
            output = BOM_UTF8 + output
 
1965
        if outfile is not None:
 
1966
            outfile.write(output)
 
1967
        else:
 
1968
            h = open(self.filename, 'w')
 
1969
            h.write(output)
 
1970
            h.close()
 
1971
 
 
1972
    def validate(self, validator, preserve_errors=False, section=None):
 
1973
        """
 
1974
        Test the ConfigObj against a configspec.
 
1975
        
 
1976
        It uses the ``validator`` object from *validate.py*.
 
1977
        
 
1978
        To run ``validate`` on the current ConfigObj, call: ::
 
1979
        
 
1980
            test = config.validate(validator)
 
1981
        
 
1982
        (Normally having previously passed in the configspec when the ConfigObj
 
1983
        was created - you can dynamically assign a dictionary of checks to the
 
1984
        ``configspec`` attribute of a section though).
 
1985
        
 
1986
        It returns ``True`` if everything passes, or a dictionary of
 
1987
        pass/fails (True/False). If every member of a subsection passes, it
 
1988
        will just have the value ``True``. (It also returns ``False`` if all
 
1989
        members fail).
 
1990
        
 
1991
        In addition, it converts the values from strings to their native
 
1992
        types if their checks pass (and ``stringify`` is set).
 
1993
        
 
1994
        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
 
1995
        of a marking a fail with a ``False``, it will preserve the actual
 
1996
        exception object. This can contain info about the reason for failure.
 
1997
        For example the ``VdtValueTooSmallError`` indeicates that the value
 
1998
        supplied was too small. If a value (or section) is missing it will
 
1999
        still be marked as ``False``.
 
2000
        
 
2001
        You must have the validate module to use ``preserve_errors=True``.
 
2002
        
 
2003
        You can then use the ``flatten_errors`` function to turn your nested
 
2004
        results dictionary into a flattened list of failures - useful for
 
2005
        displaying meaningful error messages.
 
2006
        
 
2007
        >>> try:
 
2008
        ...     from validate import Validator
 
2009
        ... except ImportError:
 
2010
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
 
2011
        ... else:
 
2012
        ...     config = '''
 
2013
        ...     test1=40
 
2014
        ...     test2=hello
 
2015
        ...     test3=3
 
2016
        ...     test4=5.0
 
2017
        ...     [section]
 
2018
        ...         test1=40
 
2019
        ...         test2=hello
 
2020
        ...         test3=3
 
2021
        ...         test4=5.0
 
2022
        ...         [[sub section]]
 
2023
        ...             test1=40
 
2024
        ...             test2=hello
 
2025
        ...             test3=3
 
2026
        ...             test4=5.0
 
2027
        ... '''.split('\\n')
 
2028
        ...     configspec = '''
 
2029
        ...     test1= integer(30,50)
 
2030
        ...     test2= string
 
2031
        ...     test3=integer
 
2032
        ...     test4=float(6.0)
 
2033
        ...     [section ]
 
2034
        ...         test1=integer(30,50)
 
2035
        ...         test2=string
 
2036
        ...         test3=integer
 
2037
        ...         test4=float(6.0)
 
2038
        ...         [[sub section]]
 
2039
        ...             test1=integer(30,50)
 
2040
        ...             test2=string
 
2041
        ...             test3=integer
 
2042
        ...             test4=float(6.0)
 
2043
        ...     '''.split('\\n')
 
2044
        ...     val = Validator()
 
2045
        ...     c1 = ConfigObj(config, configspec=configspec)
 
2046
        ...     test = c1.validate(val)
 
2047
        ...     test == {
 
2048
        ...         'test1': True,
 
2049
        ...         'test2': True,
 
2050
        ...         'test3': True,
 
2051
        ...         'test4': False,
 
2052
        ...         'section': {
 
2053
        ...             'test1': True,
 
2054
        ...             'test2': True,
 
2055
        ...             'test3': True,
 
2056
        ...             'test4': False,
 
2057
        ...             'sub section': {
 
2058
        ...                 'test1': True,
 
2059
        ...                 'test2': True,
 
2060
        ...                 'test3': True,
 
2061
        ...                 'test4': False,
 
2062
        ...             },
 
2063
        ...         },
 
2064
        ...     }
 
2065
        1
 
2066
        >>> val.check(c1.configspec['test4'], c1['test4'])
 
2067
        Traceback (most recent call last):
 
2068
        VdtValueTooSmallError: the value "5.0" is too small.
 
2069
        
 
2070
        >>> val_test_config = '''
 
2071
        ...     key = 0
 
2072
        ...     key2 = 1.1
 
2073
        ...     [section]
 
2074
        ...     key = some text
 
2075
        ...     key2 = 1.1, 3.0, 17, 6.8
 
2076
        ...         [[sub-section]]
 
2077
        ...         key = option1
 
2078
        ...         key2 = True'''.split('\\n')
 
2079
        >>> val_test_configspec = '''
 
2080
        ...     key = integer
 
2081
        ...     key2 = float
 
2082
        ...     [section]
 
2083
        ...     key = string
 
2084
        ...     key2 = float_list(4)
 
2085
        ...        [[sub-section]]
 
2086
        ...        key = option(option1, option2)
 
2087
        ...        key2 = boolean'''.split('\\n')
 
2088
        >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
 
2089
        >>> val_test.validate(val)
 
2090
        1
 
2091
        >>> val_test['key'] = 'text not a digit'
 
2092
        >>> val_res = val_test.validate(val)
 
2093
        >>> val_res == {'key2': True, 'section': True, 'key': False}
 
2094
        1
 
2095
        >>> configspec = '''
 
2096
        ...     test1=integer(30,50, default=40)
 
2097
        ...     test2=string(default="hello")
 
2098
        ...     test3=integer(default=3)
 
2099
        ...     test4=float(6.0, default=6.0)
 
2100
        ...     [section ]
 
2101
        ...         test1=integer(30,50, default=40)
 
2102
        ...         test2=string(default="hello")
 
2103
        ...         test3=integer(default=3)
 
2104
        ...         test4=float(6.0, default=6.0)
 
2105
        ...         [[sub section]]
 
2106
        ...             test1=integer(30,50, default=40)
 
2107
        ...             test2=string(default="hello")
 
2108
        ...             test3=integer(default=3)
 
2109
        ...             test4=float(6.0, default=6.0)
 
2110
        ...     '''.split('\\n')
 
2111
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
 
2112
        >>> default_test
 
2113
        {'test1': '30', 'section': {'sub section': {}}}
 
2114
        >>> default_test.validate(val)
 
2115
        1
 
2116
        >>> default_test == {
 
2117
        ...     'test1': 30,
 
2118
        ...     'test2': 'hello',
 
2119
        ...     'test3': 3,
 
2120
        ...     'test4': 6.0,
 
2121
        ...     'section': {
 
2122
        ...         'test1': 40,
 
2123
        ...         'test2': 'hello',
 
2124
        ...         'test3': 3,
 
2125
        ...         'test4': 6.0,
 
2126
        ...         'sub section': {
 
2127
        ...             'test1': 40,
 
2128
        ...             'test3': 3,
 
2129
        ...             'test2': 'hello',
 
2130
        ...             'test4': 6.0,
 
2131
        ...         },
 
2132
        ...     },
 
2133
        ... }
 
2134
        1
 
2135
        
 
2136
        Now testing with repeated sections : BIG TEST
 
2137
        
 
2138
        >>> repeated_1 = '''
 
2139
        ... [dogs]
 
2140
        ...     [[__many__]] # spec for a dog
 
2141
        ...         fleas = boolean(default=True)
 
2142
        ...         tail = option(long, short, default=long)
 
2143
        ...         name = string(default=rover)
 
2144
        ...         [[[__many__]]]  # spec for a puppy
 
2145
        ...             name = string(default="son of rover")
 
2146
        ...             age = float(default=0.0)
 
2147
        ... [cats]
 
2148
        ...     [[__many__]] # spec for a cat
 
2149
        ...         fleas = boolean(default=True)
 
2150
        ...         tail = option(long, short, default=short)
 
2151
        ...         name = string(default=pussy)
 
2152
        ...         [[[__many__]]] # spec for a kitten
 
2153
        ...             name = string(default="son of pussy")
 
2154
        ...             age = float(default=0.0)
 
2155
        ...         '''.split('\\n')
 
2156
        >>> repeated_2 = '''
 
2157
        ... [dogs]
 
2158
        ... 
 
2159
        ...     # blank dogs with puppies
 
2160
        ...     # should be filled in by the configspec
 
2161
        ...     [[dog1]]
 
2162
        ...         [[[puppy1]]]
 
2163
        ...         [[[puppy2]]]
 
2164
        ...         [[[puppy3]]]
 
2165
        ...     [[dog2]]
 
2166
        ...         [[[puppy1]]]
 
2167
        ...         [[[puppy2]]]
 
2168
        ...         [[[puppy3]]]
 
2169
        ...     [[dog3]]
 
2170
        ...         [[[puppy1]]]
 
2171
        ...         [[[puppy2]]]
 
2172
        ...         [[[puppy3]]]
 
2173
        ... [cats]
 
2174
        ... 
 
2175
        ...     # blank cats with kittens
 
2176
        ...     # should be filled in by the configspec
 
2177
        ...     [[cat1]]
 
2178
        ...         [[[kitten1]]]
 
2179
        ...         [[[kitten2]]]
 
2180
        ...         [[[kitten3]]]
 
2181
        ...     [[cat2]]
 
2182
        ...         [[[kitten1]]]
 
2183
        ...         [[[kitten2]]]
 
2184
        ...         [[[kitten3]]]
 
2185
        ...     [[cat3]]
 
2186
        ...         [[[kitten1]]]
 
2187
        ...         [[[kitten2]]]
 
2188
        ...         [[[kitten3]]]
 
2189
        ... '''.split('\\n')
 
2190
        >>> repeated_3 = '''
 
2191
        ... [dogs]
 
2192
        ... 
 
2193
        ...     [[dog1]]
 
2194
        ...     [[dog2]]
 
2195
        ...     [[dog3]]
 
2196
        ... [cats]
 
2197
        ... 
 
2198
        ...     [[cat1]]
 
2199
        ...     [[cat2]]
 
2200
        ...     [[cat3]]
 
2201
        ... '''.split('\\n')
 
2202
        >>> repeated_4 = '''
 
2203
        ... [__many__]
 
2204
        ... 
 
2205
        ...     name = string(default=Michael)
 
2206
        ...     age = float(default=0.0)
 
2207
        ...     sex = option(m, f, default=m)
 
2208
        ... '''.split('\\n')
 
2209
        >>> repeated_5 = '''
 
2210
        ... [cats]
 
2211
        ... [[__many__]]
 
2212
        ...     fleas = boolean(default=True)
 
2213
        ...     tail = option(long, short, default=short)
 
2214
        ...     name = string(default=pussy)
 
2215
        ...     [[[description]]]
 
2216
        ...         height = float(default=3.3)
 
2217
        ...         weight = float(default=6)
 
2218
        ...         [[[[coat]]]]
 
2219
        ...             fur = option(black, grey, brown, "tortoise shell", default=black)
 
2220
        ...             condition = integer(0,10, default=5)
 
2221
        ... '''.split('\\n')
 
2222
        >>> from validate import Validator
 
2223
        >>> val= Validator()
 
2224
        >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
 
2225
        >>> repeater.validate(val)
 
2226
        1
 
2227
        >>> repeater == {
 
2228
        ...     'dogs': {
 
2229
        ...         'dog1': {
 
2230
        ...             'fleas': True,
 
2231
        ...             'tail': 'long',
 
2232
        ...             'name': 'rover',
 
2233
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2234
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2235
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2236
        ...         },
 
2237
        ...         'dog2': {
 
2238
        ...             'fleas': True,
 
2239
        ...             'tail': 'long',
 
2240
        ...             'name': 'rover',
 
2241
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2242
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2243
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2244
        ...         },
 
2245
        ...         'dog3': {
 
2246
        ...             'fleas': True,
 
2247
        ...             'tail': 'long',
 
2248
        ...             'name': 'rover',
 
2249
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2250
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2251
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2252
        ...         },
 
2253
        ...     },
 
2254
        ...     'cats': {
 
2255
        ...         'cat1': {
 
2256
        ...             'fleas': True,
 
2257
        ...             'tail': 'short',
 
2258
        ...             'name': 'pussy',
 
2259
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2260
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2261
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2262
        ...         },
 
2263
        ...         'cat2': {
 
2264
        ...             'fleas': True,
 
2265
        ...             'tail': 'short',
 
2266
        ...             'name': 'pussy',
 
2267
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2268
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2269
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2270
        ...         },
 
2271
        ...         'cat3': {
 
2272
        ...             'fleas': True,
 
2273
        ...             'tail': 'short',
 
2274
        ...             'name': 'pussy',
 
2275
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2276
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2277
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2278
        ...         },
 
2279
        ...     },
 
2280
        ... }
 
2281
        1
 
2282
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
 
2283
        >>> repeater.validate(val)
 
2284
        1
 
2285
        >>> repeater == {
 
2286
        ...     'cats': {
 
2287
        ...         'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2288
        ...         'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2289
        ...         'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2290
        ...     },
 
2291
        ...     'dogs': {
 
2292
        ...         'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2293
        ...         'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2294
        ...         'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2295
        ...     },
 
2296
        ... }
 
2297
        1
 
2298
        >>> repeater = ConfigObj(configspec=repeated_4)
 
2299
        >>> repeater['Michael'] = {}
 
2300
        >>> repeater.validate(val)
 
2301
        1
 
2302
        >>> repeater == {
 
2303
        ...     'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
 
2304
        ... }
 
2305
        1
 
2306
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
 
2307
        >>> repeater == {
 
2308
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
2309
        ...     'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
 
2310
        ... }
 
2311
        1
 
2312
        >>> repeater.validate(val)
 
2313
        1
 
2314
        >>> repeater == {
 
2315
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
2316
        ...     'cats': {
 
2317
        ...         'cat1': {
 
2318
        ...             'fleas': True,
 
2319
        ...             'tail': 'short',
 
2320
        ...             'name': 'pussy',
 
2321
        ...             'description': {
 
2322
        ...                 'weight': 6.0,
 
2323
        ...                 'height': 3.2999999999999998,
 
2324
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2325
        ...             },
 
2326
        ...         },
 
2327
        ...         'cat2': {
 
2328
        ...             'fleas': True,
 
2329
        ...             'tail': 'short',
 
2330
        ...             'name': 'pussy',
 
2331
        ...             'description': {
 
2332
        ...                 'weight': 6.0,
 
2333
        ...                 'height': 3.2999999999999998,
 
2334
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2335
        ...             },
 
2336
        ...         },
 
2337
        ...         'cat3': {
 
2338
        ...             'fleas': True,
 
2339
        ...             'tail': 'short',
 
2340
        ...             'name': 'pussy',
 
2341
        ...             'description': {
 
2342
        ...                 'weight': 6.0,
 
2343
        ...                 'height': 3.2999999999999998,
 
2344
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2345
        ...             },
 
2346
        ...         },
 
2347
        ...     },
 
2348
        ... }
 
2349
        1
 
2350
        
 
2351
        Test that interpolation is preserved for validated string values.
 
2352
        Also check that interpolation works in configspecs.
 
2353
        >>> t = ConfigObj()
 
2354
        >>> t['DEFAULT'] = {}
 
2355
        >>> t['DEFAULT']['test'] = 'a'
 
2356
        >>> t['test'] = '%(test)s'
 
2357
        >>> t['test']
 
2358
        'a'
 
2359
        >>> v = Validator()
 
2360
        >>> t.configspec = {'test': 'string'}
 
2361
        >>> t.validate(v)
 
2362
        1
 
2363
        >>> t.interpolation = False
 
2364
        >>> t
 
2365
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 
2366
        >>> specs = [
 
2367
        ...    'interpolated string  = string(default="fuzzy-%(man)s")',
 
2368
        ...    '[DEFAULT]',
 
2369
        ...    'man = wuzzy',
 
2370
        ...    ]
 
2371
        >>> c = ConfigObj(configspec=specs)
 
2372
        >>> c.validate(v)
 
2373
        1
 
2374
        >>> c['interpolated string']
 
2375
        'fuzzy-wuzzy'
 
2376
        
 
2377
        FIXME: Above tests will fail if we couldn't import Validator (the ones
 
2378
        that don't raise errors will produce different output and still fail as
 
2379
        tests)
 
2380
        """
 
2381
        if section is None:
 
2382
            if self.configspec is None:
 
2383
                raise ValueError, 'No configspec supplied.'
 
2384
            if preserve_errors:
 
2385
                if VdtMissingValue is None:
 
2386
                    raise ImportError('Missing validate module.')
 
2387
            section = self
 
2388
        #
 
2389
        spec_section = section.configspec
 
2390
        if '__many__' in section.configspec:
 
2391
            many = spec_section['__many__']
 
2392
            # dynamically assign the configspecs
 
2393
            # for the sections below
 
2394
            for entry in section.sections:
 
2395
                self._handle_repeat(section[entry], many)
 
2396
        #
 
2397
        out = {}
 
2398
        ret_true = True
 
2399
        ret_false = True
 
2400
        for entry in spec_section:
 
2401
            if entry == '__many__':
 
2402
                continue
 
2403
            if (not entry in section.scalars) or (entry in section.defaults):
 
2404
                # missing entries
 
2405
                # or entries from defaults
 
2406
                missing = True
 
2407
                val = None
 
2408
            else:
 
2409
                missing = False
 
2410
                val = section[entry]
 
2411
            try:
 
2412
                check = validator.check(spec_section[entry],
 
2413
                                        val,
 
2414
                                        missing=missing
 
2415
                                        )
 
2416
            except validator.baseErrorClass, e:
 
2417
                if not preserve_errors or isinstance(e, VdtMissingValue):
 
2418
                    out[entry] = False
 
2419
                else:
 
2420
                    # preserve the error
 
2421
                    out[entry] = e
 
2422
                    ret_false = False
 
2423
                ret_true = False
 
2424
            else:
 
2425
                ret_false = False
 
2426
                out[entry] = True
 
2427
                if self.stringify or missing:
 
2428
                    # if we are doing type conversion
 
2429
                    # or the value is a supplied default
 
2430
                    if not self.stringify:
 
2431
                        if isinstance(check, (list, tuple)):
 
2432
                            # preserve lists
 
2433
                            check = [self._str(item) for item in check]
 
2434
                        elif missing and check is None:
 
2435
                            # convert the None from a default to a ''
 
2436
                            check = ''
 
2437
                        else:
 
2438
                            check = self._str(check)
 
2439
                    if (check != val) or missing:
 
2440
                        section[entry] = check
 
2441
                if missing and entry not in section.defaults:
 
2442
                    section.defaults.append(entry)
 
2443
        #
 
2444
        # FIXME: Will this miss missing sections ?
 
2445
        for entry in section.sections:
 
2446
            if section is self and entry == 'DEFAULT':
 
2447
                continue
 
2448
            check = self.validate(validator, preserve_errors=preserve_errors,
 
2449
                section=section[entry])
 
2450
            out[entry] = check
 
2451
            if check == False:
 
2452
                ret_true = False
 
2453
            elif check == True:
 
2454
                ret_false = False
 
2455
            else:
 
2456
                ret_true = False
 
2457
                ret_false = False
 
2458
        #
 
2459
        if ret_true:
 
2460
            return True
 
2461
        elif ret_false:
 
2462
            return False
 
2463
        else:
 
2464
            return out
 
2465
 
 
2466
class SimpleVal(object):
 
2467
    """
 
2468
    A simple validator.
 
2469
    Can be used to check that all members expected are present.
 
2470
    
 
2471
    To use it, provide a configspec with all your members in (the value given
 
2472
    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
 
2473
    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
 
2474
    members are present, or a dictionary with True/False meaning
 
2475
    present/missing. (Whole missing sections will be replaced with ``False``)
 
2476
    
 
2477
    >>> val = SimpleVal()
 
2478
    >>> config = '''
 
2479
    ... test1=40
 
2480
    ... test2=hello
 
2481
    ... test3=3
 
2482
    ... test4=5.0
 
2483
    ... [section]
 
2484
    ... test1=40
 
2485
    ... test2=hello
 
2486
    ... test3=3
 
2487
    ... test4=5.0
 
2488
    ...     [[sub section]]
 
2489
    ...     test1=40
 
2490
    ...     test2=hello
 
2491
    ...     test3=3
 
2492
    ...     test4=5.0
 
2493
    ... '''.split('\\n')
 
2494
    >>> configspec = '''
 
2495
    ... test1=''
 
2496
    ... test2=''
 
2497
    ... test3=''
 
2498
    ... test4=''
 
2499
    ... [section]
 
2500
    ... test1=''
 
2501
    ... test2=''
 
2502
    ... test3=''
 
2503
    ... test4=''
 
2504
    ...     [[sub section]]
 
2505
    ...     test1=''
 
2506
    ...     test2=''
 
2507
    ...     test3=''
 
2508
    ...     test4=''
 
2509
    ... '''.split('\\n')
 
2510
    >>> o = ConfigObj(config, configspec=configspec)
 
2511
    >>> o.validate(val)
 
2512
    1
 
2513
    >>> o = ConfigObj(configspec=configspec)
 
2514
    >>> o.validate(val)
 
2515
    0
 
2516
    """
 
2517
    
 
2518
    def __init__(self):
 
2519
        self.baseErrorClass = ConfigObjError
 
2520
    
 
2521
    def check(self, check, member, missing=False):
 
2522
        """A dummy check method, always returns the value unchanged."""
 
2523
        if missing:
 
2524
            raise self.baseErrorClass
 
2525
        return member
 
2526
 
 
2527
# Check / processing functions for options
 
2528
def flatten_errors(cfg, res, levels=None, results=None):
 
2529
    """
 
2530
    An example function that will turn a nested dictionary of results
 
2531
    (as returned by ``ConfigObj.validate``) into a flat list.
 
2532
    
 
2533
    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
 
2534
    dictionary returned by ``validate``.
 
2535
    
 
2536
    (This is a recursive function, so you shouldn't use the ``levels`` or
 
2537
    ``results`` arguments - they are used by the function.
 
2538
    
 
2539
    Returns a list of keys that failed. Each member of the list is a tuple :
 
2540
    ::
 
2541
    
 
2542
        ([list of sections...], key, result)
 
2543
    
 
2544
    If ``validate`` was called with ``preserve_errors=False`` (the default)
 
2545
    then ``result`` will always be ``False``.
 
2546
 
 
2547
    *list of sections* is a flattened list of sections that the key was found
 
2548
    in.
 
2549
    
 
2550
    If the section was missing then key will be ``None``.
 
2551
    
 
2552
    If the value (or section) was missing then ``result`` will be ``False``.
 
2553
    
 
2554
    If ``validate`` was called with ``preserve_errors=True`` and a value
 
2555
    was present, but failed the check, then ``result`` will be the exception
 
2556
    object returned. You can use this as a string that describes the failure.
 
2557
    
 
2558
    For example *The value "3" is of the wrong type*.
 
2559
    
 
2560
    # FIXME: is the ordering of the output arbitrary ?
 
2561
    >>> import validate
 
2562
    >>> vtor = validate.Validator()
 
2563
    >>> my_ini = '''
 
2564
    ...     option1 = True
 
2565
    ...     [section1]
 
2566
    ...     option1 = True
 
2567
    ...     [section2]
 
2568
    ...     another_option = Probably
 
2569
    ...     [section3]
 
2570
    ...     another_option = True
 
2571
    ...     [[section3b]]
 
2572
    ...     value = 3
 
2573
    ...     value2 = a
 
2574
    ...     value3 = 11
 
2575
    ...     '''
 
2576
    >>> my_cfg = '''
 
2577
    ...     option1 = boolean()
 
2578
    ...     option2 = boolean()
 
2579
    ...     option3 = boolean(default=Bad_value)
 
2580
    ...     [section1]
 
2581
    ...     option1 = boolean()
 
2582
    ...     option2 = boolean()
 
2583
    ...     option3 = boolean(default=Bad_value)
 
2584
    ...     [section2]
 
2585
    ...     another_option = boolean()
 
2586
    ...     [section3]
 
2587
    ...     another_option = boolean()
 
2588
    ...     [[section3b]]
 
2589
    ...     value = integer
 
2590
    ...     value2 = integer
 
2591
    ...     value3 = integer(0, 10)
 
2592
    ...         [[[section3b-sub]]]
 
2593
    ...         value = string
 
2594
    ...     [section4]
 
2595
    ...     another_option = boolean()
 
2596
    ...     '''
 
2597
    >>> cs = my_cfg.split('\\n')
 
2598
    >>> ini = my_ini.split('\\n')
 
2599
    >>> cfg = ConfigObj(ini, configspec=cs)
 
2600
    >>> res = cfg.validate(vtor, preserve_errors=True)
 
2601
    >>> errors = []
 
2602
    >>> for entry in flatten_errors(cfg, res):
 
2603
    ...     section_list, key, error = entry
 
2604
    ...     section_list.insert(0, '[root]')
 
2605
    ...     if key is not None:
 
2606
    ...        section_list.append(key)
 
2607
    ...     else:
 
2608
    ...         section_list.append('[missing]')
 
2609
    ...     section_string = ', '.join(section_list)
 
2610
    ...     errors.append((section_string, ' = ', error))
 
2611
    >>> errors.sort()
 
2612
    >>> for entry in errors:
 
2613
    ...     print entry[0], entry[1], (entry[2] or 0)
 
2614
    [root], option2  =  0
 
2615
    [root], option3  =  the value "Bad_value" is of the wrong type.
 
2616
    [root], section1, option2  =  0
 
2617
    [root], section1, option3  =  the value "Bad_value" is of the wrong type.
 
2618
    [root], section2, another_option  =  the value "Probably" is of the wrong type.
 
2619
    [root], section3, section3b, section3b-sub, [missing]  =  0
 
2620
    [root], section3, section3b, value2  =  the value "a" is of the wrong type.
 
2621
    [root], section3, section3b, value3  =  the value "11" is too big.
 
2622
    [root], section4, [missing]  =  0
 
2623
    """
 
2624
    if levels is None:
 
2625
        # first time called
 
2626
        levels = []
 
2627
        results = []
 
2628
    if res is True:
 
2629
        return results
 
2630
    if res is False:
 
2631
        results.append((levels[:], None, False))
 
2632
        if levels:
 
2633
            levels.pop()
 
2634
        return results
 
2635
    for (key, val) in res.items():
 
2636
        if val == True:
 
2637
            continue
 
2638
        if isinstance(cfg.get(key), dict):
 
2639
            # Go down one level
 
2640
            levels.append(key)
 
2641
            flatten_errors(cfg[key], val, levels, results)
 
2642
            continue
 
2643
        results.append((levels[:], key, val))
 
2644
    #
 
2645
    # Go up one level
 
2646
    if levels:
 
2647
        levels.pop()
 
2648
    #
 
2649
    return results
 
2650
 
 
2651
 
 
2652
# FIXME: test error code for badly built multiline values
 
2653
# FIXME: test handling of StringIO
 
2654
# FIXME: test interpolation with writing
 
2655
 
 
2656
def _doctest():
 
2657
    """
 
2658
    Dummy function to hold some of the doctests.
 
2659
    
 
2660
    >>> a.depth
 
2661
    0
 
2662
    >>> a == {
 
2663
    ...     'key2': 'val',
 
2664
    ...     'key1': 'val',
 
2665
    ...     'lev1c': {
 
2666
    ...         'lev2c': {
 
2667
    ...             'lev3c': {
 
2668
    ...                 'key1': 'val',
 
2669
    ...             },
 
2670
    ...         },
 
2671
    ...     },
 
2672
    ...     'lev1b': {
 
2673
    ...         'key2': 'val',
 
2674
    ...         'key1': 'val',
 
2675
    ...         'lev2ba': {
 
2676
    ...             'key1': 'val',
 
2677
    ...         },
 
2678
    ...         'lev2bb': {
 
2679
    ...             'key1': 'val',
 
2680
    ...         },
 
2681
    ...     },
 
2682
    ...     'lev1a': {
 
2683
    ...         'key2': 'val',
 
2684
    ...         'key1': 'val',
 
2685
    ...     },
 
2686
    ... }
 
2687
    1
 
2688
    >>> b.depth
 
2689
    0
 
2690
    >>> b == {
 
2691
    ...     'key3': 'val3',
 
2692
    ...     'key2': 'val2',
 
2693
    ...     'key1': 'val1',
 
2694
    ...     'section 1': {
 
2695
    ...         'keys11': 'val1',
 
2696
    ...         'keys13': 'val3',
 
2697
    ...         'keys12': 'val2',
 
2698
    ...     },
 
2699
    ...     'section 2': {
 
2700
    ...         'section 2 sub 1': {
 
2701
    ...             'fish': '3',
 
2702
    ...     },
 
2703
    ...     'keys21': 'val1',
 
2704
    ...     'keys22': 'val2',
 
2705
    ...     'keys23': 'val3',
 
2706
    ...     },
 
2707
    ... }
 
2708
    1
 
2709
    >>> t = '''
 
2710
    ... 'a' = b # !"$%^&*(),::;'@~#= 33
 
2711
    ... "b" = b #= 6, 33
 
2712
    ... ''' .split('\\n')
 
2713
    >>> t2 = ConfigObj(t)
 
2714
    >>> assert t2 == {'a': 'b', 'b': 'b'}
 
2715
    >>> t2.inline_comments['b'] = ''
 
2716
    >>> del t2['a']
 
2717
    >>> assert t2.write() == ['','b = b', '']
 
2718
    
 
2719
    # Test ``list_values=False`` stuff
 
2720
    >>> c = '''
 
2721
    ...     key1 = no quotes
 
2722
    ...     key2 = 'single quotes'
 
2723
    ...     key3 = "double quotes"
 
2724
    ...     key4 = "list", 'with', several, "quotes"
 
2725
    ...     '''
 
2726
    >>> cfg = ConfigObj(c.splitlines(), list_values=False)
 
2727
    >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'", 
 
2728
    ... 'key3': '"double quotes"', 
 
2729
    ... 'key4': '"list", \\'with\\', several, "quotes"'
 
2730
    ... }
 
2731
    1
 
2732
    >>> cfg = ConfigObj(list_values=False)
 
2733
    >>> cfg['key1'] = 'Multiline\\nValue'
 
2734
    >>> cfg['key2'] = '''"Value" with 'quotes' !'''
 
2735
    >>> cfg.write()
 
2736
    ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
 
2737
    >>> cfg.list_values = True
 
2738
    >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
 
2739
    ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
 
2740
    1
 
2741
    
 
2742
    Test flatten_errors:
 
2743
    
 
2744
    >>> from validate import Validator, VdtValueTooSmallError
 
2745
    >>> config = '''
 
2746
    ...     test1=40
 
2747
    ...     test2=hello
 
2748
    ...     test3=3
 
2749
    ...     test4=5.0
 
2750
    ...     [section]
 
2751
    ...         test1=40
 
2752
    ...         test2=hello
 
2753
    ...         test3=3
 
2754
    ...         test4=5.0
 
2755
    ...         [[sub section]]
 
2756
    ...             test1=40
 
2757
    ...             test2=hello
 
2758
    ...             test3=3
 
2759
    ...             test4=5.0
 
2760
    ... '''.split('\\n')
 
2761
    >>> configspec = '''
 
2762
    ...     test1= integer(30,50)
 
2763
    ...     test2= string
 
2764
    ...     test3=integer
 
2765
    ...     test4=float(6.0)
 
2766
    ...     [section ]
 
2767
    ...         test1=integer(30,50)
 
2768
    ...         test2=string
 
2769
    ...         test3=integer
 
2770
    ...         test4=float(6.0)
 
2771
    ...         [[sub section]]
 
2772
    ...             test1=integer(30,50)
 
2773
    ...             test2=string
 
2774
    ...             test3=integer
 
2775
    ...             test4=float(6.0)
 
2776
    ...     '''.split('\\n')
 
2777
    >>> val = Validator()
 
2778
    >>> c1 = ConfigObj(config, configspec=configspec)
 
2779
    >>> res = c1.validate(val)
 
2780
    >>> flatten_errors(c1, res) == [([], 'test4', False), (['section', 
 
2781
    ...     'sub section'], 'test4', False), (['section'], 'test4', False)]
 
2782
    True
 
2783
    >>> res = c1.validate(val, preserve_errors=True)
 
2784
    >>> check = flatten_errors(c1, res)
 
2785
    >>> check[0][:2]
 
2786
    ([], 'test4')
 
2787
    >>> check[1][:2]
 
2788
    (['section', 'sub section'], 'test4')
 
2789
    >>> check[2][:2]
 
2790
    (['section'], 'test4')
 
2791
    >>> for entry in check:
 
2792
    ...     isinstance(entry[2], VdtValueTooSmallError)
 
2793
    ...     print str(entry[2])
 
2794
    True
 
2795
    the value "5.0" is too small.
 
2796
    True
 
2797
    the value "5.0" is too small.
 
2798
    True
 
2799
    the value "5.0" is too small.
 
2800
    
 
2801
    Test unicode handling, BOM, write witha file like object and line endings :
 
2802
    >>> u_base = '''
 
2803
    ... # initial comment
 
2804
    ...     # inital comment 2
 
2805
    ... 
 
2806
    ... test1 = some value
 
2807
    ... # comment
 
2808
    ... test2 = another value    # inline comment
 
2809
    ... # section comment
 
2810
    ... [section]    # inline comment
 
2811
    ...     test = test    # another inline comment
 
2812
    ...     test2 = test2
 
2813
    ... 
 
2814
    ... # final comment
 
2815
    ... # final comment2
 
2816
    ... '''
 
2817
    >>> u = u_base.encode('utf_8').splitlines(True)
 
2818
    >>> u[0] = BOM_UTF8 + u[0]
 
2819
    >>> uc = ConfigObj(u)
 
2820
    >>> uc.encoding = None
 
2821
    >>> uc.BOM == True
 
2822
    1
 
2823
    >>> uc == {'test1': 'some value', 'test2': 'another value',
 
2824
    ... 'section': {'test': 'test', 'test2': 'test2'}}
 
2825
    1
 
2826
    >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
 
2827
    >>> uc.BOM
 
2828
    1
 
2829
    >>> isinstance(uc['test1'], unicode)
 
2830
    1
 
2831
    >>> uc.encoding
 
2832
    'utf_8'
 
2833
    >>> uc.newlines
 
2834
    '\\n'
 
2835
    >>> uc['latin1'] = "This costs lot's of "
 
2836
    >>> a_list = uc.write()
 
2837
    >>> len(a_list)
 
2838
    15
 
2839
    >>> isinstance(a_list[0], str)
 
2840
    1
 
2841
    >>> a_list[0].startswith(BOM_UTF8)
 
2842
    1
 
2843
    >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
 
2844
    >>> uc = ConfigObj(u)
 
2845
    >>> uc.newlines
 
2846
    '\\r\\n'
 
2847
    >>> uc.newlines = '\\r'
 
2848
    >>> from cStringIO import StringIO
 
2849
    >>> file_like = StringIO()
 
2850
    >>> uc.write(file_like)
 
2851
    >>> file_like.seek(0)
 
2852
    >>> uc2 = ConfigObj(file_like)
 
2853
    >>> uc2 == uc
 
2854
    1
 
2855
    >>> uc2.filename is None
 
2856
    1
 
2857
    >>> uc2.newlines == '\\r'
 
2858
    1
 
2859
    """
 
2860
 
 
2861
if __name__ == '__main__':
 
2862
    # run the code tests in doctest format
 
2863
    #
 
2864
    testconfig1 = """\
 
2865
    key1= val    # comment 1
 
2866
    key2= val    # comment 2
 
2867
    # comment 3
 
2868
    [lev1a]     # comment 4
 
2869
    key1= val    # comment 5
 
2870
    key2= val    # comment 6
 
2871
    # comment 7
 
2872
    [lev1b]    # comment 8
 
2873
    key1= val    # comment 9
 
2874
    key2= val    # comment 10
 
2875
    # comment 11
 
2876
        [[lev2ba]]    # comment 12
 
2877
        key1= val    # comment 13
 
2878
        # comment 14
 
2879
        [[lev2bb]]    # comment 15
 
2880
        key1= val    # comment 16
 
2881
    # comment 17
 
2882
    [lev1c]    # comment 18
 
2883
    # comment 19
 
2884
        [[lev2c]]    # comment 20
 
2885
        # comment 21
 
2886
            [[[lev3c]]]    # comment 22
 
2887
            key1 = val    # comment 23"""
 
2888
    #
 
2889
    testconfig2 = """\
 
2890
                        key1 = 'val1'
 
2891
                        key2 =   "val2"
 
2892
                        key3 = val3
 
2893
                        ["section 1"] # comment
 
2894
                        keys11 = val1
 
2895
                        keys12 = val2
 
2896
                        keys13 = val3
 
2897
                        [section 2]
 
2898
                        keys21 = val1
 
2899
                        keys22 = val2
 
2900
                        keys23 = val3
 
2901
                        
 
2902
                            [['section 2 sub 1']]
 
2903
                            fish = 3
 
2904
    """
 
2905
    #
 
2906
    testconfig6 = '''
 
2907
    name1 = """ a single line value """ # comment
 
2908
    name2 = \''' another single line value \''' # comment
 
2909
    name3 = """ a single line value """
 
2910
    name4 = \''' another single line value \'''
 
2911
        [ "multi section" ]
 
2912
        name1 = """
 
2913
        Well, this is a
 
2914
        multiline value
 
2915
        """
 
2916
        name2 = \'''
 
2917
        Well, this is a
 
2918
        multiline value
 
2919
        \'''
 
2920
        name3 = """
 
2921
        Well, this is a
 
2922
        multiline value
 
2923
        """     # a comment
 
2924
        name4 = \'''
 
2925
        Well, this is a
 
2926
        multiline value
 
2927
        \'''  # I guess this is a comment too
 
2928
    '''
 
2929
    #
 
2930
    import doctest
 
2931
    m = sys.modules.get('__main__')
 
2932
    globs = m.__dict__.copy()
 
2933
    a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
 
2934
    b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
 
2935
    i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
 
2936
    globs.update({
 
2937
        'INTP_VER': INTP_VER,
 
2938
        'a': a,
 
2939
        'b': b,
 
2940
        'i': i,
 
2941
    })
 
2942
    doctest.testmod(m, globs=globs)
 
2943
 
 
2944
"""
 
2945
    BUGS
 
2946
    ====
 
2947
    
 
2948
    None known.
 
2949
    
 
2950
    TODO
 
2951
    ====
 
2952
    
 
2953
    Better support for configuration from multiple files, including tracking
 
2954
    *where* the original file came from and writing changes to the correct
 
2955
    file.
 
2956
    
 
2957
    
 
2958
    Make ``newline`` an option (as well as an attribute) ?
 
2959
    
 
2960
    ``UTF16`` encoded files, when returned as a list of lines, will have the
 
2961
    BOM at the start of every line. Should this be removed from all but the
 
2962
    first line ?
 
2963
    
 
2964
    Option to set warning type for unicode decode ? (Defaults to strict).
 
2965
    
 
2966
    A method to optionally remove uniform indentation from multiline values.
 
2967
    (do as an example of using ``walk`` - along with string-escape)
 
2968
    
 
2969
    Should the results dictionary from validate be an ordered dictionary if
 
2970
    `odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
 
2971
    
 
2972
    Implement a better ``__repr__`` ? (``ConfigObj({})``)
 
2973
    
 
2974
    Implement some of the sequence methods (which include slicing) from the
 
2975
    newer ``odict`` ?
 
2976
    
 
2977
    INCOMPATIBLE CHANGES
 
2978
    ====================
 
2979
    
 
2980
    (I have removed a lot of needless complications - this list is probably not
 
2981
    conclusive, many option/attribute/method names have changed)
 
2982
    
 
2983
    Case sensitive
 
2984
    
 
2985
    The only valid divider is '='
 
2986
    
 
2987
    We've removed line continuations with '\'
 
2988
    
 
2989
    No recursive lists in values
 
2990
    
 
2991
    No empty section
 
2992
    
 
2993
    No distinction between flatfiles and non flatfiles
 
2994
    
 
2995
    Change in list syntax - use commas to indicate list, not parentheses
 
2996
    (square brackets and parentheses are no longer recognised as lists)
 
2997
    
 
2998
    ';' is no longer valid for comments and no multiline comments
 
2999
    
 
3000
    No attribute access
 
3001
    
 
3002
    We don't allow empty values - have to use '' or ""
 
3003
    
 
3004
    In ConfigObj 3 - setting a non-flatfile member to ``None`` would
 
3005
    initialise it as an empty section.
 
3006
    
 
3007
    The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
 
3008
    replaced by triple quote, multiple line values.
 
3009
    
 
3010
    The ``newline``, ``force_return``, and ``default`` options have gone
 
3011
    
 
3012
    The ``encoding`` and ``backup_encoding`` methods have gone - replaced
 
3013
    with the ``encode`` and ``decode`` methods.
 
3014
    
 
3015
    ``fileerror`` and ``createempty`` options have become ``file_error`` and
 
3016
    ``create_empty``
 
3017
    
 
3018
    Partial configspecs (for specifying the order members should be written
 
3019
    out and which should be present) have gone. The configspec is no longer
 
3020
    used to specify order for the ``write`` method.
 
3021
    
 
3022
    Exceeding the maximum depth of recursion in string interpolation now
 
3023
    raises an error ``InterpolationDepthError``.
 
3024
    
 
3025
    Specifying a value for interpolation which doesn't exist now raises an
 
3026
    error ``MissingInterpolationOption`` (instead of merely being ignored).
 
3027
    
 
3028
    The ``writein`` method has been removed.
 
3029
    
 
3030
    The comments attribute is now a list (``inline_comments`` equates to the
 
3031
    old comments attribute)
 
3032
    
 
3033
    ISSUES
 
3034
    ======
 
3035
    
 
3036
    ``validate`` doesn't report *extra* values or sections.
 
3037
    
 
3038
    You can't have a keyword with the same name as a section (in the same
 
3039
    section). They are both dictionary keys - so they would overlap.
 
3040
    
 
3041
    ConfigObj doesn't quote and unquote values if ``list_values=False``.
 
3042
    This means that leading or trailing whitespace in values will be lost when
 
3043
    writing. (Unless you manually quote).
 
3044
    
 
3045
    Interpolation checks first the 'DEFAULT' subsection of the current
 
3046
    section, next it checks the 'DEFAULT' section of the parent section,
 
3047
    last it checks the 'DEFAULT' section of the main section.
 
3048
    
 
3049
    Logically a 'DEFAULT' section should apply to all subsections of the *same
 
3050
    parent* - this means that checking the 'DEFAULT' subsection in the
 
3051
    *current section* is not necessarily logical ?
 
3052
    
 
3053
    In order to simplify unicode support (which is possibly of limited value
 
3054
    in a config file) I have removed automatic support and added the
 
3055
    ``encode`` and ``decode methods, which can be used to transform keys and
 
3056
    entries. Because the regex looks for specific values on inital parsing
 
3057
    (i.e. the quotes and the equals signs) it can only read ascii compatible
 
3058
    encodings. For unicode use ``UTF8``, which is ASCII compatible.
 
3059
    
 
3060
    Does it matter that we don't support the ':' divider, which is supported
 
3061
    by ``ConfigParser`` ?
 
3062
    
 
3063
    The regular expression correctly removes the value -
 
3064
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
 
3065
    back quotes (called from ``_handle_value``). What should we do ??
 
3066
    (*ought* to raise exception because it's an invalid value if lists are
 
3067
    off *sigh*. This is not what you want if you want to do your own list
 
3068
    processing - would be *better* in this case not to unquote.)
 
3069
    
 
3070
    String interpolation and validation don't play well together. When
 
3071
    validation changes type it sets the value. This will correctly fetch the
 
3072
    value using interpolation - but then overwrite the interpolation reference.
 
3073
    If the value is unchanged by validation (it's a string) - but other types
 
3074
    will be.
 
3075
    
 
3076
    
 
3077
    List Value Syntax
 
3078
    =================
 
3079
    
 
3080
    List values allow you to specify multiple values for a keyword. This
 
3081
    maps to a list as the resulting Python object when parsed.
 
3082
    
 
3083
    The syntax for lists is easy. A list is a comma separated set of values.
 
3084
    If these values contain quotes, the hash mark, or commas, then the values
 
3085
    can be surrounded by quotes. e.g. : ::
 
3086
    
 
3087
        keyword = value1, 'value 2', "value 3"
 
3088
    
 
3089
    If a value needs to be a list, but only has one member, then you indicate
 
3090
    this with a trailing comma. e.g. : ::
 
3091
    
 
3092
        keyword = "single value",
 
3093
    
 
3094
    If a value needs to be a list, but it has no members, then you indicate
 
3095
    this with a single comma. e.g. : ::
 
3096
    
 
3097
        keyword = ,     # an empty list
 
3098
    
 
3099
    Using triple quotes it will be possible for single values to contain
 
3100
    newlines and *both* single quotes and double quotes. Triple quotes aren't
 
3101
    allowed in list values. This means that the members of list values can't
 
3102
    contain carriage returns (or line feeds :-) or both quote values.
 
3103
      
 
3104
    CHANGELOG
 
3105
    =========
 
3106
    
 
3107
    2006/02/04
 
3108
    ----------
 
3109
    
 
3110
    Removed ``BOM_UTF8`` from ``__all__``.
 
3111
    
 
3112
    The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
 
3113
    *only* ``True`` for the ``UTF16`` encoding.
 
3114
    
 
3115
    File like objects no longer need a ``seek`` attribute.
 
3116
    
 
3117
    ConfigObj no longer keeps a reference to file like objects. Instead the
 
3118
    ``write`` method takes a file like object as an optional argument. (Which
 
3119
    will be used in preference of the ``filename`` attribute if htat exists as
 
3120
    well.)
 
3121
    
 
3122
    Full unicode support added. New options/attributes ``encoding``,
 
3123
    ``default_encoding``.
 
3124
    
 
3125
    utf16 files decoded to unicode.
 
3126
    
 
3127
    If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
 
3128
    written out at the start of the file. (It will normally only be ``True`` if
 
3129
    the utf8 BOM was found when the file was read.)
 
3130
    
 
3131
    File paths are *not* converted to absolute paths, relative paths will
 
3132
    remain relative as the ``filename`` attribute.
 
3133
    
 
3134
    Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
 
3135
    a list of lines.
 
3136
    
 
3137
    2006/01/31
 
3138
    ----------
 
3139
    
 
3140
    Added ``True``, ``False``, and ``enumerate`` if they are not defined.
 
3141
    (``True`` and ``False`` are needed for *early* versions of Python 2.2,
 
3142
    ``enumerate`` is needed for all versions ofPython 2.2)
 
3143
    
 
3144
    Deprecated ``istrue``, replaced it with ``as_bool``.
 
3145
    
 
3146
    Added ``as_int`` and ``as_float``.
 
3147
    
 
3148
    utf8 and utf16 BOM handled in an endian agnostic way.
 
3149
    
 
3150
    2005/12/14
 
3151
    ----------
 
3152
    
 
3153
    Validation no longer done on the 'DEFAULT' section (only in the root
 
3154
    level). This allows interpolation in configspecs.
 
3155
    
 
3156
    Change in validation syntax implemented in validate 0.2.1
 
3157
    
 
3158
    4.1.0
 
3159
    
 
3160
    2005/12/10
 
3161
    ----------
 
3162
    
 
3163
    Added ``merge``, a recursive update.
 
3164
    
 
3165
    Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
 
3166
    example function.
 
3167
    
 
3168
    Thanks to Matthew Brett for suggestions and helping me iron out bugs.
 
3169
    
 
3170
    Fixed bug where a config file is *all* comment, the comment will now be
 
3171
    ``initial_comment`` rather than ``final_comment``.
 
3172
    
 
3173
    2005/12/02
 
3174
    ----------
 
3175
    
 
3176
    Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
 
3177
    
 
3178
    2005/11/04
 
3179
    ----------
 
3180
    
 
3181
    Fixed bug in ``Section.walk`` when transforming names as well as values.
 
3182
    
 
3183
    Added the ``istrue`` method. (Fetches the boolean equivalent of a string
 
3184
    value).
 
3185
    
 
3186
    Fixed ``list_values=False`` - they are now only quoted/unquoted if they
 
3187
    are multiline values.
 
3188
    
 
3189
    List values are written as ``item, item`` rather than ``item,item``.
 
3190
    
 
3191
    4.0.1
 
3192
    
 
3193
    2005/10/09
 
3194
    ----------
 
3195
    
 
3196
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
 
3197
    ``interpolation``).
 
3198
 
 
3199
    4.0.0 Final
 
3200
    
 
3201
    2005/09/16
 
3202
    ----------
 
3203
    
 
3204
    Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
 
3205
    a reference to the new section.
 
3206
    
 
3207
    2005/09/09
 
3208
    ----------
 
3209
    
 
3210
    Removed ``PositionError``.
 
3211
    
 
3212
    Allowed quotes around keys as documented.
 
3213
    
 
3214
    Fixed bug with commas in comments. (matched as a list value)
 
3215
    
 
3216
    Beta 5
 
3217
    
 
3218
    2005/09/07
 
3219
    ----------
 
3220
    
 
3221
    Fixed bug in initialising ConfigObj from a ConfigObj.
 
3222
    
 
3223
    Changed the mailing list address.
 
3224
    
 
3225
    Beta 4
 
3226
    
 
3227
    2005/09/03
 
3228
    ----------
 
3229
    
 
3230
    Fixed bug in ``Section.__delitem__`` oops.
 
3231
    
 
3232
    2005/08/28
 
3233
    ----------
 
3234
    
 
3235
    Interpolation is switched off before writing out files.
 
3236
    
 
3237
    Fixed bug in handling ``StringIO`` instances. (Thanks to report from
 
3238
    "Gustavo Niemeyer" <gustavo@niemeyer.net>)
 
3239
    
 
3240
    Moved the doctests from the ``__init__`` method to a separate function.
 
3241
    (For the sake of IDE calltips).
 
3242
    
 
3243
    Beta 3
 
3244
    
 
3245
    2005/08/26
 
3246
    ----------
 
3247
    
 
3248
    String values unchanged by validation *aren't* reset. This preserves
 
3249
    interpolation in string values.
 
3250
    
 
3251
    2005/08/18
 
3252
    ----------
 
3253
    
 
3254
    None from a default is turned to '' if stringify is off - because setting 
 
3255
    a value to None raises an error.
 
3256
    
 
3257
    Version 4.0.0-beta2
 
3258
    
 
3259
    2005/08/16
 
3260
    ----------
 
3261
    
 
3262
    By Nicola Larosa
 
3263
    
 
3264
    Actually added the RepeatSectionError class ;-)
 
3265
    
 
3266
    2005/08/15
 
3267
    ----------
 
3268
    
 
3269
    If ``stringify`` is off - list values are preserved by the ``validate``
 
3270
    method. (Bugfix)
 
3271
    
 
3272
    2005/08/14
 
3273
    ----------
 
3274
    
 
3275
    By Michael Foord
 
3276
    
 
3277
    Fixed ``simpleVal``.
 
3278
    
 
3279
    Added ``RepeatSectionError`` error if you have additional sections in a
 
3280
    section with a ``__many__`` (repeated) section.
 
3281
    
 
3282
    By Nicola Larosa
 
3283
    
 
3284
    Reworked the ConfigObj._parse, _handle_error and _multiline methods:
 
3285
    mutated the self._infile, self._index and self._maxline attributes into
 
3286
    local variables and method parameters
 
3287
    
 
3288
    Reshaped the ConfigObj._multiline method to better reflect its semantics
 
3289
    
 
3290
    Changed the "default_test" test in ConfigObj.validate to check the fix for
 
3291
    the bug in validate.Validator.check
 
3292
    
 
3293
    2005/08/13
 
3294
    ----------
 
3295
    
 
3296
    By Nicola Larosa
 
3297
    
 
3298
    Updated comments at top
 
3299
    
 
3300
    2005/08/11
 
3301
    ----------
 
3302
    
 
3303
    By Michael Foord
 
3304
    
 
3305
    Implemented repeated sections.
 
3306
    
 
3307
    By Nicola Larosa
 
3308
    
 
3309
    Added test for interpreter version: raises RuntimeError if earlier than
 
3310
    2.2
 
3311
    
 
3312
    2005/08/10
 
3313
    ----------
 
3314
   
 
3315
    By Michael Foord
 
3316
     
 
3317
    Implemented default values in configspecs.
 
3318
    
 
3319
    By Nicola Larosa
 
3320
    
 
3321
    Fixed naked except: clause in validate that was silencing the fact
 
3322
    that Python2.2 does not have dict.pop
 
3323
    
 
3324
    2005/08/08
 
3325
    ----------
 
3326
    
 
3327
    By Michael Foord
 
3328
    
 
3329
    Bug fix causing error if file didn't exist.
 
3330
    
 
3331
    2005/08/07
 
3332
    ----------
 
3333
    
 
3334
    By Nicola Larosa
 
3335
    
 
3336
    Adjusted doctests for Python 2.2.3 compatibility
 
3337
    
 
3338
    2005/08/04
 
3339
    ----------
 
3340
    
 
3341
    By Michael Foord
 
3342
    
 
3343
    Added the inline_comments attribute
 
3344
    
 
3345
    We now preserve and rewrite all comments in the config file
 
3346
    
 
3347
    configspec is now a section attribute
 
3348
    
 
3349
    The validate method changes values in place
 
3350
    
 
3351
    Added InterpolationError
 
3352
    
 
3353
    The errors now have line number, line, and message attributes. This
 
3354
    simplifies error handling
 
3355
    
 
3356
    Added __docformat__
 
3357
    
 
3358
    2005/08/03
 
3359
    ----------
 
3360
    
 
3361
    By Michael Foord
 
3362
    
 
3363
    Fixed bug in Section.pop (now doesn't raise KeyError if a default value
 
3364
    is specified)
 
3365
    
 
3366
    Replaced ``basestring`` with ``types.StringTypes``
 
3367
    
 
3368
    Removed the ``writein`` method
 
3369
    
 
3370
    Added __version__
 
3371
    
 
3372
    2005/07/29
 
3373
    ----------
 
3374
    
 
3375
    By Nicola Larosa
 
3376
    
 
3377
    Indentation in config file is not significant anymore, subsections are
 
3378
    designated by repeating square brackets
 
3379
    
 
3380
    Adapted all tests and docs to the new format
 
3381
    
 
3382
    2005/07/28
 
3383
    ----------
 
3384
    
 
3385
    By Nicola Larosa
 
3386
    
 
3387
    Added more tests
 
3388
    
 
3389
    2005/07/23
 
3390
    ----------
 
3391
    
 
3392
    By Nicola Larosa
 
3393
    
 
3394
    Reformatted final docstring in ReST format, indented it for easier folding
 
3395
    
 
3396
    Code tests converted to doctest format, and scattered them around
 
3397
    in various docstrings
 
3398
    
 
3399
    Walk method rewritten using scalars and sections attributes
 
3400
    
 
3401
    2005/07/22
 
3402
    ----------
 
3403
    
 
3404
    By Nicola Larosa
 
3405
    
 
3406
    Changed Validator and SimpleVal "test" methods to "check"
 
3407
    
 
3408
    More code cleanup
 
3409
    
 
3410
    2005/07/21
 
3411
    ----------
 
3412
    
 
3413
    Changed Section.sequence to Section.scalars and Section.sections
 
3414
    
 
3415
    Added Section.configspec
 
3416
    
 
3417
    Sections in the root section now have no extra indentation
 
3418
    
 
3419
    Comments now better supported in Section and preserved by ConfigObj
 
3420
    
 
3421
    Comments also written out
 
3422
    
 
3423
    Implemented initial_comment and final_comment
 
3424
    
 
3425
    A scalar value after a section will now raise an error
 
3426
    
 
3427
    2005/07/20
 
3428
    ----------
 
3429
    
 
3430
    Fixed a couple of bugs
 
3431
    
 
3432
    Can now pass a tuple instead of a list
 
3433
    
 
3434
    Simplified dict and walk methods
 
3435
    
 
3436
    Added __str__ to Section
 
3437
    
 
3438
    2005/07/10
 
3439
    ----------
 
3440
    
 
3441
    By Nicola Larosa
 
3442
    
 
3443
    More code cleanup
 
3444
    
 
3445
    2005/07/08
 
3446
    ----------
 
3447
    
 
3448
    The stringify option implemented. On by default.
 
3449
    
 
3450
    2005/07/07
 
3451
    ----------
 
3452
    
 
3453
    Renamed private attributes with a single underscore prefix.
 
3454
    
 
3455
    Changes to interpolation - exceeding recursion depth, or specifying a
 
3456
    missing value, now raise errors.
 
3457
    
 
3458
    Changes for Python 2.2 compatibility. (changed boolean tests - removed
 
3459
    ``is True`` and ``is False``)
 
3460
    
 
3461
    Added test for duplicate section and member (and fixed bug)
 
3462
    
 
3463
    2005/07/06
 
3464
    ----------
 
3465
    
 
3466
    By Nicola Larosa
 
3467
    
 
3468
    Code cleanup
 
3469
    
 
3470
    2005/07/02
 
3471
    ----------
 
3472
    
 
3473
    Version 0.1.0
 
3474
    
 
3475
    Now properly handles values including comments and lists.
 
3476
    
 
3477
    Better error handling.
 
3478
    
 
3479
    String interpolation.
 
3480
    
 
3481
    Some options implemented.
 
3482
    
 
3483
    You can pass a Section a dictionary to initialise it.
 
3484
    
 
3485
    Setting a Section member to a dictionary will create a Section instance.
 
3486
    
 
3487
    2005/06/26
 
3488
    ----------
 
3489
    
 
3490
    Version 0.0.1
 
3491
    
 
3492
    Experimental reader.
 
3493
    
 
3494
    A reasonably elegant implementation - a basic reader in 160 lines of code.
 
3495
    
 
3496
    *A programming language is a medium of expression.* - Paul Graham
 
3497
"""
 
3498