~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to config/configobj.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# configobj.py
2
 
# A config file reader/writer that supports nested sections in config files.
3
 
# Copyright (C) 2005-2006 Michael Foord, Nicola Larosa
4
 
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
 
#         nico AT tekNico DOT net
6
 
 
7
 
# ConfigObj 4
8
 
# http://www.voidspace.org.uk/python/configobj.html
9
 
 
10
 
# Released subject to the BSD License
11
 
# Please see http://www.voidspace.org.uk/python/license.shtml
12
 
 
13
 
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14
 
# For information about bugfixes, updates and support, please join the
15
 
# ConfigObj mailing list:
16
 
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17
 
# Comments, suggestions and bug reports welcome.
18
 
 
19
 
from __future__ import generators
20
 
 
21
 
import sys
22
 
INTP_VER = sys.version_info[:2]
23
 
if INTP_VER < (2, 2):
24
 
    raise RuntimeError("Python v.2.2 or later needed")
25
 
 
26
 
import os, re
27
 
compiler = None
28
 
try:
29
 
    import compiler
30
 
except ImportError:
31
 
    # for IronPython
32
 
    pass
33
 
from types import StringTypes
34
 
from warnings import warn
35
 
try:
36
 
    from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
37
 
except ImportError:
38
 
    # Python 2.2 does not have these
39
 
    # UTF-8
40
 
    BOM_UTF8 = '\xef\xbb\xbf'
41
 
    # UTF-16, little endian
42
 
    BOM_UTF16_LE = '\xff\xfe'
43
 
    # UTF-16, big endian
44
 
    BOM_UTF16_BE = '\xfe\xff'
45
 
    if sys.byteorder == 'little':
46
 
        # UTF-16, native endianness
47
 
        BOM_UTF16 = BOM_UTF16_LE
48
 
    else:
49
 
        # UTF-16, native endianness
50
 
        BOM_UTF16 = BOM_UTF16_BE
51
 
 
52
 
# A dictionary mapping BOM to
53
 
# the encoding to decode with, and what to set the
54
 
# encoding attribute to.
55
 
BOMS = {
56
 
    BOM_UTF8: ('utf_8', None),
57
 
    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
58
 
    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
59
 
    BOM_UTF16: ('utf_16', 'utf_16'),
60
 
    }
61
 
# All legal variants of the BOM codecs.
62
 
# TODO: the list of aliases is not meant to be exhaustive, is there a
63
 
#   better way ?
64
 
BOM_LIST = {
65
 
    'utf_16': 'utf_16',
66
 
    'u16': 'utf_16',
67
 
    'utf16': 'utf_16',
68
 
    'utf-16': 'utf_16',
69
 
    'utf16_be': 'utf16_be',
70
 
    'utf_16_be': 'utf16_be',
71
 
    'utf-16be': 'utf16_be',
72
 
    'utf16_le': 'utf16_le',
73
 
    'utf_16_le': 'utf16_le',
74
 
    'utf-16le': 'utf16_le',
75
 
    'utf_8': 'utf_8',
76
 
    'u8': 'utf_8',
77
 
    'utf': 'utf_8',
78
 
    'utf8': 'utf_8',
79
 
    'utf-8': 'utf_8',
80
 
    }
81
 
 
82
 
# Map of encodings to the BOM to write.
83
 
BOM_SET = {
84
 
    'utf_8': BOM_UTF8,
85
 
    'utf_16': BOM_UTF16,
86
 
    'utf16_be': BOM_UTF16_BE,
87
 
    'utf16_le': BOM_UTF16_LE,
88
 
    None: BOM_UTF8
89
 
    }
90
 
 
91
 
try:
92
 
    from validate import VdtMissingValue
93
 
except ImportError:
94
 
    VdtMissingValue = None
95
 
 
96
 
try:
97
 
    enumerate
98
 
except NameError:
99
 
    def enumerate(obj):
100
 
        """enumerate for Python 2.2."""
101
 
        i = -1
102
 
        for item in obj:
103
 
            i += 1
104
 
            yield i, item
105
 
 
106
 
try:
107
 
    True, False
108
 
except NameError:
109
 
    True, False = 1, 0
110
 
 
111
 
 
112
 
__version__ = '4.4.0'
113
 
 
114
 
__revision__ = '$Id: configobj.py,v 3.5 2007/07/02 18:20:24 benjamin%smedbergs.us Exp $'
115
 
 
116
 
__docformat__ = "restructuredtext en"
117
 
 
118
 
__all__ = (
119
 
    '__version__',
120
 
    'DEFAULT_INDENT_TYPE',
121
 
    'DEFAULT_INTERPOLATION',
122
 
    'ConfigObjError',
123
 
    'NestingError',
124
 
    'ParseError',
125
 
    'DuplicateError',
126
 
    'ConfigspecError',
127
 
    'ConfigObj',
128
 
    'SimpleVal',
129
 
    'InterpolationError',
130
 
    'InterpolationLoopError',
131
 
    'MissingInterpolationOption',
132
 
    'RepeatSectionError',
133
 
    'UnreprError',
134
 
    'UnknownType',
135
 
    '__docformat__',
136
 
    'flatten_errors',
137
 
)
138
 
 
139
 
DEFAULT_INTERPOLATION = 'configparser'
140
 
DEFAULT_INDENT_TYPE = '    '
141
 
MAX_INTERPOL_DEPTH = 10
142
 
 
143
 
OPTION_DEFAULTS = {
144
 
    'interpolation': True,
145
 
    'raise_errors': False,
146
 
    'list_values': True,
147
 
    'create_empty': False,
148
 
    'file_error': False,
149
 
    'configspec': None,
150
 
    'stringify': True,
151
 
    # option may be set to one of ('', ' ', '\t')
152
 
    'indent_type': None,
153
 
    'encoding': None,
154
 
    'default_encoding': None,
155
 
    'unrepr': False,
156
 
    'write_empty_values': False,
157
 
}
158
 
 
159
 
 
160
 
def getObj(s):
161
 
    s = "a=" + s
162
 
    if compiler is None:
163
 
        raise ImportError('compiler module not available')
164
 
    p = compiler.parse(s)
165
 
    return p.getChildren()[1].getChildren()[0].getChildren()[1]
166
 
 
167
 
class UnknownType(Exception):
168
 
    pass
169
 
 
170
 
class Builder:
171
 
    
172
 
    def build(self, o):
173
 
        m = getattr(self, 'build_' + o.__class__.__name__, None)
174
 
        if m is None:
175
 
            raise UnknownType(o.__class__.__name__)
176
 
        return m(o)
177
 
    
178
 
    def build_List(self, o):
179
 
        return map(self.build, o.getChildren())
180
 
    
181
 
    def build_Const(self, o):
182
 
        return o.value
183
 
    
184
 
    def build_Dict(self, o):
185
 
        d = {}
186
 
        i = iter(map(self.build, o.getChildren()))
187
 
        for el in i:
188
 
            d[el] = i.next()
189
 
        return d
190
 
    
191
 
    def build_Tuple(self, o):
192
 
        return tuple(self.build_List(o))
193
 
    
194
 
    def build_Name(self, o):
195
 
        if o.name == 'None':
196
 
            return None
197
 
        if o.name == 'True':
198
 
            return True
199
 
        if o.name == 'False':
200
 
            return False
201
 
        
202
 
        # An undefinted Name
203
 
        raise UnknownType('Undefined Name')
204
 
    
205
 
    def build_Add(self, o):
206
 
        real, imag = map(self.build_Const, o.getChildren())
207
 
        try:
208
 
            real = float(real)
209
 
        except TypeError:
210
 
            raise UnknownType('Add')
211
 
        if not isinstance(imag, complex) or imag.real != 0.0:
212
 
            raise UnknownType('Add')
213
 
        return real+imag
214
 
    
215
 
    def build_Getattr(self, o):
216
 
        parent = self.build(o.expr)
217
 
        return getattr(parent, o.attrname)
218
 
    
219
 
    def build_UnarySub(self, o):
220
 
        return -self.build_Const(o.getChildren()[0])
221
 
    
222
 
    def build_UnaryAdd(self, o):
223
 
        return self.build_Const(o.getChildren()[0])
224
 
 
225
 
def unrepr(s):
226
 
    if not s:
227
 
        return s
228
 
    return Builder().build(getObj(s))
229
 
 
230
 
def _splitlines(instring):
231
 
    """Split a string on lines, without losing line endings or truncating."""
232
 
    
233
 
 
234
 
class ConfigObjError(SyntaxError):
235
 
    """
236
 
    This is the base class for all errors that ConfigObj raises.
237
 
    It is a subclass of SyntaxError.
238
 
    """
239
 
    def __init__(self, message='', line_number=None, line=''):
240
 
        self.line = line
241
 
        self.line_number = line_number
242
 
        self.message = message
243
 
        SyntaxError.__init__(self, message)
244
 
 
245
 
class NestingError(ConfigObjError):
246
 
    """
247
 
    This error indicates a level of nesting that doesn't match.
248
 
    """
249
 
 
250
 
class ParseError(ConfigObjError):
251
 
    """
252
 
    This error indicates that a line is badly written.
253
 
    It is neither a valid ``key = value`` line,
254
 
    nor a valid section marker line.
255
 
    """
256
 
 
257
 
class DuplicateError(ConfigObjError):
258
 
    """
259
 
    The keyword or section specified already exists.
260
 
    """
261
 
 
262
 
class ConfigspecError(ConfigObjError):
263
 
    """
264
 
    An error occurred whilst parsing a configspec.
265
 
    """
266
 
 
267
 
class InterpolationError(ConfigObjError):
268
 
    """Base class for the two interpolation errors."""
269
 
 
270
 
class InterpolationLoopError(InterpolationError):
271
 
    """Maximum interpolation depth exceeded in string interpolation."""
272
 
 
273
 
    def __init__(self, option):
274
 
        InterpolationError.__init__(
275
 
            self,
276
 
            'interpolation loop detected in value "%s".' % option)
277
 
 
278
 
class RepeatSectionError(ConfigObjError):
279
 
    """
280
 
    This error indicates additional sections in a section with a
281
 
    ``__many__`` (repeated) section.
282
 
    """
283
 
 
284
 
class MissingInterpolationOption(InterpolationError):
285
 
    """A value specified for interpolation was missing."""
286
 
 
287
 
    def __init__(self, option):
288
 
        InterpolationError.__init__(
289
 
            self,
290
 
            'missing option "%s" in interpolation.' % option)
291
 
 
292
 
class UnreprError(ConfigObjError):
293
 
    """An error parsing in unrepr mode."""
294
 
 
295
 
 
296
 
class InterpolationEngine(object):
297
 
    """
298
 
    A helper class to help perform string interpolation.
299
 
 
300
 
    This class is an abstract base class; its descendants perform
301
 
    the actual work.
302
 
    """
303
 
 
304
 
    # compiled regexp to use in self.interpolate()
305
 
    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
306
 
 
307
 
    def __init__(self, section):
308
 
        # the Section instance that "owns" this engine
309
 
        self.section = section
310
 
 
311
 
    def interpolate(self, key, value):
312
 
        def recursive_interpolate(key, value, section, backtrail):
313
 
            """The function that does the actual work.
314
 
 
315
 
            ``value``: the string we're trying to interpolate.
316
 
            ``section``: the section in which that string was found
317
 
            ``backtrail``: a dict to keep track of where we've been,
318
 
            to detect and prevent infinite recursion loops
319
 
 
320
 
            This is similar to a depth-first-search algorithm.
321
 
            """
