~free.ekanayaka/landscape-client/karmic-updates-1.4.4-0ubuntu0.9.10

« back to all changes in this revision

Viewing changes to landscape/lib/configobj.py

  • Committer: Bazaar Package Importer
  • Author(s): Rick Clark
  • Date: 2008-09-08 16:35:57 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080908163557-l3ixzj5dxz37wnw2
Tags: 1.0.18-0ubuntu1
New upstream release 

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