/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Andrew Bennetts
  • Date: 2008-02-18 08:30:38 UTC
  • mto: This revision was merged to the branch mainline in revision 3756.
  • Revision ID: andrew.bennetts@canonical.com-20080218083038-tts55zsx5xrz3l2e
Lots of assorted hackery to reduce the number of imports for common operations.  Improves 'rocks', 'st' and 'help' times by ~50ms on my laptop.

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