322
 
            # Have we been here already?
323
 
            if backtrail.has_key((key, section.name)):
324
 
                # Yes - infinite loop detected
325
 
                raise InterpolationLoopError(key)
326
 
            # Place a marker on our backtrail so we won't come back here again
327
 
            backtrail[(key, section.name)] = 1
328
 
 
329
 
            # Now start the actual work
330
 
            match = self._KEYCRE.search(value)
331
 
            while match:
332
 
                # The actual parsing of the match is implementation-dependent,
333
 
                # so delegate to our helper function
334
 
                k, v, s = self._parse_match(match)
335
 
                if k is None:
336
 
                    # That's the signal that no further interpolation is needed
337
 
                    replacement = v
338
 
                else:
339
 
                    # Further interpolation may be needed to obtain final value
340
 
                    replacement = recursive_interpolate(k, v, s, backtrail)
341
 
                # Replace the matched string with its final value
342
 
                start, end = match.span()
343
 
                value = ''.join((value[:start], replacement, value[end:]))
344
 
                new_search_start = start + len(replacement)
345
 
                # Pick up the next interpolation key, if any, for next time
346
 
                # through the while loop
347
 
                match = self._KEYCRE.search(value, new_search_start)
348
 
 
349
 
            # Now safe to come back here again; remove marker from backtrail
350
 
            del backtrail[(key, section.name)]
351
 
 
352
 
            return value
353
 
 
354
 
        # Back in interpolate(), all we have to do is kick off the recursive
355
 
        # function with appropriate starting values
356
 
        value = recursive_interpolate(key, value, self.section, {})
357
 
        return value
358
 
 
359
 
    def _fetch(self, key):
360
 
        """Helper function to fetch values from owning section.
361
 
 
362
 
        Returns a 2-tuple: the value, and the section where it was found.
363
 
        """
364
 
        # switch off interpolation before we try and fetch anything !
365
 
        save_interp = self.section.main.interpolation
366
 
        self.section.main.interpolation = False
367
 
 
368
 
        # Start at section that "owns" this InterpolationEngine
369
 
        current_section = self.section
370
 
        while True:
371
 
            # try the current section first
372
 
            val = current_section.get(key)
373
 
            if val is not None:
374
 
                break
375
 
            # try "DEFAULT" next
376
 
            val = current_section.get('DEFAULT', {}).get(key)
377
 
            if val is not None:
378
 
                break
379
 
            # move up to parent and try again
380
 
            # top-level's parent is itself
381
 
            if current_section.parent is current_section:
382
 
                # reached top level, time to give up
383
 
                break
384
 
            current_section = current_section.parent
385
 
 
386
 
        # restore interpolation to previous value before returning
387
 
        self.section.main.interpolation = save_interp
388
 
        if val is None:
389
 
            raise MissingInterpolationOption(key)
390
 
        return val, current_section
391
 
 
392
 
    def _parse_match(self, match):
393
 
        """Implementation-dependent helper function.
394
 
 
395
 
        Will be passed a match object corresponding to the interpolation
396
 
        key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
397
 
        key in the appropriate config file section (using the ``_fetch()``
398
 
        helper function) and return a 3-tuple: (key, value, section)
399
 
 
400
 
        ``key`` is the name of the key we're looking for
401
 
        ``value`` is the value found for that key
402
 
        ``section`` is a reference to the section where it was found
403
 
 
404
 
        ``key`` and ``section`` should be None if no further
405
 
        interpolation should be performed on the resulting value
406
 
        (e.g., if we interpolated "$$" and returned "$").
407
 
        """
408
 
        raise NotImplementedError
409
 
    
410
 
 
411
 
class ConfigParserInterpolation(InterpolationEngine):
412
 
    """Behaves like ConfigParser."""
413
 
    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
414
 
 
415
 
    def _parse_match(self, match):
416
 
        key = match.group(1)
417
 
        value, section = self._fetch(key)
418
 
        return key, value, section
419
 
 
420
 
 
421
 
class TemplateInterpolation(InterpolationEngine):
422
 
    """Behaves like string.Template."""
423
 
    _delimiter = '$'
424
 
    _KEYCRE = re.compile(r"""
425
 
        \$(?:
426
 
          (?P<escaped>\$)              |   # Two $ signs
427
 
          (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
428
 
          {(?P<braced>[^}]*)}              # ${name} format
429
 
        )
430
 
        """, re.IGNORECASE | re.VERBOSE)
431
 
 
432
 
    def _parse_match(self, match):
433
 
        # Valid name (in or out of braces): fetch value from section
434
 
        key = match.group('named') or match.group('braced')
435
 
        if key is not None:
436
 
            value, section = self._fetch(key)
437
 
            return key, value, section
438
 
        # Escaped delimiter (e.g., $$): return single delimiter
439
 
        if match.group('escaped') is not None:
440
 
            # Return None for key and section to indicate it's time to stop
441
 
            return None, self._delimiter, None
442
 
        # Anything else: ignore completely, just return it unchanged
443
 
        return None, match.group(), None
444
 
 
445
 
interpolation_engines = {
446
 
    'configparser': ConfigParserInterpolation,
447
 
    'template': TemplateInterpolation,
448
 
}
449
 
 
450
 
class Section(dict):
451
 
    """
452
 
    A dictionary-like object that represents a section in a config file.
453
 
    
454
 
    It does string interpolation if the 'interpolation' attribute
455
 
    of the 'main' object is set to True.
456
 
    
457
 
    Interpolation is tried first from this object, then from the 'DEFAULT'
458
 
    section of this object, next from the parent and its 'DEFAULT' section,
459
 
    and so on until the main object is reached.
460
 
    
461
 
    A Section will behave like an ordered dictionary - following the
462
 
    order of the ``scalars`` and ``sections`` attributes.
463
 
    You can use this to change the order of members.
464
 
    
465
 
    Iteration follows the order: scalars, then sections.
466
 
    """
467
 
 
468
 
    def __init__(self, parent, depth, main, indict=None, name=None):
469
 
        """
470
 
        * parent is the section above
471
 
        * depth is the depth level of this section
472
 
        * main is the main ConfigObj
473
 
        * indict is a dictionary to initialise the section with
474
 
        """
475
 
        if indict is None:
476
 
            indict = {}
477
 
        dict.__init__(self)
478
 
        # used for nesting level *and* interpolation
479
 
        self.parent = parent
480
 
        # used for the interpolation attribute
481
 
        self.main = main
482
 
        # level of nesting depth of this Section
483
 
        self.depth = depth
484
 
        # the sequence of scalar values in this Section
485
 
        self.scalars = []
486
 
        # the sequence of sections in this Section
487
 
        self.sections = []
488
 
        # purely for information
489
 
        self.name = name
490
 
        # for comments :-)
491
 
        self.comments = {}
492
 
        self.inline_comments = {}
493
 
        # for the configspec
494
 
        self.configspec = {}
495
 
        self._order = []
496
 
        self._configspec_comments = {}
497
 
        self._configspec_inline_comments = {}
498
 
        self._cs_section_comments = {}
499
 
        self._cs_section_inline_comments = {}
500
 
        # for defaults
501
 
        self.defaults = []
502
 
        #
503
 
        # we do this explicitly so that __setitem__ is used properly
504
 
        # (rather than just passing to ``dict.__init__``)
505
 
        for entry in indict:
506
 
            self[entry] = indict[entry]
507
 
 
508
 
    def _interpolate(self, key, value):
509
 
        try:
510
 
            # do we already have an interpolation engine?
511
 
            engine = self._interpolation_engine
512
 
        except AttributeError:
513
 
            # not yet: first time running _interpolate(), so pick the engine
514
 
            name = self.main.interpolation
515
 
            if name == True:  # note that "if name:" would be incorrect here
516
 
                # backwards-compatibility: interpolation=True means use default
517
 
                name = DEFAULT_INTERPOLATION
518
 
            name = name.lower()  # so that "Template", "template", etc. all work
519
 
            class_ = interpolation_engines.get(name, None)
520
 
            if class_ is None:
521
 
                # invalid value for self.main.interpolation
522
 
                self.main.interpolation = False
523
 
                return value
524
 
            else:
525
 
                # save reference to engine so we don't have to do this again
526
 
                engine = self._interpolation_engine = class_(self)
527
 
        # let the engine do the actual work
528
 
        return engine.interpolate(key, value)
529
 
 
530
 
    def __getitem__(self, key):
531
 
        """Fetch the item and do string interpolation."""
532
 
        val = dict.__getitem__(self, key)
533
 
        if self.main.interpolation and isinstance(val, StringTypes):
534
 
            return self._interpolate(key, val)
535
 
        return val
536
 
 
537
 
    def __setitem__(self, key, value, unrepr=False):
538
 
        """
539
 
        Correctly set a value.
540
 
        
541
 
        Making dictionary values Section instances.
542
 
        (We have to special case 'Section' instances - which are also dicts)
543
 
        
544
 
        Keys must be strings.
545
 
        Values need only be strings (or lists of strings) if
546
 
        ``main.stringify`` is set.
547
 
        
548
 
        `unrepr`` must be set when setting a value to a dictionary, without
549
 
        creating a new sub-section.
550
 
        """
551
 
        if not isinstance(key, StringTypes):
552
 
            raise ValueError, 'The key "%s" is not a string.' % key
553
 
        # add the comment
554
 
        if not self.comments.has_key(key):
555
 
            self.comments[key] = []
556
 
            self.inline_comments[key] = ''
557
 
        # remove the entry from defaults
558
 
        if key in self.defaults:
559
 
            self.defaults.remove(key)
560
 
        #
561
 
        if isinstance(value, Section):
562
 
            if not self.has_key(key):
563
 
                self.sections.append(key)
564
 
            dict.__setitem__(self, key, value)
565
 
        elif isinstance(value, dict) and not unrepr:
566
 
            # First create the new depth level,
567
 
            # then create the section
568
 
            if not self.has_key(key):
569
 
                self.sections.append(key)
570
 
            new_depth = self.depth + 1
571
 
            dict.__setitem__(
572
 
                self,
573
 
                key,
574
 
                Section(
575
 
                    self,
576
 
                    new_depth,
577
 
                    self.main,
578
 
                    indict=value,
579
 
                    name=key))
580
 
        else:
581
 
            if not self.has_key(key):
582
 
                self.scalars.append(key)
583
 
            if not self.main.stringify:
584
 
                if isinstance(value, StringTypes):
585
 
                    pass
586
 
                elif isinstance(value, (list, tuple)):
587
 
                    for entry in value:
588
 
                        if not isinstance(entry, StringTypes):
589
 
                            raise TypeError, (
590
 
                                'Value is not a string "%s".' % entry)
591
 
                else:
592
 
                    raise TypeError, 'Value is not a string "%s".' % value
593
 
            dict.__setitem__(self, key, value)
594
 
 
595
 
    def __delitem__(self, key):
596
 
        """Remove items from the sequence when deleting."""
597
 
        dict. __delitem__(self, key)
598
 
        if key in self.scalars:
599
 
            self.scalars.remove(key)
600
 
        else:
601
 
            self.sections.remove(key)
602
 
        del self.comments[key]
603
 
        del self.inline_comments[key]
604
 
 
605
 
    def get(self, key, default=None):
