~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2005-06-06 13:24:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050606132418-1082c87e2473e266
- manpage generator by Hans Ulrich Niedermann

Show diffs side-by-side

added added

removed removed

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