606
 
        """A version of ``get`` that doesn't bypass string interpolation."""
607
 
        try:
608
 
            return self[key]
609
 
        except KeyError:
610
 
            return default
611
 
 
612
 
    def update(self, indict):
613
 
        """
614
 
        A version of update that uses our ``__setitem__``.
615
 
        """
616
 
        for entry in indict:
617
 
            self[entry] = indict[entry]
618
 
 
619
 
    def pop(self, key, *args):
620
 
        """ """
621
 
        val = dict.pop(self, key, *args)
622
 
        if key in self.scalars:
623
 
            del self.comments[key]
624
 
            del self.inline_comments[key]
625
 
            self.scalars.remove(key)
626
 
        elif key in self.sections:
627
 
            del self.comments[key]
628
 
            del self.inline_comments[key]
629
 
            self.sections.remove(key)
630
 
        if self.main.interpolation and isinstance(val, StringTypes):
631
 
            return self._interpolate(key, val)
632
 
        return val
633
 
 
634
 
    def popitem(self):
635
 
        """Pops the first (key,val)"""
636
 
        sequence = (self.scalars + self.sections)
637
 
        if not sequence:
638
 
            raise KeyError, ": 'popitem(): dictionary is empty'"
639
 
        key = sequence[0]
640
 
        val =  self[key]
641
 
        del self[key]
642
 
        return key, val
643
 
 
644
 
    def clear(self):
645
 
        """
646
 
        A version of clear that also affects scalars/sections
647
 
        Also clears comments and configspec.
648
 
        
649
 
        Leaves other attributes alone :
650
 
            depth/main/parent are not affected
651
 
        """
652
 
        dict.clear(self)
653
 
        self.scalars = []
654
 
        self.sections = []
655
 
        self.comments = {}
656
 
        self.inline_comments = {}
657
 
        self.configspec = {}
658
 
 
659
 
    def setdefault(self, key, default=None):
660
 
        """A version of setdefault that sets sequence if appropriate."""
661
 
        try:
662
 
            return self[key]
663
 
        except KeyError:
664
 
            self[key] = default
665
 
            return self[key]
666
 
 
667
 
    def items(self):
668
 
        """ """
669
 
        return zip((self.scalars + self.sections), self.values())
670
 
 
671
 
    def keys(self):
672
 
        """ """
673
 
        return (self.scalars + self.sections)
674
 
 
675
 
    def values(self):
676
 
        """ """
677
 
        return [self[key] for key in (self.scalars + self.sections)]
678
 
 
679
 
    def iteritems(self):
680
 
        """ """
681
 
        return iter(self.items())
682
 
 
683
 
    def iterkeys(self):
684
 
        """ """
685
 
        return iter((self.scalars + self.sections))
686
 
 
687
 
    __iter__ = iterkeys
688
 
 
689
 
    def itervalues(self):
690
 
        """ """
691
 
        return iter(self.values())
692
 
 
693
 
    def __repr__(self):
694
 
        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
695
 
            for key in (self.scalars + self.sections)])
696
 
 
697
 
    __str__ = __repr__
698
 
 
699
 
    # Extra methods - not in a normal dictionary
700
 
 
701
 
    def dict(self):
702
 
        """
703
 
        Return a deepcopy of self as a dictionary.
704
 
        
705
 
        All members that are ``Section`` instances are recursively turned to
706
 
        ordinary dictionaries - by calling their ``dict`` method.
707
 
        
708
 
        >>> n = a.dict()
709
 
        >>> n == a
710
 
        1
711
 
        >>> n is a
712
 
        0
713
 
        """
714
 
        newdict = {}
715
 
        for entry in self:
716
 
            this_entry = self[entry]
717
 
            if isinstance(this_entry, Section):
718
 
                this_entry = this_entry.dict()
719
 
            elif isinstance(this_entry, list):
720
 
                # create a copy rather than a reference
721
 
                this_entry = list(this_entry)
722
 
            elif isinstance(this_entry, tuple):
723
 
                # create a copy rather than a reference
724
 
                this_entry = tuple(this_entry)
725
 
            newdict[entry] = this_entry
726
 
        return newdict
727
 
 
728
 
    def merge(self, indict):
729
 
        """
730
 
        A recursive update - useful for merging config files.
731
 
        
732
 
        >>> a = '''[section1]
733
 
        ...     option1 = True
734
 
        ...     [[subsection]]
735
 
        ...     more_options = False
736
 
        ...     # end of file'''.splitlines()
737
 
        >>> b = '''# File is user.ini
738
 
        ...     [section1]
739
 
        ...     option1 = False
740
 
        ...     # end of file'''.splitlines()
741
 
        >>> c1 = ConfigObj(b)
742
 
        >>> c2 = ConfigObj(a)
743
 
        >>> c2.merge(c1)
744
 
        >>> c2
745
 
        {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
746
 
        """
747
 
        for key, val in indict.items():
748
 
            if (key in self and isinstance(self[key], dict) and
749
 
                                isinstance(val, dict)):
750
 
                self[key].merge(val)
751
 
            else:   
752
 
                self[key] = val
753
 
 
754
 
    def rename(self, oldkey, newkey):
755
 
        """
756
 
        Change a keyname to another, without changing position in sequence.
757
 
        
758
 
        Implemented so that transformations can be made on keys,
759
 
        as well as on values. (used by encode and decode)
760
 
        
761
 
        Also renames comments.
762
 
        """
763
 
        if oldkey in self.scalars:
764
 
            the_list = self.scalars
765
 
        elif oldkey in self.sections:
766
 
            the_list = self.sections
767
 
        else:
768
 
            raise KeyError, 'Key "%s" not found.' % oldkey
769
 
        pos = the_list.index(oldkey)
770
 
        #
771
 
        val = self[oldkey]
772
 
        dict.__delitem__(self, oldkey)
773
 
        dict.__setitem__(self, newkey, val)
774
 
        the_list.remove(oldkey)
775
 
        the_list.insert(pos, newkey)
776
 
        comm = self.comments[oldkey]
777
 
        inline_comment = self.inline_comments[oldkey]
778
 
        del self.comments[oldkey]
779
 
        del self.inline_comments[oldkey]
780
 
        self.comments[newkey] = comm
781
 
        self.inline_comments[newkey] = inline_comment
782
 
 
783
 
    def walk(self, function, raise_errors=True,
784
 
            call_on_sections=False, **keywargs):
785
 
        """
786
 
        Walk every member and call a function on the keyword and value.
787
 
        
788
 
        Return a dictionary of the return values
789
 
        
790
 
        If the function raises an exception, raise the errror
791
 
        unless ``raise_errors=False``, in which case set the return value to
792
 
        ``False``.
793
 
        
794
 
        Any unrecognised keyword arguments you pass to walk, will be pased on
795
 
        to the function you pass in.
796
 
        
797
 
        Note: if ``call_on_sections`` is ``True`` then - on encountering a
798
 
        subsection, *first* the function is called for the *whole* subsection,
799
 
        and then recurses into its members. This means your function must be
800
 
        able to handle strings, dictionaries and lists. This allows you
801
 
        to change the key of subsections as well as for ordinary members. The
802
 
        return value when called on the whole subsection has to be discarded.
803
 
        
804
 
        See  the encode and decode methods for examples, including functions.
805
 
        
806
 
        .. caution::
807
 
        
808
 
            You can use ``walk`` to transform the names of members of a section
809
 
            but you mustn't add or delete members.
810
 
        
811
 
        >>> config = '''[XXXXsection]
812
 
        ... XXXXkey = XXXXvalue'''.splitlines()
813
 
        >>> cfg = ConfigObj(config)
814
 
        >>> cfg
815
 
        {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
816
 
        >>> def transform(section, key):
817
 
        ...     val = section[key]
818
 
        ...     newkey = key.replace('XXXX', 'CLIENT1')
819
 
        ...     section.rename(key, newkey)
820
 
        ...     if isinstance(val, (tuple, list, dict)):
821
 
        ...         pass
822
 
        ...     else:
823
 
        ...         val = val.replace('XXXX', 'CLIENT1')
824
 
        ...         section[newkey] = val
825
 
        >>> cfg.walk(transform, call_on_sections=True)
826
 
        {'CLIENT1section': {'CLIENT1key': None}}
827
 
        >>> cfg
828
 
        {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
829
 
        """
830
 
        out = {}
831
 
        # scalars first
832
 
        for i in range(len(self.scalars)):
833
 
            entry = self.scalars[i]
834
 
            try:
835
 
                val = function(self, entry, **keywargs)
836
 
                # bound again in case name has changed
837
 
                entry = self.scalars[i]
838
 
                out[entry] = val
839
 
            except Exception:
840
 
                if raise_errors:
841
 
                    raise
842
 
                else:
843
 
                    entry = self.scalars[i]
844
 
                    out[entry] = False
845
 
        # then sections
846
 
        for i in range(len(self.sections)):
847
 
            entry = self.sections[i]
848
 
            if call_on_sections:
849
 
                try:
850
 
                    function(self, entry, **keywargs)
851
 
                except Exception:
852
 
                    if raise_errors:
853
 
                        raise
854
 
                    else:
855
 
                        entry = self.sections[i]
856
 
                        out[entry] = False
857
 
                # bound again in case name has changed
858
 
                entry = self.sections[i]
859
 
            # previous result is discarded
860
 
            out[entry] = self[entry].walk(
861
 
                function,
862
 
                raise_errors=raise_errors,
863
 
                call_on_sections=call_on_sections,
864
 
                **keywargs)
865
 
        return out
866
 
 
867
 
    def decode(self, encoding):
868
 
        """
869
 
        Decode all strings and values to unicode, using the specified encoding.
870
 
        
871
 
        Works with subsections and list values.
872
 
        
873
 
        Uses the ``walk`` method.
874
 
        
875
 
        Testing ``encode`` and ``decode``.
876
 
        >>> m = ConfigObj(a)
877
 
        >>> m.decode('ascii')
878
 
        >>> def testuni(val):
879
 
        ...     for entry in val:
880
 
        ...         if not isinstance(entry, unicode):
881
 
        ...             print >> sys.stderr, type(entry)
882
 
        ...             raise AssertionError, 'decode failed.'
883
 
        ...         if isinstance(val[entry], dict):
884
 
        ...             testuni(val[entry])
885
 
        ...         elif not isinstance(val[entry], unicode):
886
 
        ...             raise AssertionError, 'decode failed.'
887
 
        >>> testuni(m)
888
 
        >>> m.encode('ascii')
889
 
        >>> a == m
890
 
        1
891
 
        """
892
 
        warn('use of ``decode`` is deprecated.', DeprecationWarning)
893
 
        def decode(section, key, encoding=encoding, warn=True):
894
 
            """ """
895
 
            val = section[key]
896
 
            if isinstance(val, (list, tuple)):
897
 
                newval = []
898
 
                for entry in val:
899
 
                    newval.append(entry.decode(encoding))
900
 
            elif isinstance(val, dict):
901
 
                newval = val
902
 
            else:
903
 
                newval = val.decode(encoding)
904
 
            newkey = key.decode(encoding)
905
 
            section.rename(key, newkey)
906
 
            section[newkey] = newval
907
 
        # using ``call_on_sections`` allows us to modify section names
908
 
        self.walk(decode, call_on_sections=True)
909
 
 
910
 
    def encode(self, encoding):
911
 
        """
912
 
        Encode all strings and values from unicode,
913
 
        using the specified encoding.
914
 
        
915
 
        Works with subsections and list values.
916
 
        Uses the ``walk`` method.
917
 
        """
918
 
        warn('use of ``encode`` is deprecated.', DeprecationWarning)
919
 
        def encode(section, key, encoding=encoding):
920
 
            """ """
921
 
            val = section[key]
922
 
            if isinstance(val, (list, tuple)):
923
 
                newval = []
924
 
                for entry in val:
925
 
                    newval.append(entry.encode(encoding))
926
 
            elif isinstance(val, dict):
927
 
                newval = val
928
 
            else:
929
 
                newval = val.encode(encoding)
930
 
            newkey = key.encode(encoding)
931
 
            section.rename(key, newkey)
932
 
            section[newkey] = newval
933
 
        self.walk(encode, call_on_sections=True)
934
 
 
935
 
    def istrue(self, key):
936
 
        """A deprecated version of ``as_bool``."""
937
 
        warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
938
 
                'instead.', DeprecationWarning)
939
 
        return self.as_bool(key)
940
 
 
941
 
    def as_bool(self, key):
942
 
        """
943
 
        Accepts a key as input. The corresponding value must be a string or
944
 
        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
945
 
        retain compatibility with Python 2.2.
946
 
        
947
 
        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
948
 
        ``True``.
949
 
        
950
 
        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
951
 
        ``False``.
952
 
        
953
 
        ``as_bool`` is not case sensitive.
954
 
        
955
 
        Any other input will raise a ``ValueError``.
956
 
        
957
 
        >>> a = ConfigObj()
958
 
        >>> a['a'] = 'fish'
959
 
        >>> a.as_bool('a')
960
 
        Traceback (most recent call last):
961
 
        ValueError: Value "fish" is neither True nor False
962
 
        >>> a['b'] = 'True'
963
 
        >>> a.as_bool('b')
964
 
        1
965
 
        >>> a['b'] = 'off'
966
 
        >>> a.as_bool('b')
967
 
        0
968
 
        """
969
 
        val = self[key]
970
 
        if val == True:
971
 
            return True
972
 
        elif val == False:
973
 
            return False
974
 
        else:
975
 
            try:
976
 
                if not isinstance(val, StringTypes):
977
 
                    raise KeyError
978
 
                else:
979
 
                    return self.main._bools[val.lower()]
980
 
            except KeyError:
981
 
                raise ValueError('Value "%s" is neither True nor False' % val)
982
 
 
983
 
    def as_int(self, key):
984
 
        """
985
 
        A convenience method which coerces the specified value to an integer.
986
 
        
987
 
        If the value is an invalid literal for ``int``, a ``ValueError`` will
988
 
        be raised.
989
 
        
990
 
        >>> a = ConfigObj()
991
 
        >>> a['a'] = 'fish'
992
 
        >>> a.as_int('a')
993
 
        Traceback (most recent call last):
994
 
        ValueError: invalid literal for int(): fish
995
 
        >>> a['b'] = '1'
996
 
        >>> a.as_int('b')
997
 
        1
998
 
        >>> a['b'] = '3.2'
999
 
        >>> a.as_int('b')
1000
 
        Traceback (most recent call last):
1001
 
        ValueError: invalid literal for int(): 3.2
1002
 
        """
1003
 
        return int(self[key])
1004
 
 
1005
 
    def as_float(self, key):
1006
 
        """
1007
 
        A convenience method which coerces the specified value to a float.
1008
 
        
1009
 
        If the value is an invalid literal for ``float``, a ``ValueError`` will
1010
 
        be raised.
1011
 
        
1012
 
        >>> a = ConfigObj()
1013
 
        >>> a['a'] = 'fish'
1014
 
        >>> a.as_float('a')
1015
 
        Traceback (most recent call last):
1016
 
        ValueError: invalid literal for float(): fish
1017
 
        >>> a['b'] = '1'
1018
 
        >>> a.as_float('b')
1019
 
        1.0
1020
 
        >>> a['b'] = '3.2'
1021
 
        >>> a.as_float('b')
1022
 
        3.2000000000000002
1023
 
        """
1024
 
        return float(self[key])
1025
 
    
1026
 
 
1027
 
class ConfigObj(Section):
1028
 
    """An object to read, create, and write config files."""
1029
 
 
1030
 
    _keyword = re.compile(r'''^ # line start
1031
 
        (\s*)                   # indentation
1032
 
        (                       # keyword
1033
 
            (?:".*?")|          # double quotes
1034
 
            (?:'.*?')|          # single quotes
1035
 
            (?:[^'"=].*?)       # no quotes
1036
 
        )
1037
 
        \s*=\s*                 # divider
1038
 
        (.*)                    # value (including list values and comments)
1039
 
        $   # line end
1040
 
        ''',
1041
 
        re.VERBOSE)
1042
 
 
1043
 
    _sectionmarker = re.compile(r'''^
1044
 
        (\s*)                     # 1: indentation
1045
 
        ((?:\[\s*)+)              # 2: section marker open
1046
 
        (                         # 3: section name open
1047
 
            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
1048
 
            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
1049
 
            (?:[^'"\s].*?)        # at least one non-space unquoted
1050
 
        )                         # section name close
1051
 
        ((?:\s*\])+)              # 4: section marker close
1052
 
        \s*(\#.*)?                # 5: optional comment
1053
 
        $''',
1054
 
        re.VERBOSE)
1055
 
 
1056
 
    # this regexp pulls list values out as a single string
1057
 
    # or single values and comments
1058
 
    # FIXME: this regex adds a '' to the end of comma terminated lists
1059
 
    #   workaround in ``_handle_value``
1060
 
    _valueexp = re.compile(r'''^
1061
 
        (?:
1062
 
            (?:
1063
 
                (
1064
 
                    (?:
1065
 
                        (?:
1066
 
                            (?:".*?")|              # double quotes
1067
 
                            (?:'.*?')|              # single quotes
1068
 
                            (?:[^'",\#][^,\#]*?)    # unquoted
1069
 
                        )
1070
 
                        \s*,\s*                     # comma
1071
 
                    )*      # match all list items ending in a comma (if any)
1072
 
                )
1073
 
                (
1074
 
                    (?:".*?")|                      # double quotes
1075
 
                    (?:'.*?')|                      # single quotes
1076
 
                    (?:[^'",\#\s][^,]*?)|           # unquoted
1077
 
                    (?:(?<!,))                      # Empty value
1078
 
                )?          # last item in a list - or string value
1079
 
            )|
1080
 
            (,)             # alternatively a single comma - empty list
1081
 
        )
1082
 
        \s*(\#.*)?          # optional comment
1083
 
        $''',
1084
 
        re.VERBOSE)
1085
 
 
1086
 
    # use findall to get the members of a list value
1087
 
    _listvalueexp = re.compile(r'''
1088
 
        (
1089
 
            (?:".*?")|          # double quotes
1090
 
            (?:'.*?')|          # single quotes
1091
 
            (?:[^'",\#].*?)       # unquoted
1092
 
        )
1093
 
        \s*,\s*                 # comma
1094
 
        ''',
1095
 
        re.VERBOSE)
1096
 
 
1097
 
    # this regexp is used for the value
1098
 
    # when lists are switched off
1099
 
    _nolistvalue = re.compile(r'''^
1100
 
        (
1101
 
            (?:".*?")|          # double quotes
1102
 
            (?:'.*?')|          # single quotes
1103
 
            (?:[^'"\#].*?)|     # unquoted
1104
 
            (?:)                # Empty value
1105
 
        )
1106
 
        \s*(\#.*)?              # optional comment
1107
 
        $''',
1108
 
        re.VERBOSE)
1109
 
 
1110
 
    # regexes for finding triple quoted values on one line
1111
 
    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1112
 
    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1113
 
    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1114
 
    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1115
 
 
1116
 
    _triple_quote = {
1117
 
        "'''": (_single_line_single, _multi_line_single),
1118
 
        '"""': (_single_line_double, _multi_line_double),
1119
 
    }
1120
 
 
1121
 
    # Used by the ``istrue`` Section method
1122
 
    _bools = {
1123
 
        'yes': True, 'no': False,
1124
 
        'on': True, 'off': False,
1125
 
        '1': True, '0': False,
1126
 
        'true': True, 'false': False,
1127
 
        }
1128
 
 
1129
 
    def __init__(self, infile=None, options=None, **kwargs):
1130
 
        """
1131
 
        Parse or create a config file object.
1132
 
        
1133
 
        ``ConfigObj(infile=None, options=None, **kwargs)``
1134
 
        """
1135
 
        if infile is None:
1136
 
            infile = []
1137
 
        if options is None:
1138
 
            options = {}
1139
 
        else:
1140
 
            options = dict(options)
1141
 
        # keyword arguments take precedence over an options dictionary
1142
 
        options.update(kwargs)
1143
 
        # init the superclass
1144
 
        Section.__init__(self, self, 0, self)
1145
 
        #
1146
 
        defaults = OPTION_DEFAULTS.copy()
1147
 
        for entry in options.keys():
1148
 
            if entry not in defaults.keys():
1149
 
                raise TypeError, 'Unrecognised option "%s".' % entry
1150
 
        # TODO: check the values too.
1151
 
        #
1152
 
        # Add any explicit options to the defaults
1153
 
        defaults.update(options)
1154
 
        #
1155
 
        # initialise a few variables
1156
 
        self.filename = None
1157
 
        self._errors = []
1158
 
        self.raise_errors = defaults['raise_errors']
1159
 
        self.interpolation = defaults['interpolation']
1160
 
        self.list_values = defaults['list_values']
1161
 
        self.create_empty = defaults['create_empty']
1162
 
        self.file_error = defaults['file_error']
1163
 
        self.stringify = defaults['stringify']
1164
 
        self.indent_type = defaults['indent_type']
1165
 
        self.encoding = defaults['encoding']
1166
 
        self.default_encoding = defaults['default_encoding']
1167
 
        self.BOM = False
1168
 
        self.newlines = None
1169
 
        self.write_empty_values = defaults['write_empty_values']
1170
 
        self.unrepr = defaults['unrepr']
1171
 
        #
1172
 
        self.initial_comment = []
1173
 
        self.final_comment = []
1174
 
        #
1175
 
        self._terminated = False
1176
 
        #
1177
 
        if isinstance(infile, StringTypes):
1178
 
            self.filename = infile
1179
 
            if os.path.isfile(infile):
1180
 
                infile = open(infile).read() or []
1181
 
            elif self.file_error:
1182
 
                # raise an error if the file doesn't exist
1183
 
                raise IOError, 'Config file not found: "%s".' % self.filename
1184
 
            else:
1185
 
                # file doesn't already exist
1186
 
                if self.create_empty:
1187
 
                    # this is a good test that the filename specified
1188
 
                    # isn't impossible - like on a non existent device
1189
 
                    h = open(infile, 'w')
1190
 
                    h.write('')
1191
 
                    h.close()
1192
 
                infile = []
1193
 
        elif isinstance(infile, (list, tuple)):
1194
 
            infile = list(infile)
1195
 
        elif isinstance(infile, dict):
1196
 
            # initialise self
1197
 
            # the Section class handles creating subsections
1198
 
            if isinstance(infile, ConfigObj):
1199
 
                # get a copy of our ConfigObj
1200
 
                infile = infile.dict()
1201
 
            for entry in infile:
1202
 
                self[entry] = infile[entry]
1203
 
            del self._errors
1204
 
            if defaults['configspec'] is not None:
1205
 
                self._handle_configspec(defaults['configspec'])
1206
 
            else:
1207
 
                self.configspec = None
1208
 
            return
1209
 
        elif hasattr(infile, 'read'):
1210
 
            # This supports file like objects
1211
 
            infile = infile.read() or []
1212
 
            # needs splitting into lines - but needs doing *after* decoding
1213
 
            # in case it's not an 8 bit encoding
1214
 
        else:
1215
 
            raise TypeError, ('infile must be a filename,'
1216
 
                ' file like object, or list of lines.')
1217
 
        #
1218
 
        if infile:
1219
 
            # don't do it for the empty ConfigObj
1220
 
            infile = self._handle_bom(infile)
1221
 
            # infile is now *always* a list
1222
 
            #
1223
 
            # Set the newlines attribute (first line ending it finds)
1224
 
            # and strip trailing '\n' or '\r' from lines
1225
 
            for line in infile:
1226
 
                if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1227
 
                    continue
1228
 
                for end in ('\r\n', '\n', '\r'):
1229
 
                    if line.endswith(end):
1230
 
                        self.newlines = end
1231
 
                        break
1232
 
                break
1233
 
            if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'):
1234
 
                self._terminated = True
1235
 
            infile = [line.rstrip('\r\n') for line in infile]
1236
 
        #
1237
 
        self._parse(infile)
1238
 
        # if we had any errors, now is the time to raise them
1239
 
        if self._errors:
1240
 
            info = "at line %s." % self._errors[0].line_number
1241
 
            if len(self._errors) > 1:
1242
 
                msg = ("Parsing failed with several errors.\nFirst error %s" %
1243
 
                    info)
1244
 
                error = ConfigObjError(msg)
1245
 
            else:
1246
 
                error = self._errors[0]
1247
 
            # set the errors attribute; it's a list of tuples:
1248
 
            # (error_type, message, line_number)
1249
 
            error.errors = self._errors
1250
 
            # set the config attribute
1251
 
            error.config = self
1252
 
            raise error
1253
 
        # delete private attributes
1254
 
        del self._errors
1255
 
        #
1256
 
        if defaults['configspec'] is None:
1257
 
            self.configspec = None
1258
 
        else:
1259
 
            self._handle_configspec(defaults['configspec'])
1260
 
    
1261
 
    def __repr__(self):
1262
 
        return 'ConfigObj({%s})' % ', '.join(
1263
 
            [('%s: %s' % (repr(key), repr(self[key]))) for key in
1264
 
            (self.scalars + self.sections)])
1265
 
    
1266
 
    def _handle_bom(self, infile):
1267
 
        """
1268
 
        Handle any BOM, and decode if necessary.
1269
 
        
1270
 
        If an encoding is specified, that *must* be used - but the BOM should
1271
 
        still be removed (and the BOM attribute set).
1272
 
        
1273
 
        (If the encoding is wrongly specified, then a BOM for an alternative
1274
 
        encoding won't be discovered or removed.)
1275
 
        
1276
 
        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1277
 
        removed. The BOM attribute will be set. UTF16 will be decoded to
1278
 
        unicode.
1279
 
        
1280
 
        NOTE: This method must not be called with an empty ``infile``.
1281
 
        
1282
 
        Specifying the *wrong* encoding is likely to cause a
1283
 
        ``UnicodeDecodeError``.
1284
 
        
1285
 
        ``infile`` must always be returned as a list of lines, but may be
1286
 
        passed in as a single string.
1287
 
        """
1288
 
        if ((self.encoding is not None) and
1289
 
            (self.encoding.lower() not in BOM_LIST)):
1290
 
            # No need to check for a BOM
1291
 
            # the encoding specified doesn't have one
1292
 
            # just decode
1293
 
            return self._decode(infile, self.encoding)
1294
 
        #
1295
 
        if isinstance(infile, (list, tuple)):
1296
 
            line = infile[0]
1297
 
        else:
1298
 
            line = infile
1299
 
        if self.encoding is not None:
1300
 
            # encoding explicitly supplied
1301
 
            # And it could have an associated BOM
1302
 
            # TODO: if encoding is just UTF16 - we ought to check for both
1303
 
            # TODO: big endian and little endian versions.
1304
 
            enc = BOM_LIST[self.encoding.lower()]
1305
 
            if enc == 'utf_16':
1306
 
                # For UTF16 we try big endian and little endian
1307
 
                for BOM, (encoding, final_encoding) in BOMS.items():
1308
 
                    if not final_encoding:
1309
 
                        # skip UTF8
1310
 
                        continue
1311
 
                    if infile.startswith(BOM):
1312
 
                        ### BOM discovered
1313
 
                        ##self.BOM = True
1314
 
                        # Don't need to remove BOM
1315
 
                        return self._decode(infile, encoding)
1316
 
                #
1317
 
                # If we get this far, will *probably* raise a DecodeError
1318
 
                # As it doesn't appear to start with a BOM
1319
 
                return self._decode(infile, self.encoding)
1320
 
            #
1321
 
            # Must be UTF8
1322
 
            BOM = BOM_SET[enc]
1323
 
            if not line.startswith(BOM):
1324
 
                return self._decode(infile, self.encoding)
1325
 
            #
1326
 
            newline = line[len(BOM):]
1327
 
            #
1328
 
            # BOM removed
1329
 
            if isinstance(infile, (list, tuple)):
1330
 
                infile[0] = newline
1331
 
            else:
1332
 
                infile = newline
1333
 
            self.BOM = True
1334
 
            return self._decode(infile, self.encoding)
1335
 
        #
1336
 
        # No encoding specified - so we need to check for UTF8/UTF16
1337
 
        for BOM, (encoding, final_encoding) in BOMS.items():
1338
 
            if not line.startswith(BOM):
1339
 
                continue
1340
 
            else:
1341
 
                # BOM discovered
1342
 
                self.encoding = final_encoding
1343
 
                if not final_encoding:
1344
 
                    self.BOM = True
1345
 
                    # UTF8
1346
 
                    # remove BOM
1347
 
                    newline = line[len(BOM):]
1348
 
                    if isinstance(infile, (list, tuple)):
1349
 
                        infile[0] = newline
1350
 
                    else:
1351
 
                        infile = newline
1352
 
                    # UTF8 - don't decode
1353
 
                    if isinstance(infile, StringTypes):
1354
 
                        return infile.splitlines(True)
1355
 
                    else:
1356
 
                        return infile
1357
 
                # UTF16 - have to decode
1358
 
                return self._decode(infile, encoding)
1359
 
        #
1360
 
        # No BOM discovered and no encoding specified, just return
1361
 
        if isinstance(infile, StringTypes):
1362
 
            # infile read from a file will be a single string
1363
 
            return infile.splitlines(True)
1364
 
        else:
1365
 
            return infile
1366
 
 
1367
 
    def _a_to_u(self, aString):
1368
 
        """Decode ASCII strings to unicode if a self.encoding is specified."""
1369
 
        if self.encoding:
1370
 
            return aString.decode('ascii')
1371
 
        else:
1372
 
            return aString
1373
 
 
1374
 
    def _decode(self, infile, encoding):
1375
 
        """
1376
 
        Decode infile to unicode. Using the specified encoding.
1377
 
        
1378
 
        if is a string, it also needs converting to a list.
1379
 
        """
1380
 
        if isinstance(infile, StringTypes):
1381
 
            # can't be unicode
1382
 
            # NOTE: Could raise a ``UnicodeDecodeError``
1383
 
            return infile.decode(encoding).splitlines(True)
1384
 
        for i, line in enumerate(infile):
1385
 
            if not isinstance(line, unicode):
1386
 
                # NOTE: The isinstance test here handles mixed lists of unicode/string
1387
 
                # NOTE: But the decode will break on any non-string values
1388
 
                # NOTE: Or could raise a ``UnicodeDecodeError``
1389
 
                infile[i] = line.decode(encoding)
1390
 
        return infile
1391
 
 
1392
 
    def _decode_element(self, line):
1393
 
        """Decode element to unicode if necessary."""
1394
 
        if not self.encoding:
1395
 
            return line
1396
 
        if isinstance(line, str) and self.default_encoding:
1397
 
            return line.decode(self.default_encoding)
1398
 
        return line
1399
 
 
1400
 
    def _str(self, value):
1401
 
        """
1402
 
        Used by ``stringify`` within validate, to turn non-string values
1403
 
        into strings.
1404
 
        """
1405
 
        if not isinstance(value, StringTypes):
1406
 
            return str(value)
1407
 
        else:
1408
 
            return value
1409
 
 
1410
 
    def _parse(self, infile):
1411
 
        """Actually parse the config file."""
1412
 
        temp_list_values = self.list_values
1413
 
        if self.unrepr:
1414
 
            self.list_values = False
1415
 
        comment_list = []
1416
 
        done_start = False
1417
 
        this_section = self
1418
 
        maxline = len(infile) - 1
1419
 
        cur_index = -1
1420
 
        reset_comment = False
1421
 
        while cur_index < maxline:
1422
 
            if reset_comment:
1423
 
                comment_list = []
1424
 
            cur_index += 1
1425
 
            line = infile[cur_index]
1426
 
            sline = line.strip()
1427
 
            # do we have anything on the line ?
1428
 
            if not sline or sline.startswith('#') or sline.startswith(';'):
1429
 
                reset_comment = False
1430
 
                comment_list.append(line)
1431
 
                continue
1432
 
            if not done_start:
1433
 
                # preserve initial comment
1434
 
                self.initial_comment = comment_list
1435
 
                comment_list = []
1436
 
                done_start = True
1437
 
            reset_comment = True
1438
 
            # first we check if it's a section marker
1439
 
            mat = self._sectionmarker.match(line)
1440
 
            if mat is not None:
1441
 
                # is a section line
1442
 
                (indent, sect_open, sect_name, sect_close, comment) = (
1443
 
                    mat.groups())
1444
 
                if indent and (self.indent_type is None):
1445
 
                    self.indent_type = indent
1446
 
                cur_depth = sect_open.count('[')
1447
 
                if cur_depth != sect_close.count(']'):
1448
 
                    self._handle_error(
1449
 
                        "Cannot compute the section depth at line %s.",
1450
 
                        NestingError, infile, cur_index)
1451
 
                    continue
1452
 
                #
1453
 
                if cur_depth < this_section.depth:
1454
 
                    # the new section is dropping back to a previous level
1455
 
                    try:
1456
 
                        parent = self._match_depth(
1457
 
                            this_section,
1458
 
                            cur_depth).parent
1459
 
                    except SyntaxError:
1460
 
                        self._handle_error(
1461
 
                            "Cannot compute nesting level at line %s.",
1462
 
                            NestingError, infile, cur_index)
1463
 
                        continue
1464
 
                elif cur_depth == this_section.depth:
1465
 
                    # the new section is a sibling of the current section
1466
 
                    parent = this_section.parent
1467
 
                elif cur_depth == this_section.depth + 1:
1468
 
                    # the new section is a child the current section
1469
 
                    parent = this_section
1470
 
                else:
1471
 
                    self._handle_error(
1472
 
                        "Section too nested at line %s.",
1473
 
                        NestingError, infile, cur_index)
1474
 
                #
1475
 
                sect_name = self._unquote(sect_name)
1476
 
                if parent.has_key(sect_name):
1477
 
                    self._handle_error(
1478
 
                        'Duplicate section name at line %s.',
1479
 
                        DuplicateError, infile, cur_index)
1480
 
                    continue
1481
 
                # create the new section
1482
 
                this_section = Section(
1483
 
                    parent,
1484
 
                    cur_depth,
1485
 
                    self,
1486
 
                    name=sect_name)
1487
 
                parent[sect_name] = this_section
1488
 
                parent.inline_comments[sect_name] = comment
1489
 
                parent.comments[sect_name] = comment_list
1490
 
                continue
1491
 
            #
1492
 
            # it's not a section marker,
1493
 
            # so it should be a valid ``key = value`` line
1494
 
            mat = self._keyword.match(line)
1495
 
            if mat is None:
1496
 
                # it neither matched as a keyword
1497
 
                # or a section marker
1498
 
                self._handle_error(
1499
 
                    'Invalid line at line "%s".',
1500
 
                    ParseError, infile, cur_index)
1501
 
            else:
1502
 
                # is a keyword value
1503
 
                # value will include any inline comment
1504
 
                (indent, key, value) = mat.groups()
1505
 
                if indent and (self.indent_type is None):
1506
 
                    self.indent_type = indent
1507
 
                # check for a multiline value
1508
 
                if value[:3] in ['"""', "'''"]:
1509
 
                    try:
1510
 
                        (value, comment, cur_index) = self._multiline(
1511
 
                            value, infile, cur_index, maxline)
1512
 
                    except SyntaxError:
1513
 
                        self._handle_error(
1514
 
                            'Parse error in value at line %s.',
1515
 
                            ParseError, infile, cur_index)
1516
 
                        continue
1517
 
                    else:
1518
 
                        if self.unrepr:
1519
 
                            comment = ''
1520
 
                            try:
1521
 
                                value = unrepr(value)
1522
 
                            except Exception, e:
1523
 
                                if type(e) == UnknownType:
1524
 
                                    msg = 'Unknown name or type in value at line %s.'
1525
 
                                else:
1526
 
                                    msg = 'Parse error in value at line %s.'
1527
 
                                self._handle_error(msg, UnreprError, infile,
1528
 
                                    cur_index)
1529
 
                                continue
1530
 
                else:
1531
 
                    if self.unrepr:
1532
 
                        comment = ''
1533
 
                        try:
1534
 
                            value = unrepr(value)
1535
 
                        except Exception, e:
1536
 
                            if isinstance(e, UnknownType):
1537
 
                                msg = 'Unknown name or type in value at line %s.'
1538
 
                            else:
1539
 
                                msg = 'Parse error in value at line %s.'
1540
 
                            self._handle_error(msg, UnreprError, infile,
1541
 
                                cur_index)
1542
 
                            continue
1543
 
                    else:
1544
 
                        # extract comment and lists
1545
 
                        try:
1546
 
                            (value, comment) = self._handle_value(value)
1547
 
                        except SyntaxError:
1548
 
                            self._handle_error(
1549
 
                                'Parse error in value at line %s.',
1550
 
                                ParseError, infile, cur_index)
1551
 
                            continue
1552
 
                #
1553
 
                key = self._unquote(key)
1554
 
                if this_section.has_key(key):
1555
 
                    self._handle_error(
1556
 
                        'Duplicate keyword name at line %s.',
1557
 
                        DuplicateError, infile, cur_index)
1558
 
                    continue
1559
 
                # add the key.
1560
 
                # we set unrepr because if we have got this far we will never
1561
 
                # be creating a new section
1562
 
                this_section.__setitem__(key, value, unrepr=True)
1563
 
                this_section.inline_comments[key] = comment
1564
 
                this_section.comments[key] = comment_list
1565
 
                continue
1566
 
        #
1567
 
        if self.indent_type is None:
1568
 
            # no indentation used, set the type accordingly
1569
 
            self.indent_type = ''
1570
 
        #
1571
 
        if self._terminated:
1572
 
            comment_list.append('')
1573
 
        # preserve the final comment
1574
 
        if not self and not self.initial_comment:
1575
 
            self.initial_comment = comment_list
1576
 
        elif not reset_comment:
1577
 
            self.final_comment = comment_list
1578
 
        self.list_values = temp_list_values
1579
 
 
1580
 
    def _match_depth(self, sect, depth):
1581
 
        """
1582
 
        Given a section and a depth level, walk back through the sections
1583
 
        parents to see if the depth level matches a previous section.
1584
 
        
1585
 
        Return a reference to the right section,
1586
 
        or raise a SyntaxError.
1587
 
        """
1588
 
        while depth < sect.depth:
1589
 
            if sect is sect.parent:
1590
 
                # we've reached the top level already
1591
 
                raise SyntaxError
1592
 
            sect = sect.parent
1593
 
        if sect.depth == depth:
1594
 
            return sect
1595
 
        # shouldn't get here
1596
 
        raise SyntaxError
1597
 
 
1598
 
    def _handle_error(self, text, ErrorClass, infile, cur_index):
1599
 
        """
1600
 
        Handle an error according to the error settings.
1601
 
        
1602
 
        Either raise the error or store it.
1603
 
        The error will have occurred at ``cur_index``
1604
 
        """
1605
 
        line = infile[cur_index]
1606
 
        cur_index += 1
1607
 
        message = text % cur_index
1608
 
        error = ErrorClass(message, cur_index, line)
1609
 
        if self.raise_errors:
1610
 
            # raise the error - parsing stops here
1611
 
            raise error
1612
 
        # store the error
1613
 
        # reraise when parsing has finished
1614
 
        self._errors.append(error)
1615
 
 
1616
 
    def _unquote(self, value):
1617
 
        """Return an unquoted version of a value"""
1618
 
        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1619
 
            value = value[1:-1]
1620
 
        return value
1621
 
 
1622
 
    def _quote(self, value, multiline=True):
1623
 
        """
1624
 
        Return a safely quoted version of a value.
1625
 
        
1626
 
        Raise a ConfigObjError if the value cannot be safely quoted.
1627
 
        If multiline is ``True`` (default) then use triple quotes
1628
 
        if necessary.
1629
 
        
1630
 
        Don't quote values that don't need it.
1631
 
        Recursively quote members of a list and return a comma joined list.
1632
 
        Multiline is ``False`` for lists.
1633
 
        Obey list syntax for empty and single member lists.
1634
 
        
1635
 
        If ``list_values=False`` then the value is only quoted if it contains
1636
 
        a ``\n`` (is multiline).
1637
 
        
1638
 
        If ``write_empty_values`` is set, and the value is an empty string, it
1639
 
        won't be quoted.
1640
 
        """
1641
 
        if multiline and self.write_empty_values and value == '':
1642
 
            # Only if multiline is set, so that it is used for values not
1643
 
            # keys, and not values that are part of a list
1644
 
            return ''
1645
 
        if multiline and isinstance(value, (list, tuple)):
1646
 
            if not value:
1647
 
                return ','
1648
 
            elif len(value) == 1:
1649
 
                return self._quote(value[0], multiline=False) + ','
1650
 
            return ', '.join([self._quote(val, multiline=False)
1651
 
                for val in value])
1652
 
        if not isinstance(value, StringTypes):
1653
 
            if self.stringify:
1654
 
                value = str(value)
1655
 
            else:
1656
 
                raise TypeError, 'Value "%s" is not a string.' % value
1657
 
        squot = "'%s'"
1658
 
        dquot = '"%s"'
1659
 
        noquot = "%s"
1660
 
        wspace_plus = ' \r\t\n\v\t\'"'
1661
 
        tsquot = '"""%s"""'
1662
 
        tdquot = "'''%s'''"
1663
 
        if not value:
1664
 
            return '""'
1665
 
        if (not self.list_values and '\n' not in value) or not (multiline and
1666
 
                ((("'" in value) and ('"' in value)) or ('\n' in value))):
1667
 
            if not self.list_values:
1668
 
                # we don't quote if ``list_values=False``
1669
 
                quot = noquot
1670
 
            # for normal values either single or double quotes will do
1671
 
            elif '\n' in value:
1672
 
                # will only happen if multiline is off - e.g. '\n' in key
1673
 
                raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1674
 
                    value)
1675
 
            elif ((value[0] not in wspace_plus) and
1676
 
                    (value[-1] not in wspace_plus) and
1677
 
                    (',' not in value)):
1678
 
                quot = noquot
1679
 
            else:
1680
 
                if ("'" in value) and ('"' in value):
1681
 
                    raise ConfigObjError, (
1682
 
                        'Value "%s" cannot be safely quoted.' % value)
1683
 
                elif '"' in value:
1684
 
                    quot = squot
1685
 
                else:
1686
 
                    quot = dquot
1687
 
        else:
1688
 
            # if value has '\n' or "'" *and* '"', it will need triple quotes
1689
 
            if (value.find('"""') != -1) and (value.find("'''") != -1):
1690
 
                raise ConfigObjError, (
1691
 
                    'Value "%s" cannot be safely quoted.' % value)
1692
 
            if value.find('"""') == -1:
1693
 
                quot = tdquot
1694
 
            else:
1695
 
                quot = tsquot
1696
 
        return quot % value
1697
 
 
1698
 
    def _handle_value(self, value):
1699
 
        """
1700
 
        Given a value string, unquote, remove comment,
1701
 
        handle lists. (including empty and single member lists)
1702
 
        """
1703
 
        # do we look for lists in values ?
1704
 
        if not self.list_values:
1705
 
            mat = self._nolistvalue.match(value)
1706
 
            if mat is None:
1707
 
                raise SyntaxError
1708
 
            # NOTE: we don't unquote here
1709
 
            return mat.groups()
1710
 
        #
1711
 
        mat = self._valueexp.match(value)
1712
 
        if mat is None:
1713
 
            # the value is badly constructed, probably badly quoted,
1714
 
            # or an invalid list
1715
 
            raise SyntaxError
1716
 
        (list_values, single, empty_list, comment) = mat.groups()
1717
 
        if (list_values == '') and (single is None):
1718
 
            # change this if you want to accept empty values
1719
 
            raise SyntaxError
1720
 
        # NOTE: note there is no error handling from here if the regex
1721
 
        # is wrong: then incorrect values will slip through
1722
 
        if empty_list is not None:
1723
 
            # the single comma - meaning an empty list
1724
 
            return ([], comment)
1725
 
        if single is not None:
1726
 
            # handle empty values
1727
 
            if list_values and not single:
1728
 
                # FIXME: the '' is a workaround because our regex now matches
1729
 
                #   '' at the end of a list if it has a trailing comma
1730
 
                single = None
1731
 
            else:
1732
 
                single = single or '""'
1733
 
                single = self._unquote(single)
1734
 
        if list_values == '':
1735
 
            # not a list value
1736
 
            return (single, comment)
1737
 
        the_list = self._listvalueexp.findall(list_values)
1738
 
        the_list = [self._unquote(val) for val in the_list]
1739
 
        if single is not None:
1740
 
            the_list += [single]
1741
 
        return (the_list, comment)
1742
 
 
1743
 
    def _multiline(self, value, infile, cur_index, maxline):
1744
 
        """Extract the value, where we are in a multiline situation."""
1745
 
        quot = value[:3]
1746
 
        newvalue = value[3:]
1747
 
        single_line = self._triple_quote[quot][0]
1748
 
        multi_line = self._triple_quote[quot][1]
1749
 
        mat = single_line.match(value)
1750
 
        if mat is not None:
1751
 
            retval = list(mat.groups())
1752
 
            retval.append(cur_index)
1753
 
            return retval
1754
 
        elif newvalue.find(quot) != -1:
1755
 
            # somehow the triple quote is missing
1756
 
            raise SyntaxError
1757
 
        #
1758
 
        while cur_index < maxline:
1759
 
            cur_index += 1
1760
 
            newvalue += '\n'
1761
 
            line = infile[cur_index]
1762
 
            if line.find(quot) == -1:
1763
 
                newvalue += line
1764
 
            else:
1765
 
                # end of multiline, process it
1766
 
                break
1767
 
        else:
1768
 
            # we've got to the end of the config, oops...
1769
 
            raise SyntaxError
1770
 
        mat = multi_line.match(line)
1771
 
        if mat is None:
1772
 
            # a badly formed line
1773
 
            raise SyntaxError
1774
 
        (value, comment) = mat.groups()
1775
 
        return (newvalue + value, comment, cur_index)
1776
 
 
1777
 
    def _handle_configspec(self, configspec):
1778
 
        """Parse the configspec."""
1779
 
        # FIXME: Should we check that the configspec was created with the 
1780
 
        #   correct settings ? (i.e. ``list_values=False``)
1781
 
        if not isinstance(configspec, ConfigObj):
1782
 
            try:
1783
 
                configspec = ConfigObj(
1784
 
                    configspec,
1785
 
                    raise_errors=True,
1786
 
                    file_error=True,
1787
 
                    list_values=False)
1788
 
            except ConfigObjError, e:
1789
 
                # FIXME: Should these errors have a reference
1790
 
                # to the already parsed ConfigObj ?
1791
 
                raise ConfigspecError('Parsing configspec failed: %s' % e)
1792
 
            except IOError, e:
1793
 
                raise IOError('Reading configspec failed: %s' % e)
1794
 
        self._set_configspec_value(configspec, self)
1795
 
 
1796
 
    def _set_configspec_value(self, configspec, section):
1797
 
        """Used to recursively set configspec values."""
1798
 
        if '__many__' in configspec.sections:
1799
 
            section.configspec['__many__'] = configspec['__many__']
1800
 
            if len(configspec.sections) > 1:
1801
 
                # FIXME: can we supply any useful information here ?
1802
 
                raise RepeatSectionError
1803
 
        if hasattr(configspec, 'initial_comment'):
1804
 
            section._configspec_initial_comment = configspec.initial_comment
1805
 
            section._configspec_final_comment = configspec.final_comment
1806
 
            section._configspec_encoding = configspec.encoding
1807
 
            section._configspec_BOM = configspec.BOM
1808
 
            section._configspec_newlines = configspec.newlines
1809
 
            section._configspec_indent_type = configspec.indent_type
1810
 
        for entry in configspec.scalars:
1811
 
            section._configspec_comments[entry] = configspec.comments[entry]
1812
 
            section._configspec_inline_comments[entry] = (
1813
 
                configspec.inline_comments[entry])
1814
 
            section.configspec[entry] = configspec[entry]
1815
 
            section._order.append(entry)
1816
 
        for entry in configspec.sections:
1817
 
            if entry == '__many__':
1818
 
                continue
1819
 
            section._cs_section_comments[entry] = configspec.comments[entry]
1820
 
            section._cs_section_inline_comments[entry] = (
1821
 
                configspec.inline_comments[entry])
1822
 
            if not section.has_key(entry):
1823
 
                section[entry] = {}
1824
 
            self._set_configspec_value(configspec[entry], section[entry])
1825
 
 
1826
 
    def _handle_repeat(self, section, configspec):
1827
 
        """Dynamically assign configspec for repeated section."""
1828
 
        try:
1829
 
            section_keys = configspec.sections
1830
 
            scalar_keys = configspec.scalars
1831
 
        except AttributeError:
1832
 
            section_keys = [entry for entry in configspec 
1833
 
                                if isinstance(configspec[entry], dict)]
1834
 
            scalar_keys = [entry for entry in configspec 
1835
 
                                if not isinstance(configspec[entry], dict)]
1836
 
        if '__many__' in section_keys and len(section_keys) > 1:
1837
 
            # FIXME: can we supply any useful information here ?
1838
 
            raise RepeatSectionError
1839
 
        scalars = {}
1840
 
        sections = {}
1841
 
        for entry in scalar_keys:
1842
 
            val = configspec[entry]
1843
 
            scalars[entry] = val
1844
 
        for entry in section_keys:
1845
 
            val = configspec[entry]
1846
 
            if entry == '__many__':
1847
 
                scalars[entry] = val
1848
 
                continue
1849
 
            sections[entry] = val
1850
 
        #
1851
 
        section.configspec = scalars
1852
 
        for entry in sections:
1853
 
            if not section.has_key(entry):
1854
 
                section[entry] = {}
1855
 
            self._handle_repeat(section[entry], sections[entry])
1856
 
 
1857
 
    def _write_line(self, indent_string, entry, this_entry, comment):
1858
 
        """Write an individual line, for the write method"""
1859
 
        # NOTE: the calls to self._quote here handles non-StringType values.
1860
 
        if not self.unrepr:
1861
 
            val = self._decode_element(self._quote(this_entry))
1862
 
        else:
1863
 
            val = repr(this_entry)
1864
 
        return '%s%s%s%s%s' % (
1865
 
            indent_string,
1866
 
            self._decode_element(self._quote(entry, multiline=False)),
1867
 
            self._a_to_u(' = '),
1868
 
            val,
1869
 
            self._decode_element(comment))
1870
 
 
1871
 
    def _write_marker(self, indent_string, depth, entry, comment):
1872
 
        """Write a section marker line"""
1873
 
        return '%s%s%s%s%s' % (
1874
 
            indent_string,
1875
 
            self._a_to_u('[' * depth),
1876
 
            self._quote(self._decode_element(entry), multiline=False),
1877
 
            self._a_to_u(']' * depth),
1878
 
            self._decode_element(comment))
1879
 
 
1880
 
    def _handle_comment(self, comment):
1881
 
        """Deal with a comment."""
1882
 
        if not comment:
1883
 
            return ''
1884
 
        start = self.indent_type
1885
 
        if not comment.startswith('#'):
1886
 
            start += self._a_to_u(' # ')
1887
 
        return (start + comment)
1888
 
 
1889
 
    # Public methods
1890
 
 
1891
 
    def write(self, outfile=None, section=None):
1892
 
        """
1893
 
        Write the current ConfigObj as a file
1894
 
        
1895
 
        tekNico: FIXME: use StringIO instead of real files
1896
 
        
1897
 
        >>> filename = a.filename
1898
 
        >>> a.filename = 'test.ini'
1899
 
        >>> a.write()
1900
 
        >>> a.filename = filename
1901
 
        >>> a == ConfigObj('test.ini', raise_errors=True)
1902
 
        1
1903
 
        """
1904
 
        if self.indent_type is None:
1905
 
            # this can be true if initialised from a dictionary
1906
 
            self.indent_type = DEFAULT_INDENT_TYPE
1907
 
        #
1908
 
        out = []
1909
 
        cs = self._a_to_u('#')
1910
 
        csp = self._a_to_u('# ')
1911
 
        if section is None:
1912
 
            int_val = self.interpolation
1913
 
            self.interpolation = False
1914
 
            section = self
1915
 
            for line in self.initial_comment:
1916
 
                line = self._decode_element(line)
1917
 
                stripped_line = line.strip()
1918
 
                if stripped_line and not stripped_line.startswith(cs):
1919
 
                    line = csp + line
1920
 
                out.append(line)
1921
 
        #
1922
 
        indent_string = self.indent_type * section.depth
1923
 
        for entry in (section.scalars + section.sections):
1924
 
            if entry in section.defaults:
1925
 
                # don't write out default values
1926
 
                continue
1927
 
            for comment_line in section.comments[entry]:
1928
 
                comment_line = self._decode_element(comment_line.lstrip())
1929
 
                if comment_line and not comment_line.startswith(cs):
1930
 
                    comment_line = csp + comment_line
1931
 
                out.append(indent_string + comment_line)
1932
 
            this_entry = section[entry]
1933
 
            comment = self._handle_comment(section.inline_comments[entry])
1934
 
            #
1935
 
            if isinstance(this_entry, dict):
1936
 
                # a section
1937
 
                out.append(self._write_marker(
1938
 
                    indent_string,
1939
 
                    this_entry.depth,
1940
 
                    entry,
1941
 
                    comment))
1942
 
                out.extend(self.write(section=this_entry))
1943
 
            else:
1944
 
                out.append(self._write_line(
1945
 
                    indent_string,
1946
 
                    entry,
1947
 
                    this_entry,
1948
 
                    comment))
1949
 
        #
1950
 
        if section is self:
1951
 
            for line in self.final_comment:
1952
 
                line = self._decode_element(line)
1953
 
                stripped_line = line.strip()
1954
 
                if stripped_line and not stripped_line.startswith(cs):
1955
 
                    line = csp + line
1956
 
                out.append(line)
1957
 
            self.interpolation = int_val
1958
 
        #
1959
 
        if section is not self:
1960
 
            return out
1961
 
        #
1962
 
        if (self.filename is None) and (outfile is None):
1963
 
            # output a list of lines
1964
 
            # might need to encode
1965
 
            # NOTE: This will *screw* UTF16, each line will start with the BOM
1966
 
            if self.encoding:
1967
 
                out = [l.encode(self.encoding) for l in out]
1968
 
            if (self.BOM and ((self.encoding is None) or
1969
 
                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1970
 
                # Add the UTF8 BOM
1971
 
                if not out:
1972
 
                    out.append('')
1973
 
                out[0] = BOM_UTF8 + out[0]
1974
 
            return out
1975
 
        #
1976
 
        # Turn the list to a string, joined with correct newlines
1977
 
        output = (self._a_to_u(self.newlines or os.linesep)
1978
 
            ).join(out)
1979
 
        if self.encoding:
1980
 
            output = output.encode(self.encoding)
1981
 
        if (self.BOM and ((self.encoding is None) or
1982
 
            (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1983
 
            # Add the UTF8 BOM
1984
 
            output = BOM_UTF8 + output
1985
 
        if outfile is not None:
1986
 
            outfile.write(output)
1987
 
        else:
1988
 
            h = open(self.filename, 'wb')
1989
 
            h.write(output)
1990
 
            h.close()
1991
 
 
1992
 
    def validate(self, validator, preserve_errors=False, copy=False,
1993
 
        section=None):
1994
 
        """
1995
 
        Test the ConfigObj against a configspec.
1996
 
        
1997
 
        It uses the ``validator`` object from *validate.py*.
1998
 
        
1999
 
        To run ``validate`` on the current ConfigObj, call: ::
2000
 
        
2001
 
            test = config.validate(validator)
2002
 
        
2003
 
        (Normally having previously passed in the configspec when the ConfigObj
2004
 
        was created - you can dynamically assign a dictionary of checks to the
2005
 
        ``configspec`` attribute of a section though).
2006
 
        
2007
 
        It returns ``True`` if everything passes, or a dictionary of
2008
 
        pass/fails (True/False). If every member of a subsection passes, it
2009
 
        will just have the value ``True``. (It also returns ``False`` if all
2010
 
        members fail).
2011
 
        
2012
 
        In addition, it converts the values from strings to their native
2013
 
        types if their checks pass (and ``stringify`` is set).
2014
 
        
2015
 
        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2016
 
        of a marking a fail with a ``False``, it will preserve the actual
2017
 
        exception object. This can contain info about the reason for failure.
2018
 
        For example the ``VdtValueTooSmallError`` indeicates that the value
2019
 
        supplied was too small. If a value (or section) is missing it will
2020
 
        still be marked as ``False``.
2021
 
        
2022
 
        You must have the validate module to use ``preserve_errors=True``.
2023
 
        
2024
 
        You can then use the ``flatten_errors`` function to turn your nested
2025
 
        results dictionary into a flattened list of failures - useful for
2026
 
        displaying meaningful error messages.
2027
 
        """
2028
 
        if section is None:
2029
 
            if self.configspec is None:
2030
 
                raise ValueError, 'No configspec supplied.'
2031
 
            if preserve_errors:
2032
 
                if VdtMissingValue is None:
2033
 
                    raise ImportError('Missing validate module.')
2034
 
            section = self
2035
 
        #
2036
 
        spec_section = section.configspec
2037
 
        if copy and hasattr(section, '_configspec_initial_comment'):
2038
 
            section.initial_comment = section._configspec_initial_comment
2039
 
            section.final_comment = section._configspec_final_comment
2040
 
            section.encoding = section._configspec_encoding
2041
 
            section.BOM = section._configspec_BOM
2042
 
            section.newlines = section._configspec_newlines
2043
 
            section.indent_type = section._configspec_indent_type
2044
 
        if '__many__' in section.configspec:
2045
 
            many = spec_section['__many__']
2046
 
            # dynamically assign the configspecs
2047
 
            # for the sections below
2048
 
            for entry in section.sections:
2049
 
                self._handle_repeat(section[entry], many)
2050
 
        #
2051
 
        out = {}
2052
 
        ret_true = True
2053
 
        ret_false = True
2054
 
        order = [k for k in section._order if k in spec_section]
2055
 
        order += [k for k in spec_section if k not in order]
2056
 
        for entry in order:
2057
 
            if entry == '__many__':
2058
 
                continue
2059
 
            if (not entry in section.scalars) or (entry in section.defaults):
2060
 
                # missing entries
2061
 
                # or entries from defaults
2062
 
                missing = True
2063
 
                val = None
2064
 
                if copy and not entry in section.scalars:
2065
 
                    # copy comments
2066
 
                    section.comments[entry] = (
2067
 
                        section._configspec_comments.get(entry, []))
2068
 
                    section.inline_comments[entry] = (
2069
 
                        section._configspec_inline_comments.get(entry, ''))
2070
 
                #
2071
 
            else:
2072
 
                missing = False
2073
 
                val = section[entry]
2074
 
            try:
2075
 
                check = validator.check(spec_section[entry],
2076
 
                                        val,
2077
 
                                        missing=missing
2078
 
                                        )
2079
 
            except validator.baseErrorClass, e:
2080
 
                if not preserve_errors or isinstance(e, VdtMissingValue):
2081
 
                    out[entry] = False
2082
 
                else:
2083
 
                    # preserve the error
2084
 
                    out[entry] = e
2085
 
                    ret_false = False
2086
 
                ret_true = False
2087
 
            else:
2088
 
                ret_false = False
2089
 
                out[entry] = True
2090
 
                if self.stringify or missing:
2091
 
                    # if we are doing type conversion
2092
 
                    # or the value is a supplied default
2093
 
                    if not self.stringify:
2094
 
                        if isinstance(check, (list, tuple)):
2095
 
                            # preserve lists
2096
 
                            check = [self._str(item) for item in check]
2097
 
                        elif missing and check is None:
2098
 
                            # convert the None from a default to a ''
2099
 
                            check = ''
2100
 
                        else:
2101
 
                            check = self._str(check)
2102
 
                    if (check != val) or missing:
2103
 
                        section[entry] = check
2104
 
                if not copy and missing and entry not in section.defaults:
2105
 
                    section.defaults.append(entry)
2106
 
        #
2107
 
        # Missing sections will have been created as empty ones when the
2108
 
        # configspec was read.
2109
 
        for entry in section.sections:
2110
 
            # FIXME: this means DEFAULT is not copied in copy mode
2111
 
            if section is self and entry == 'DEFAULT':
2112
 
                continue
2113
 
            if copy:
2114
 
                section.comments[entry] = section._cs_section_comments[entry]
2115
 
                section.inline_comments[entry] = (
2116
 
                    section._cs_section_inline_comments[entry])
2117
 
            check = self.validate(validator, preserve_errors=preserve_errors,
2118
 
                copy=copy, section=section[entry])
2119
 
            out[entry] = check
2120
 
            if check == False:
2121
 
                ret_true = False
2122
 
            elif check == True:
2123
 
                ret_false = False
2124
 
            else:
2125
 
                ret_true = False
2126
 
                ret_false = False
2127
 
        #
2128
 
        if ret_true:
2129
 
            return True
2130
 
        elif ret_false:
2131
 
            return False
2132
 
        else:
2133
 
            return out
2134
 
 
2135
 
class SimpleVal(object):
2136
 
    """
2137
 
    A simple validator.
2138
 
    Can be used to check that all members expected are present.
2139
 
    
2140
 
    To use it, provide a configspec with all your members in (the value given
2141
 
    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2142
 
    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2143
 
    members are present, or a dictionary with True/False meaning
2144
 
    present/missing. (Whole missing sections will be replaced with ``False``)
2145
 
    """
2146
 
    
2147
 
    def __init__(self):
2148
 
        self.baseErrorClass = ConfigObjError
2149
 
    
2150
 
    def check(self, check, member, missing=False):
2151
 
        """A dummy check method, always returns the value unchanged."""
2152
 
        if missing:
2153
 
            raise self.baseErrorClass
2154
 
        return member
2155
 
 
2156
 
# Check / processing functions for options
2157
 
def flatten_errors(cfg, res, levels=None, results=None):
2158
 
    """
2159
 
    An example function that will turn a nested dictionary of results
2160
 
    (as returned by ``ConfigObj.validate``) into a flat list.
2161
 
    
2162
 
    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2163
 
    dictionary returned by ``validate``.
2164
 
    
2165
 
    (This is a recursive function, so you shouldn't use the ``levels`` or
2166
 
    ``results`` arguments - they are used by the function.
2167
 
    
2168
 
    Returns a list of keys that failed. Each member of the list is a tuple :
2169
 
    ::
2170
 
    
2171
 
        ([list of sections...], key, result)
2172
 
    
2173
 
    If ``validate`` was called with ``preserve_errors=False`` (the default)
2174
 
    then ``result`` will always be ``False``.
2175
 
 
2176
 
    *list of sections* is a flattened list of sections that the key was found
2177
 
    in.
2178
 
    
2179
 
    If the section was missing then key will be ``None``.
2180
 
    
2181
 
    If the value (or section) was missing then ``result`` will be ``False``.
2182
 
    
2183
 
    If ``validate`` was called with ``preserve_errors=True`` and a value
2184
 
    was present, but failed the check, then ``result`` will be the exception
2185
 
    object returned. You can use this as a string that describes the failure.
2186
 
    
2187
 
    For example *The value "3" is of the wrong type*.
2188
 
    
2189
 
    >>> import validate
2190
 
    >>> vtor = validate.Validator()
2191
 
    >>> my_ini = '''
2192
 
    ...     option1 = True
2193
 
    ...     [section1]
2194
 
    ...     option1 = True
2195
 
    ...     [section2]
2196
 
    ...     another_option = Probably
2197
 
    ...     [section3]
2198
 
    ...     another_option = True
2199
 
    ...     [[section3b]]
2200
 
    ...     value = 3
2201
 
    ...     value2 = a
2202
 
    ...     value3 = 11
2203
 
    ...     '''
2204
 
    >>> my_cfg = '''
2205
 
    ...     option1 = boolean()
2206
 
    ...     option2 = boolean()
2207
 
    ...     option3 = boolean(default=Bad_value)
2208
 
    ...     [section1]
2209
 
    ...     option1 = boolean()
2210
 
    ...     option2 = boolean()
2211
 
    ...     option3 = boolean(default=Bad_value)
2212
 
    ...     [section2]
2213
 
    ...     another_option = boolean()
2214
 
    ...     [section3]
2215
 
    ...     another_option = boolean()
2216
 
    ...     [[section3b]]
2217
 
    ...     value = integer
2218
 
    ...     value2 = integer
2219
 
    ...     value3 = integer(0, 10)
2220
 
    ...         [[[section3b-sub]]]
2221
 
    ...         value = string
2222
 
    ...     [section4]
2223
 
    ...     another_option = boolean()
2224
 
    ...     '''
2225
 
    >>> cs = my_cfg.split('\\n')
2226
 
    >>> ini = my_ini.split('\\n')
2227
 
    >>> cfg = ConfigObj(ini, configspec=cs)
2228
 
    >>> res = cfg.validate(vtor, preserve_errors=True)
2229
 
    >>> errors = []
2230
 
    >>> for entry in flatten_errors(cfg, res):
2231
 
    ...     section_list, key, error = entry
2232
 
    ...     section_list.insert(0, '[root]')
2233
 
    ...     if key is not None:
2234
 
    ...        section_list.append(key)
2235
 
    ...     else:
2236
 
    ...         section_list.append('[missing]')
2237
 
    ...     section_string = ', '.join(section_list)
2238
 
    ...     errors.append((section_string, ' = ', error))
2239
 
    >>> errors.sort()
2240
 
    >>> for entry in errors:
2241
 
    ...     print entry[0], entry[1], (entry[2] or 0)
2242
 
    [root], option2  =  0
2243
 
    [root], option3  =  the value "Bad_value" is of the wrong type.
2244
 
    [root], section1, option2  =  0
2245
 
    [root], section1, option3  =  the value "Bad_value" is of the wrong type.
2246
 
    [root], section2, another_option  =  the value "Probably" is of the wrong type.
2247
 
    [root], section3, section3b, section3b-sub, [missing]  =  0
2248
 
    [root], section3, section3b, value2  =  the value "a" is of the wrong type.
2249
 
    [root], section3, section3b, value3  =  the value "11" is too big.
2250
 
    [root], section4, [missing]  =  0
2251
 
    """
2252
 
    if levels is None:
2253
 
        # first time called
2254
 
        levels = []
2255
 
        results = []
2256
 
    if res is True:
2257
 
        return results
2258
 
    if res is False:
2259
 
        results.append((levels[:], None, False))
2260
 
        if levels:
2261
 
            levels.pop()
2262
 
        return results
2263
 
    for (key, val) in res.items():
2264
 
        if val == True:
2265
 
            continue
2266
 
        if isinstance(cfg.get(key), dict):
2267
 
            # Go down one level
2268
 
            levels.append(key)
2269
 
            flatten_errors(cfg[key], val, levels, results)
2270
 
            continue
2271
 
        results.append((levels[:], key, val))
2272
 
    #
2273
 
    # Go up one level
2274
 
    if levels:
2275
 
        levels.pop()
2276
 
    #
2277
 
    return results
2278
 
 
2279
 
"""*A programming language is a medium of expression.* - Paul Graham"""