~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Date: 2007-07-05 19:39:28 UTC
  • mto: This revision was merged to the branch mainline in revision 2614.
  • Revision ID: john@arbash-meinel.com-20070705193928-xtm8nh4ucc8qosdn
Add direct tests of how we handle incomplete/'broken' lines

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# configobj.py
2
2
# A config file reader/writer that supports nested sections in config files.
3
 
# Copyright (C) 2005-2006 Michael Foord, Nicola Larosa
 
3
# Copyright (C) 2005 Michael Foord, Nicola Larosa
4
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
5
#         nico AT tekNico DOT net
6
6
 
18
18
 
19
19
from __future__ import generators
20
20
 
 
21
"""
 
22
    >>> z = ConfigObj()
 
23
    >>> z['a'] = 'a'
 
24
    >>> z['sect'] = {
 
25
    ...    'subsect': {
 
26
    ...         'a': 'fish',
 
27
    ...         'b': 'wobble',
 
28
    ...     },
 
29
    ...     'member': 'value',
 
30
    ... }
 
31
    >>> x = ConfigObj(z.write())
 
32
    >>> z == x
 
33
    1
 
34
"""
 
35
 
21
36
import sys
22
37
INTP_VER = sys.version_info[:2]
23
38
if INTP_VER < (2, 2):
24
39
    raise RuntimeError("Python v.2.2 or later needed")
25
40
 
26
41
import os, re
27
 
compiler = None
28
 
try:
29
 
    import compiler
30
 
except ImportError:
31
 
    # for IronPython
32
 
    pass
33
42
from types import StringTypes
34
43
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
 
44
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
51
45
 
52
46
# A dictionary mapping BOM to
53
47
# the encoding to decode with, and what to set the
109
103
    True, False = 1, 0
110
104
 
111
105
 
112
 
__version__ = '4.4.0'
 
106
__version__ = '4.2.0beta2'
113
107
 
114
108
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
115
109
 
116
110
__docformat__ = "restructuredtext en"
117
111
 
 
112
# NOTE: Does it make sense to have the following in __all__ ?
 
113
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
 
114
# NOTE: If used as from configobj import...
 
115
# NOTE: They are effectively read only
118
116
__all__ = (
119
117
    '__version__',
120
118
    'DEFAULT_INDENT_TYPE',
121
 
    'DEFAULT_INTERPOLATION',
 
119
    'NUM_INDENT_SPACES',
 
120
    'MAX_INTERPOL_DEPTH',
122
121
    'ConfigObjError',
123
122
    'NestingError',
124
123
    'ParseError',
127
126
    'ConfigObj',
128
127
    'SimpleVal',
129
128
    'InterpolationError',
130
 
    'InterpolationLoopError',
 
129
    'InterpolationDepthError',
131
130
    'MissingInterpolationOption',
132
131
    'RepeatSectionError',
133
 
    'UnreprError',
134
 
    'UnknownType',
135
132
    '__docformat__',
136
133
    'flatten_errors',
137
134
)
138
135
 
139
 
DEFAULT_INTERPOLATION = 'configparser'
140
 
DEFAULT_INDENT_TYPE = '    '
 
136
DEFAULT_INDENT_TYPE = ' '
 
137
NUM_INDENT_SPACES = 4
141
138
MAX_INTERPOL_DEPTH = 10
142
139
 
143
140
OPTION_DEFAULTS = {
152
149
    'indent_type': None,
153
150
    'encoding': None,
154
151
    'default_encoding': None,
155
 
    'unrepr': False,
156
 
    'write_empty_values': False,
157
152
}
158
153
 
159
 
 
160
 
def getObj(s):
161
 
    s = "a=" + s
162
 
    if compiler is None:
163
 
        raise ImportError('compiler module not available')
164
 
    p = compiler.parse(s)
165
 
    return p.getChildren()[1].getChildren()[0].getChildren()[1]
166
 
 
167
 
class UnknownType(Exception):
168
 
    pass
169
 
 
170
 
class Builder:
171
 
    
172
 
    def build(self, o):
173
 
        m = getattr(self, 'build_' + o.__class__.__name__, None)
174
 
        if m is None:
175
 
            raise UnknownType(o.__class__.__name__)
176
 
        return m(o)
177
 
    
178
 
    def build_List(self, o):
179
 
        return map(self.build, o.getChildren())
180
 
    
181
 
    def build_Const(self, o):
182
 
        return o.value
183
 
    
184
 
    def build_Dict(self, o):
185
 
        d = {}
186
 
        i = iter(map(self.build, o.getChildren()))
187
 
        for el in i:
188
 
            d[el] = i.next()
189
 
        return d
190
 
    
191
 
    def build_Tuple(self, o):
192
 
        return tuple(self.build_List(o))
193
 
    
194
 
    def build_Name(self, o):
195
 
        if o.name == 'None':
196
 
            return None
197
 
        if o.name == 'True':
198
 
            return True
199
 
        if o.name == 'False':
200
 
            return False
201
 
        
202
 
        # An undefinted Name
203
 
        raise UnknownType('Undefined Name')
204
 
    
205
 
    def build_Add(self, o):
206
 
        real, imag = map(self.build_Const, o.getChildren())
207
 
        try:
208
 
            real = float(real)
209
 
        except TypeError:
210
 
            raise UnknownType('Add')
211
 
        if not isinstance(imag, complex) or imag.real != 0.0:
212
 
            raise UnknownType('Add')
213
 
        return real+imag
214
 
    
215
 
    def build_Getattr(self, o):
216
 
        parent = self.build(o.expr)
217
 
        return getattr(parent, o.attrname)
218
 
    
219
 
    def build_UnarySub(self, o):
220
 
        return -self.build_Const(o.getChildren()[0])
221
 
    
222
 
    def build_UnaryAdd(self, o):
223
 
        return self.build_Const(o.getChildren()[0])
224
 
 
225
 
def unrepr(s):
226
 
    if not s:
227
 
        return s
228
 
    return Builder().build(getObj(s))
229
 
 
230
 
def _splitlines(instring):
231
 
    """Split a string on lines, without losing line endings or truncating."""
232
 
    
233
 
 
234
154
class ConfigObjError(SyntaxError):
235
155
    """
236
156
    This is the base class for all errors that ConfigObj raises.
237
157
    It is a subclass of SyntaxError.
 
158
    
 
159
    >>> raise ConfigObjError
 
160
    Traceback (most recent call last):
 
161
    ConfigObjError
238
162
    """
239
163
    def __init__(self, message='', line_number=None, line=''):
240
164
        self.line = line
245
169
class NestingError(ConfigObjError):
246
170
    """
247
171
    This error indicates a level of nesting that doesn't match.
 
172
    
 
173
    >>> raise NestingError
 
174
    Traceback (most recent call last):
 
175
    NestingError
248
176
    """
249
177
 
250
178
class ParseError(ConfigObjError):
252
180
    This error indicates that a line is badly written.
253
181
    It is neither a valid ``key = value`` line,
254
182
    nor a valid section marker line.
 
183
    
 
184
    >>> raise ParseError
 
185
    Traceback (most recent call last):
 
186
    ParseError
255
187
    """
256
188
 
257
189
class DuplicateError(ConfigObjError):
258
190
    """
259
191
    The keyword or section specified already exists.
 
192
    
 
193
    >>> raise DuplicateError
 
194
    Traceback (most recent call last):
 
195
    DuplicateError
260
196
    """
261
197
 
262
198
class ConfigspecError(ConfigObjError):
263
199
    """
264
200
    An error occured whilst parsing a configspec.
 
201
    
 
202
    >>> raise ConfigspecError
 
203
    Traceback (most recent call last):
 
204
    ConfigspecError
265
205
    """
266
206
 
267
207
class InterpolationError(ConfigObjError):
268
208
    """Base class for the two interpolation errors."""
269
209
 
270
 
class InterpolationLoopError(InterpolationError):
 
210
class InterpolationDepthError(InterpolationError):
271
211
    """Maximum interpolation depth exceeded in string interpolation."""
272
212
 
273
213
    def __init__(self, option):
 
214
        """
 
215
        >>> raise InterpolationDepthError('yoda')
 
216
        Traceback (most recent call last):
 
217
        InterpolationDepthError: max interpolation depth exceeded in value "yoda".
 
218
        """
274
219
        InterpolationError.__init__(
275
220
            self,
276
 
            'interpolation loop detected in value "%s".' % option)
 
221
            'max interpolation depth exceeded in value "%s".' % option)
277
222
 
278
223
class RepeatSectionError(ConfigObjError):
279
224
    """
280
225
    This error indicates additional sections in a section with a
281
226
    ``__many__`` (repeated) section.
 
227
    
 
228
    >>> raise RepeatSectionError
 
229
    Traceback (most recent call last):
 
230
    RepeatSectionError
282
231
    """
283
232
 
284
233
class MissingInterpolationOption(InterpolationError):
285
234
    """A value specified for interpolation was missing."""
286
235
 
287
236
    def __init__(self, option):
 
237
        """
 
238
        >>> raise MissingInterpolationOption('yoda')
 
239
        Traceback (most recent call last):
 
240
        MissingInterpolationOption: missing option "yoda" in interpolation.
 
241
        """
288
242
        InterpolationError.__init__(
289
243
            self,
290
244
            'missing option "%s" in interpolation.' % option)
291
245
 
292
 
class UnreprError(ConfigObjError):
293
 
    """An error parsing in unrepr mode."""
294
 
 
295
 
 
296
 
class InterpolationEngine(object):
297
 
    """
298
 
    A helper class to help perform string interpolation.
299
 
 
300
 
    This class is an abstract base class; its descendants perform
301
 
    the actual work.
302
 
    """
303
 
 
304
 
    # compiled regexp to use in self.interpolate()
305
 
    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
306
 
 
307
 
    def __init__(self, section):
308
 
        # the Section instance that "owns" this engine
309
 
        self.section = section
310
 
 
311
 
    def interpolate(self, key, value):
312
 
        def recursive_interpolate(key, value, section, backtrail):
313
 
            """The function that does the actual work.
314
 
 
315
 
            ``value``: the string we're trying to interpolate.
316
 
            ``section``: the section in which that string was found
317
 
            ``backtrail``: a dict to keep track of where we've been,
318
 
            to detect and prevent infinite recursion loops
319
 
 
320
 
            This is similar to a depth-first-search algorithm.
321
 
            """
322
 
            # Have we been here already?
323
 
            if backtrail.has_key((key, section.name)):
324
 
                # Yes - infinite loop detected
325
 
                raise InterpolationLoopError(key)
326
 
            # Place a marker on our backtrail so we won't come back here again
327
 
            backtrail[(key, section.name)] = 1
328
 
 
329
 
            # Now start the actual work
330
 
            match = self._KEYCRE.search(value)
331
 
            while match:
332
 
                # The actual parsing of the match is implementation-dependent,
333
 
                # so delegate to our helper function
334
 
                k, v, s = self._parse_match(match)
335
 
                if k is None:
336
 
                    # That's the signal that no further interpolation is needed
337
 
                    replacement = v
338
 
                else:
339
 
                    # Further interpolation may be needed to obtain final value
340
 
                    replacement = recursive_interpolate(k, v, s, backtrail)
341
 
                # Replace the matched string with its final value
342
 
                start, end = match.span()
343
 
                value = ''.join((value[:start], replacement, value[end:]))
344
 
                new_search_start = start + len(replacement)
345
 
                # Pick up the next interpolation key, if any, for next time
346
 
                # through the while loop
347
 
                match = self._KEYCRE.search(value, new_search_start)
348
 
 
349
 
            # Now safe to come back here again; remove marker from backtrail
350
 
            del backtrail[(key, section.name)]
351
 
 
352
 
            return value
353
 
 
354
 
        # Back in interpolate(), all we have to do is kick off the recursive
355
 
        # function with appropriate starting values
356
 
        value = recursive_interpolate(key, value, self.section, {})
357
 
        return value
358
 
 
359
 
    def _fetch(self, key):
360
 
        """Helper function to fetch values from owning section.
361
 
 
362
 
        Returns a 2-tuple: the value, and the section where it was found.
363
 
        """
364
 
        # switch off interpolation before we try and fetch anything !
365
 
        save_interp = self.section.main.interpolation
366
 
        self.section.main.interpolation = False
367
 
 
368
 
        # Start at section that "owns" this InterpolationEngine
369
 
        current_section = self.section
370
 
        while True:
371
 
            # try the current section first
372
 
            val = current_section.get(key)
373
 
            if val is not None:
374
 
                break
375
 
            # try "DEFAULT" next
376
 
            val = current_section.get('DEFAULT', {}).get(key)
377
 
            if val is not None:
378
 
                break
379
 
            # move up to parent and try again
380
 
            # top-level's parent is itself
381
 
            if current_section.parent is current_section:
382
 
                # reached top level, time to give up
383
 
                break
384
 
            current_section = current_section.parent
385
 
 
386
 
        # restore interpolation to previous value before returning
387
 
        self.section.main.interpolation = save_interp
388
 
        if val is None:
389
 
            raise MissingInterpolationOption(key)
390
 
        return val, current_section
391
 
 
392
 
    def _parse_match(self, match):
393
 
        """Implementation-dependent helper function.
394
 
 
395
 
        Will be passed a match object corresponding to the interpolation
396
 
        key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
397
 
        key in the appropriate config file section (using the ``_fetch()``
398
 
        helper function) and return a 3-tuple: (key, value, section)
399
 
 
400
 
        ``key`` is the name of the key we're looking for
401
 
        ``value`` is the value found for that key
402
 
        ``section`` is a reference to the section where it was found
403
 
 
404
 
        ``key`` and ``section`` should be None if no further
405
 
        interpolation should be performed on the resulting value
406
 
        (e.g., if we interpolated "$$" and returned "$").
407
 
        """
408
 
        raise NotImplementedError
409
 
    
410
 
 
411
 
class ConfigParserInterpolation(InterpolationEngine):
412
 
    """Behaves like ConfigParser."""
413
 
    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
414
 
 
415
 
    def _parse_match(self, match):
416
 
        key = match.group(1)
417
 
        value, section = self._fetch(key)
418
 
        return key, value, section
419
 
 
420
 
 
421
 
class TemplateInterpolation(InterpolationEngine):
422
 
    """Behaves like string.Template."""
423
 
    _delimiter = '$'
424
 
    _KEYCRE = re.compile(r"""
425
 
        \$(?:
426
 
          (?P<escaped>\$)              |   # Two $ signs
427
 
          (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
428
 
          {(?P<braced>[^}]*)}              # ${name} format
429
 
        )
430
 
        """, re.IGNORECASE | re.VERBOSE)
431
 
 
432
 
    def _parse_match(self, match):
433
 
        # Valid name (in or out of braces): fetch value from section
434
 
        key = match.group('named') or match.group('braced')
435
 
        if key is not None:
436
 
            value, section = self._fetch(key)
437
 
            return key, value, section
438
 
        # Escaped delimiter (e.g., $$): return single delimiter
439
 
        if match.group('escaped') is not None:
440
 
            # Return None for key and section to indicate it's time to stop
441
 
            return None, self._delimiter, None
442
 
        # Anything else: ignore completely, just return it unchanged
443
 
        return None, match.group(), None
444
 
 
445
 
interpolation_engines = {
446
 
    'configparser': ConfigParserInterpolation,
447
 
    'template': TemplateInterpolation,
448
 
}
449
 
 
450
246
class Section(dict):
451
247
    """
452
248
    A dictionary-like object that represents a section in a config file.
453
249
    
454
 
    It does string interpolation if the 'interpolation' attribute
 
250
    It does string interpolation if the 'interpolate' attribute
455
251
    of the 'main' object is set to True.
456
252
    
457
 
    Interpolation is tried first from this object, then from the 'DEFAULT'
458
 
    section of this object, next from the parent and its 'DEFAULT' section,
459
 
    and so on until the main object is reached.
 
253
    Interpolation is tried first from the 'DEFAULT' section of this object,
 
254
    next from the 'DEFAULT' section of the parent, lastly the main object.
460
255
    
461
256
    A Section will behave like an ordered dictionary - following the
462
257
    order of the ``scalars`` and ``sections`` attributes.
465
260
    Iteration follows the order: scalars, then sections.
466
261
    """
467
262
 
 
263
    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
 
264
 
468
265
    def __init__(self, parent, depth, main, indict=None, name=None):
469
266
        """
470
267
        * parent is the section above
492
289
        self.inline_comments = {}
493
290
        # for the configspec
494
291
        self.configspec = {}
495
 
        self._order = []
496
 
        self._configspec_comments = {}
497
 
        self._configspec_inline_comments = {}
498
 
        self._cs_section_comments = {}
499
 
        self._cs_section_inline_comments = {}
500
292
        # for defaults
501
293
        self.defaults = []
502
294
        #
505
297
        for entry in indict:
506
298
            self[entry] = indict[entry]
507
299
 
508
 
    def _interpolate(self, key, value):
509
 
        try:
510
 
            # do we already have an interpolation engine?
511
 
            engine = self._interpolation_engine
512
 
        except AttributeError:
513
 
            # not yet: first time running _interpolate(), so pick the engine
514
 
            name = self.main.interpolation
515
 
            if name == True:  # note that "if name:" would be incorrect here
516
 
                # backwards-compatibility: interpolation=True means use default
517
 
                name = DEFAULT_INTERPOLATION
518
 
            name = name.lower()  # so that "Template", "template", etc. all work
519
 
            class_ = interpolation_engines.get(name, None)
520
 
            if class_ is None:
521
 
                # invalid value for self.main.interpolation
522
 
                self.main.interpolation = False
523
 
                return value
 
300
    def _interpolate(self, value):
 
301
        """Nicked from ConfigParser."""
 
302
        depth = MAX_INTERPOL_DEPTH
 
303
        # loop through this until it's done
 
304
        while depth:
 
305
            depth -= 1
 
306
            if value.find("%(") != -1:
 
307
                value = self._KEYCRE.sub(self._interpolation_replace, value)
524
308
            else:
525
 
                # save reference to engine so we don't have to do this again
526
 
                engine = self._interpolation_engine = class_(self)
527
 
        # let the engine do the actual work
528
 
        return engine.interpolate(key, value)
 
309
                break
 
310
        else:
 
311
            raise InterpolationDepthError(value)
 
312
        return value
 
313
 
 
314
    def _interpolation_replace(self, match):
 
315
        """ """
 
316
        s = match.group(1)
 
317
        if s is None:
 
318
            return match.group()
 
319
        else:
 
320
            # switch off interpolation before we try and fetch anything !
 
321
            self.main.interpolation = False
 
322
            # try the 'DEFAULT' member of *this section* first
 
323
            val = self.get('DEFAULT', {}).get(s)
 
324
            # try the 'DEFAULT' member of the *parent section* next
 
325
            if val is None:
 
326
                val = self.parent.get('DEFAULT', {}).get(s)
 
327
            # last, try the 'DEFAULT' member of the *main section*
 
328
            if val is None:
 
329
                val = self.main.get('DEFAULT', {}).get(s)
 
330
            self.main.interpolation = True
 
331
            if val is None:
 
332
                raise MissingInterpolationOption(s)
 
333
            return val
529
334
 
530
335
    def __getitem__(self, key):
531
336
        """Fetch the item and do string interpolation."""
532
337
        val = dict.__getitem__(self, key)
533
338
        if self.main.interpolation and isinstance(val, StringTypes):
534
 
            return self._interpolate(key, val)
 
339
            return self._interpolate(val)
535
340
        return val
536
341
 
537
 
    def __setitem__(self, key, value, unrepr=False):
 
342
    def __setitem__(self, key, value):
538
343
        """
539
344
        Correctly set a value.
540
345
        
544
349
        Keys must be strings.
545
350
        Values need only be strings (or lists of strings) if
546
351
        ``main.stringify`` is set.
547
 
        
548
 
        `unrepr`` must be set when setting a value to a dictionary, without
549
 
        creating a new sub-section.
550
352
        """
551
353
        if not isinstance(key, StringTypes):
552
354
            raise ValueError, 'The key "%s" is not a string.' % key
553
355
        # add the comment
554
 
        if not self.comments.has_key(key):
 
356
        if key not in self.comments:
555
357
            self.comments[key] = []
556
358
            self.inline_comments[key] = ''
557
359
        # remove the entry from defaults
559
361
            self.defaults.remove(key)
560
362
        #
561
363
        if isinstance(value, Section):
562
 
            if not self.has_key(key):
 
364
            if key not in self:
563
365
                self.sections.append(key)
564
366
            dict.__setitem__(self, key, value)
565
 
        elif isinstance(value, dict) and not unrepr:
 
367
        elif isinstance(value, dict):
566
368
            # First create the new depth level,
567
369
            # then create the section
568
 
            if not self.has_key(key):
 
370
            if key not in self:
569
371
                self.sections.append(key)
570
372
            new_depth = self.depth + 1
571
373
            dict.__setitem__(
578
380
                    indict=value,
579
381
                    name=key))
580
382
        else:
581
 
            if not self.has_key(key):
 
383
            if key not in self:
582
384
                self.scalars.append(key)
583
385
            if not self.main.stringify:
584
386
                if isinstance(value, StringTypes):
616
418
        for entry in indict:
617
419
            self[entry] = indict[entry]
618
420
 
 
421
 
619
422
    def pop(self, key, *args):
620
423
        """ """
621
424
        val = dict.pop(self, key, *args)
628
431
            del self.inline_comments[key]
629
432
            self.sections.remove(key)
630
433
        if self.main.interpolation and isinstance(val, StringTypes):
631
 
            return self._interpolate(key, val)
 
434
            return self._interpolate(val)
632
435
        return val
633
436
 
634
437
    def popitem(self):
716
519
            this_entry = self[entry]
717
520
            if isinstance(this_entry, Section):
718
521
                this_entry = this_entry.dict()
719
 
            elif isinstance(this_entry, list):
 
522
            elif isinstance(this_entry, (list, tuple)):
720
523
                # create a copy rather than a reference
721
524
                this_entry = list(this_entry)
722
 
            elif isinstance(this_entry, tuple):
723
 
                # create a copy rather than a reference
724
 
                this_entry = tuple(this_entry)
725
525
            newdict[entry] = this_entry
726
526
        return newdict
727
527
 
889
689
        >>> a == m
890
690
        1
891
691
        """
892
 
        warn('use of ``decode`` is deprecated.', DeprecationWarning)
893
 
        def decode(section, key, encoding=encoding, warn=True):
 
692
        def decode(section, key, encoding=encoding):
894
693
            """ """
895
694
            val = section[key]
896
695
            if isinstance(val, (list, tuple)):
915
714
        Works with subsections and list values.
916
715
        Uses the ``walk`` method.
917
716
        """
918
 
        warn('use of ``encode`` is deprecated.', DeprecationWarning)
919
717
        def encode(section, key, encoding=encoding):
920
718
            """ """
921
719
            val = section[key]
1025
823
    
1026
824
 
1027
825
class ConfigObj(Section):
1028
 
    """An object to read, create, and write config files."""
 
826
    """
 
827
    An object to read, create, and write config files.
 
828
    
 
829
    Testing with duplicate keys and sections.
 
830
    
 
831
    >>> c = '''
 
832
    ... [hello]
 
833
    ... member = value
 
834
    ... [hello again]
 
835
    ... member = value
 
836
    ... [ "hello" ]
 
837
    ... member = value
 
838
    ... '''
 
839
    >>> ConfigObj(c.split('\\n'), raise_errors = True)
 
840
    Traceback (most recent call last):
 
841
    DuplicateError: Duplicate section name at line 5.
 
842
    
 
843
    >>> d = '''
 
844
    ... [hello]
 
845
    ... member = value
 
846
    ... [hello again]
 
847
    ... member1 = value
 
848
    ... member2 = value
 
849
    ... 'member1' = value
 
850
    ... [ "and again" ]
 
851
    ... member = value
 
852
    ... '''
 
853
    >>> ConfigObj(d.split('\\n'), raise_errors = True)
 
854
    Traceback (most recent call last):
 
855
    DuplicateError: Duplicate keyword name at line 6.
 
856
    """
1029
857
 
1030
858
    _keyword = re.compile(r'''^ # line start
1031
859
        (\s*)                   # indentation
1055
883
 
1056
884
    # this regexp pulls list values out as a single string
1057
885
    # or single values and comments
1058
 
    # FIXME: this regex adds a '' to the end of comma terminated lists
1059
 
    #   workaround in ``_handle_value``
1060
886
    _valueexp = re.compile(r'''^
1061
887
        (?:
1062
888
            (?:
1065
891
                        (?:
1066
892
                            (?:".*?")|              # double quotes
1067
893
                            (?:'.*?')|              # single quotes
1068
 
                            (?:[^'",\#][^,\#]*?)    # unquoted
 
894
                            (?:[^'",\#][^,\#]*?)       # unquoted
1069
895
                        )
1070
896
                        \s*,\s*                     # comma
1071
897
                    )*      # match all list items ending in a comma (if any)
1073
899
                (
1074
900
                    (?:".*?")|                      # double quotes
1075
901
                    (?:'.*?')|                      # single quotes
1076
 
                    (?:[^'",\#\s][^,]*?)|           # unquoted
1077
 
                    (?:(?<!,))                      # Empty value
 
902
                    (?:[^'",\#\s][^,]*?)             # unquoted
1078
903
                )?          # last item in a list - or string value
1079
904
            )|
1080
905
            (,)             # alternatively a single comma - empty list
1100
925
        (
1101
926
            (?:".*?")|          # double quotes
1102
927
            (?:'.*?')|          # single quotes
1103
 
            (?:[^'"\#].*?)|     # unquoted
1104
 
            (?:)                # Empty value
 
928
            (?:[^'"\#].*?)      # unquoted
1105
929
        )
1106
930
        \s*(\#.*)?              # optional comment
1107
931
        $''',
1136
960
            infile = []
1137
961
        if options is None:
1138
962
            options = {}
1139
 
        else:
1140
 
            options = dict(options)
1141
963
        # keyword arguments take precedence over an options dictionary
1142
964
        options.update(kwargs)
1143
965
        # init the superclass
1166
988
        self.default_encoding = defaults['default_encoding']
1167
989
        self.BOM = False
1168
990
        self.newlines = None
1169
 
        self.write_empty_values = defaults['write_empty_values']
1170
 
        self.unrepr = defaults['unrepr']
1171
991
        #
1172
992
        self.initial_comment = []
1173
993
        self.final_comment = []
1174
994
        #
1175
 
        self._terminated = False
1176
 
        #
1177
995
        if isinstance(infile, StringTypes):
1178
996
            self.filename = infile
1179
997
            if os.path.isfile(infile):
1206
1024
            else:
1207
1025
                self.configspec = None
1208
1026
            return
1209
 
        elif hasattr(infile, 'read'):
 
1027
        elif getattr(infile, 'read', None) is not None:
1210
1028
            # This supports file like objects
1211
1029
            infile = infile.read() or []
1212
1030
            # needs splitting into lines - but needs doing *after* decoding
1223
1041
            # Set the newlines attribute (first line ending it finds)
1224
1042
            # and strip trailing '\n' or '\r' from lines
1225
1043
            for line in infile:
1226
 
                if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
 
1044
                if (not line) or (line[-1] not in '\r\n'):
1227
1045
                    continue
1228
1046
                for end in ('\r\n', '\n', '\r'):
1229
1047
                    if line.endswith(end):
1230
1048
                        self.newlines = end
1231
1049
                        break
1232
1050
                break
1233
 
            if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'):
1234
 
                self._terminated = True
1235
1051
            infile = [line.rstrip('\r\n') for line in infile]
1236
1052
        #
1237
1053
        self._parse(infile)
1238
1054
        # if we had any errors, now is the time to raise them
1239
1055
        if self._errors:
1240
 
            info = "at line %s." % self._errors[0].line_number
1241
 
            if len(self._errors) > 1:
1242
 
                msg = ("Parsing failed with several errors.\nFirst error %s" %
1243
 
                    info)
1244
 
                error = ConfigObjError(msg)
1245
 
            else:
1246
 
                error = self._errors[0]
 
1056
            error = ConfigObjError("Parsing failed.")
1247
1057
            # set the errors attribute; it's a list of tuples:
1248
1058
            # (error_type, message, line_number)
1249
1059
            error.errors = self._errors
1257
1067
            self.configspec = None
1258
1068
        else:
1259
1069
            self._handle_configspec(defaults['configspec'])
1260
 
    
1261
 
    def __repr__(self):
1262
 
        return 'ConfigObj({%s})' % ', '.join(
1263
 
            [('%s: %s' % (repr(key), repr(self[key]))) for key in
1264
 
            (self.scalars + self.sections)])
1265
 
    
 
1070
 
1266
1071
    def _handle_bom(self, infile):
1267
1072
        """
1268
1073
        Handle any BOM, and decode if necessary.
1288
1093
        if ((self.encoding is not None) and
1289
1094
            (self.encoding.lower() not in BOM_LIST)):
1290
1095
            # No need to check for a BOM
1291
 
            # the encoding specified doesn't have one
 
1096
            # encoding specified doesn't have one
1292
1097
            # just decode
1293
1098
            return self._decode(infile, self.encoding)
1294
1099
        #
1364
1169
        else:
1365
1170
            return infile
1366
1171
 
1367
 
    def _a_to_u(self, aString):
1368
 
        """Decode ASCII strings to unicode if a self.encoding is specified."""
1369
 
        if self.encoding:
1370
 
            return aString.decode('ascii')
 
1172
    def _a_to_u(self, string):
 
1173
        """Decode ascii strings to unicode if a self.encoding is specified."""
 
1174
        if not self.encoding:
 
1175
            return string
1371
1176
        else:
1372
 
            return aString
 
1177
            return string.decode('ascii')
1373
1178
 
1374
1179
    def _decode(self, infile, encoding):
1375
1180
        """
1408
1213
            return value
1409
1214
 
1410
1215
    def _parse(self, infile):
1411
 
        """Actually parse the config file."""
1412
 
        temp_list_values = self.list_values
1413
 
        if self.unrepr:
1414
 
            self.list_values = False
 
1216
        """
 
1217
        Actually parse the config file
 
1218
        
 
1219
        Testing Interpolation
 
1220
        
 
1221
        >>> c = ConfigObj()
 
1222
        >>> c['DEFAULT'] = {
 
1223
        ...     'b': 'goodbye',
 
1224
        ...     'userdir': 'c:\\\\home',
 
1225
        ...     'c': '%(d)s',
 
1226
        ...     'd': '%(c)s'
 
1227
        ... }
 
1228
        >>> c['section'] = {
 
1229
        ...     'a': '%(datadir)s\\\\some path\\\\file.py',
 
1230
        ...     'b': '%(userdir)s\\\\some path\\\\file.py',
 
1231
        ...     'c': 'Yo %(a)s',
 
1232
        ...     'd': '%(not_here)s',
 
1233
        ...     'e': '%(c)s',
 
1234
        ... }
 
1235
        >>> c['section']['DEFAULT'] = {
 
1236
        ...     'datadir': 'c:\\\\silly_test',
 
1237
        ...     'a': 'hello - %(b)s',
 
1238
        ... }
 
1239
        >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
 
1240
        1
 
1241
        >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
 
1242
        1
 
1243
        >>> c['section']['c'] == 'Yo hello - goodbye'
 
1244
        1
 
1245
        
 
1246
        Switching Interpolation Off
 
1247
        
 
1248
        >>> c.interpolation = False
 
1249
        >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
 
1250
        1
 
1251
        >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
 
1252
        1
 
1253
        >>> c['section']['c'] == 'Yo %(a)s'
 
1254
        1
 
1255
        
 
1256
        Testing the interpolation errors.
 
1257
        
 
1258
        >>> c.interpolation = True
 
1259
        >>> c['section']['d']
 
1260
        Traceback (most recent call last):
 
1261
        MissingInterpolationOption: missing option "not_here" in interpolation.
 
1262
        >>> c['section']['e']
 
1263
        Traceback (most recent call last):
 
1264
        InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
 
1265
        
 
1266
        Testing our quoting.
 
1267
        
 
1268
        >>> i._quote('\"""\'\'\'')
 
1269
        Traceback (most recent call last):
 
1270
        SyntaxError: EOF while scanning triple-quoted string
 
1271
        >>> try:
 
1272
        ...     i._quote('\\n', multiline=False)
 
1273
        ... except ConfigObjError, e:
 
1274
        ...    e.msg
 
1275
        'Value "\\n" cannot be safely quoted.'
 
1276
        >>> k._quote(' "\' ', multiline=False)
 
1277
        Traceback (most recent call last):
 
1278
        SyntaxError: EOL while scanning single-quoted string
 
1279
        
 
1280
        Testing with "stringify" off.
 
1281
        >>> c.stringify = False
 
1282
        >>> c['test'] = 1
 
1283
        Traceback (most recent call last):
 
1284
        TypeError: Value is not a string "1".
 
1285
        """
1415
1286
        comment_list = []
1416
1287
        done_start = False
1417
1288
        this_section = self
1437
1308
            reset_comment = True
1438
1309
            # first we check if it's a section marker
1439
1310
            mat = self._sectionmarker.match(line)
 
1311
##            print >> sys.stderr, sline, mat
1440
1312
            if mat is not None:
1441
1313
                # is a section line
1442
1314
                (indent, sect_open, sect_name, sect_close, comment) = (
1443
1315
                    mat.groups())
1444
1316
                if indent and (self.indent_type is None):
1445
 
                    self.indent_type = indent
 
1317
                    self.indent_type = indent[0]
1446
1318
                cur_depth = sect_open.count('[')
1447
1319
                if cur_depth != sect_close.count(']'):
1448
1320
                    self._handle_error(
1449
1321
                        "Cannot compute the section depth at line %s.",
1450
1322
                        NestingError, infile, cur_index)
1451
1323
                    continue
1452
 
                #
1453
1324
                if cur_depth < this_section.depth:
1454
1325
                    # the new section is dropping back to a previous level
1455
1326
                    try:
1473
1344
                        NestingError, infile, cur_index)
1474
1345
                #
1475
1346
                sect_name = self._unquote(sect_name)
1476
 
                if parent.has_key(sect_name):
 
1347
                if sect_name in parent:
 
1348
##                    print >> sys.stderr, sect_name
1477
1349
                    self._handle_error(
1478
1350
                        'Duplicate section name at line %s.',
1479
1351
                        DuplicateError, infile, cur_index)
1487
1359
                parent[sect_name] = this_section
1488
1360
                parent.inline_comments[sect_name] = comment
1489
1361
                parent.comments[sect_name] = comment_list
 
1362
##                print >> sys.stderr, parent[sect_name] is this_section
1490
1363
                continue
1491
1364
            #
1492
1365
            # it's not a section marker,
1493
1366
            # so it should be a valid ``key = value`` line
1494
1367
            mat = self._keyword.match(line)
1495
 
            if mat is None:
1496
 
                # it neither matched as a keyword
1497
 
                # or a section marker
1498
 
                self._handle_error(
1499
 
                    'Invalid line at line "%s".',
1500
 
                    ParseError, infile, cur_index)
1501
 
            else:
 
1368
##            print >> sys.stderr, sline, mat
 
1369
            if mat is not None:
1502
1370
                # is a keyword value
1503
1371
                # value will include any inline comment
1504
1372
                (indent, key, value) = mat.groups()
1505
1373
                if indent and (self.indent_type is None):
1506
 
                    self.indent_type = indent
 
1374
                    self.indent_type = indent[0]
1507
1375
                # check for a multiline value
1508
1376
                if value[:3] in ['"""', "'''"]:
1509
1377
                    try:
1514
1382
                            'Parse error in value at line %s.',
1515
1383
                            ParseError, infile, cur_index)
1516
1384
                        continue
1517
 
                    else:
1518
 
                        if self.unrepr:
1519
 
                            comment = ''
1520
 
                            try:
1521
 
                                value = unrepr(value)
1522
 
                            except Exception, e:
1523
 
                                if type(e) == UnknownType:
1524
 
                                    msg = 'Unknown name or type in value at line %s.'
1525
 
                                else:
1526
 
                                    msg = 'Parse error in value at line %s.'
1527
 
                                self._handle_error(msg, UnreprError, infile,
1528
 
                                    cur_index)
1529
 
                                continue
1530
1385
                else:
1531
 
                    if self.unrepr:
1532
 
                        comment = ''
1533
 
                        try:
1534
 
                            value = unrepr(value)
1535
 
                        except Exception, e:
1536
 
                            if isinstance(e, UnknownType):
1537
 
                                msg = 'Unknown name or type in value at line %s.'
1538
 
                            else:
1539
 
                                msg = 'Parse error in value at line %s.'
1540
 
                            self._handle_error(msg, UnreprError, infile,
1541
 
                                cur_index)
1542
 
                            continue
1543
 
                    else:
1544
 
                        # extract comment and lists
1545
 
                        try:
1546
 
                            (value, comment) = self._handle_value(value)
1547
 
                        except SyntaxError:
1548
 
                            self._handle_error(
1549
 
                                'Parse error in value at line %s.',
1550
 
                                ParseError, infile, cur_index)
1551
 
                            continue
 
1386
                    # extract comment and lists
 
1387
                    try:
 
1388
                        (value, comment) = self._handle_value(value)
 
1389
                    except SyntaxError:
 
1390
                        self._handle_error(
 
1391
                            'Parse error in value at line %s.',
 
1392
                            ParseError, infile, cur_index)
 
1393
                        continue
1552
1394
                #
 
1395
##                print >> sys.stderr, sline
1553
1396
                key = self._unquote(key)
1554
 
                if this_section.has_key(key):
 
1397
                if key in this_section:
1555
1398
                    self._handle_error(
1556
1399
                        'Duplicate keyword name at line %s.',
1557
1400
                        DuplicateError, infile, cur_index)
1558
1401
                    continue
1559
 
                # add the key.
1560
 
                # we set unrepr because if we have got this far we will never
1561
 
                # be creating a new section
1562
 
                this_section.__setitem__(key, value, unrepr=True)
 
1402
                # add the key
 
1403
##                print >> sys.stderr, this_section.name
 
1404
                this_section[key] = value
1563
1405
                this_section.inline_comments[key] = comment
1564
1406
                this_section.comments[key] = comment_list
 
1407
##                print >> sys.stderr, key, this_section[key]
 
1408
##                if this_section.name is not None:
 
1409
##                    print >> sys.stderr, this_section
 
1410
##                    print >> sys.stderr, this_section.parent
 
1411
##                    print >> sys.stderr, this_section.parent[this_section.name]
1565
1412
                continue
1566
 
        #
 
1413
            #
 
1414
            # it neither matched as a keyword
 
1415
            # or a section marker
 
1416
            self._handle_error(
 
1417
                'Invalid line at line "%s".',
 
1418
                ParseError, infile, cur_index)
1567
1419
        if self.indent_type is None:
1568
1420
            # no indentation used, set the type accordingly
1569
1421
            self.indent_type = ''
1570
 
        #
1571
 
        if self._terminated:
1572
 
            comment_list.append('')
1573
1422
        # preserve the final comment
1574
1423
        if not self and not self.initial_comment:
1575
1424
            self.initial_comment = comment_list
1576
 
        elif not reset_comment:
 
1425
        else:
1577
1426
            self.final_comment = comment_list
1578
 
        self.list_values = temp_list_values
1579
1427
 
1580
1428
    def _match_depth(self, sect, depth):
1581
1429
        """
1603
1451
        The error will have occured at ``cur_index``
1604
1452
        """
1605
1453
        line = infile[cur_index]
1606
 
        cur_index += 1
1607
1454
        message = text % cur_index
1608
1455
        error = ErrorClass(message, cur_index, line)
1609
1456
        if self.raise_errors:
1634
1481
        
1635
1482
        If ``list_values=False`` then the value is only quoted if it contains
1636
1483
        a ``\n`` (is multiline).
1637
 
        
1638
 
        If ``write_empty_values`` is set, and the value is an empty string, it
1639
 
        won't be quoted.
1640
1484
        """
1641
 
        if multiline and self.write_empty_values and value == '':
1642
 
            # Only if multiline is set, so that it is used for values not
1643
 
            # keys, and not values that are part of a list
1644
 
            return ''
1645
 
        if multiline and isinstance(value, (list, tuple)):
 
1485
        if isinstance(value, (list, tuple)):
1646
1486
            if not value:
1647
1487
                return ','
1648
1488
            elif len(value) == 1:
1699
1539
        """
1700
1540
        Given a value string, unquote, remove comment,
1701
1541
        handle lists. (including empty and single member lists)
 
1542
        
 
1543
        Testing list values.
 
1544
        
 
1545
        >>> testconfig3 = '''
 
1546
        ... a = ,
 
1547
        ... b = test,
 
1548
        ... c = test1, test2   , test3
 
1549
        ... d = test1, test2, test3,
 
1550
        ... '''
 
1551
        >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
 
1552
        >>> d['a'] == []
 
1553
        1
 
1554
        >>> d['b'] == ['test']
 
1555
        1
 
1556
        >>> d['c'] == ['test1', 'test2', 'test3']
 
1557
        1
 
1558
        >>> d['d'] == ['test1', 'test2', 'test3']
 
1559
        1
 
1560
        
 
1561
        Testing with list values off.
 
1562
        
 
1563
        >>> e = ConfigObj(
 
1564
        ...     testconfig3.split('\\n'),
 
1565
        ...     raise_errors=True,
 
1566
        ...     list_values=False)
 
1567
        >>> e['a'] == ','
 
1568
        1
 
1569
        >>> e['b'] == 'test,'
 
1570
        1
 
1571
        >>> e['c'] == 'test1, test2   , test3'
 
1572
        1
 
1573
        >>> e['d'] == 'test1, test2, test3,'
 
1574
        1
 
1575
        
 
1576
        Testing creating from a dictionary.
 
1577
        
 
1578
        >>> f = {
 
1579
        ...     'key1': 'val1',
 
1580
        ...     'key2': 'val2',
 
1581
        ...     'section 1': {
 
1582
        ...         'key1': 'val1',
 
1583
        ...         'key2': 'val2',
 
1584
        ...         'section 1b': {
 
1585
        ...             'key1': 'val1',
 
1586
        ...             'key2': 'val2',
 
1587
        ...         },
 
1588
        ...     },
 
1589
        ...     'section 2': {
 
1590
        ...         'key1': 'val1',
 
1591
        ...         'key2': 'val2',
 
1592
        ...         'section 2b': {
 
1593
        ...             'key1': 'val1',
 
1594
        ...             'key2': 'val2',
 
1595
        ...         },
 
1596
        ...     },
 
1597
        ...      'key3': 'val3',
 
1598
        ... }
 
1599
        >>> g = ConfigObj(f)
 
1600
        >>> f == g
 
1601
        1
 
1602
        
 
1603
        Testing we correctly detect badly built list values (4 of them).
 
1604
        
 
1605
        >>> testconfig4 = '''
 
1606
        ... config = 3,4,,
 
1607
        ... test = 3,,4
 
1608
        ... fish = ,,
 
1609
        ... dummy = ,,hello, goodbye
 
1610
        ... '''
 
1611
        >>> try:
 
1612
        ...     ConfigObj(testconfig4.split('\\n'))
 
1613
        ... except ConfigObjError, e:
 
1614
        ...     len(e.errors)
 
1615
        4
 
1616
        
 
1617
        Testing we correctly detect badly quoted values (4 of them).
 
1618
        
 
1619
        >>> testconfig5 = '''
 
1620
        ... config = "hello   # comment
 
1621
        ... test = 'goodbye
 
1622
        ... fish = 'goodbye   # comment
 
1623
        ... dummy = "hello again
 
1624
        ... '''
 
1625
        >>> try:
 
1626
        ...     ConfigObj(testconfig5.split('\\n'))
 
1627
        ... except ConfigObjError, e:
 
1628
        ...     len(e.errors)
 
1629
        4
1702
1630
        """
1703
1631
        # do we look for lists in values ?
1704
1632
        if not self.list_values:
1705
1633
            mat = self._nolistvalue.match(value)
1706
1634
            if mat is None:
1707
1635
                raise SyntaxError
 
1636
            (value, comment) = mat.groups()
1708
1637
            # NOTE: we don't unquote here
1709
 
            return mat.groups()
1710
 
        #
 
1638
            return (value, comment)
1711
1639
        mat = self._valueexp.match(value)
1712
1640
        if mat is None:
1713
1641
            # the value is badly constructed, probably badly quoted,
1723
1651
            # the single comma - meaning an empty list
1724
1652
            return ([], comment)
1725
1653
        if single is not None:
1726
 
            # handle empty values
1727
 
            if list_values and not single:
1728
 
                # FIXME: the '' is a workaround because our regex now matches
1729
 
                #   '' at the end of a list if it has a trailing comma
1730
 
                single = None
1731
 
            else:
1732
 
                single = single or '""'
1733
 
                single = self._unquote(single)
 
1654
            single = self._unquote(single)
1734
1655
        if list_values == '':
1735
1656
            # not a list value
1736
1657
            return (single, comment)
1741
1662
        return (the_list, comment)
1742
1663
 
1743
1664
    def _multiline(self, value, infile, cur_index, maxline):
1744
 
        """Extract the value, where we are in a multiline situation."""
 
1665
        """
 
1666
        Extract the value, where we are in a multiline situation
 
1667
        
 
1668
        Testing multiline values.
 
1669
        
 
1670
        >>> i == {
 
1671
        ...     'name4': ' another single line value ',
 
1672
        ...     'multi section': {
 
1673
        ...         'name4': '\\n        Well, this is a\\n        multiline '
 
1674
        ...             'value\\n        ',
 
1675
        ...         'name2': '\\n        Well, this is a\\n        multiline '
 
1676
        ...             'value\\n        ',
 
1677
        ...         'name3': '\\n        Well, this is a\\n        multiline '
 
1678
        ...             'value\\n        ',
 
1679
        ...         'name1': '\\n        Well, this is a\\n        multiline '
 
1680
        ...             'value\\n        ',
 
1681
        ...     },
 
1682
        ...     'name2': ' another single line value ',
 
1683
        ...     'name3': ' a single line value ',
 
1684
        ...     'name1': ' a single line value ',
 
1685
        ... }
 
1686
        1
 
1687
        """
1745
1688
        quot = value[:3]
1746
1689
        newvalue = value[3:]
1747
1690
        single_line = self._triple_quote[quot][0]
1776
1719
 
1777
1720
    def _handle_configspec(self, configspec):
1778
1721
        """Parse the configspec."""
1779
 
        # FIXME: Should we check that the configspec was created with the 
1780
 
        #   correct settings ? (i.e. ``list_values=False``)
1781
 
        if not isinstance(configspec, ConfigObj):
1782
 
            try:
1783
 
                configspec = ConfigObj(
1784
 
                    configspec,
1785
 
                    raise_errors=True,
1786
 
                    file_error=True,
1787
 
                    list_values=False)
1788
 
            except ConfigObjError, e:
1789
 
                # FIXME: Should these errors have a reference
1790
 
                # to the already parsed ConfigObj ?
1791
 
                raise ConfigspecError('Parsing configspec failed: %s' % e)
1792
 
            except IOError, e:
1793
 
                raise IOError('Reading configspec failed: %s' % e)
 
1722
        try:
 
1723
            configspec = ConfigObj(
 
1724
                configspec,
 
1725
                raise_errors=True,
 
1726
                file_error=True,
 
1727
                list_values=False)
 
1728
        except ConfigObjError, e:
 
1729
            # FIXME: Should these errors have a reference
 
1730
            # to the already parsed ConfigObj ?
 
1731
            raise ConfigspecError('Parsing configspec failed: %s' % e)
 
1732
        except IOError, e:
 
1733
            raise IOError('Reading configspec failed: %s' % e)
1794
1734
        self._set_configspec_value(configspec, self)
1795
1735
 
1796
1736
    def _set_configspec_value(self, configspec, section):
1800
1740
            if len(configspec.sections) > 1:
1801
1741
                # FIXME: can we supply any useful information here ?
1802
1742
                raise RepeatSectionError
1803
 
        if hasattr(configspec, 'initial_comment'):
1804
 
            section._configspec_initial_comment = configspec.initial_comment
1805
 
            section._configspec_final_comment = configspec.final_comment
1806
 
            section._configspec_encoding = configspec.encoding
1807
 
            section._configspec_BOM = configspec.BOM
1808
 
            section._configspec_newlines = configspec.newlines
1809
 
            section._configspec_indent_type = configspec.indent_type
1810
1743
        for entry in configspec.scalars:
1811
 
            section._configspec_comments[entry] = configspec.comments[entry]
1812
 
            section._configspec_inline_comments[entry] = (
1813
 
                configspec.inline_comments[entry])
1814
1744
            section.configspec[entry] = configspec[entry]
1815
 
            section._order.append(entry)
1816
1745
        for entry in configspec.sections:
1817
1746
            if entry == '__many__':
1818
1747
                continue
1819
 
            section._cs_section_comments[entry] = configspec.comments[entry]
1820
 
            section._cs_section_inline_comments[entry] = (
1821
 
                configspec.inline_comments[entry])
1822
 
            if not section.has_key(entry):
 
1748
            if entry not in section:
1823
1749
                section[entry] = {}
1824
1750
            self._set_configspec_value(configspec[entry], section[entry])
1825
1751
 
1850
1776
        #
1851
1777
        section.configspec = scalars
1852
1778
        for entry in sections:
1853
 
            if not section.has_key(entry):
 
1779
            if entry not in section:
1854
1780
                section[entry] = {}
1855
1781
            self._handle_repeat(section[entry], sections[entry])
1856
1782
 
1857
1783
    def _write_line(self, indent_string, entry, this_entry, comment):
1858
1784
        """Write an individual line, for the write method"""
1859
1785
        # NOTE: the calls to self._quote here handles non-StringType values.
1860
 
        if not self.unrepr:
1861
 
            val = self._decode_element(self._quote(this_entry))
1862
 
        else:
1863
 
            val = repr(this_entry)
1864
1786
        return '%s%s%s%s%s' % (
1865
1787
            indent_string,
1866
1788
            self._decode_element(self._quote(entry, multiline=False)),
1867
1789
            self._a_to_u(' = '),
1868
 
            val,
 
1790
            self._decode_element(self._quote(this_entry)),
1869
1791
            self._decode_element(comment))
1870
1792
 
1871
1793
    def _write_marker(self, indent_string, depth, entry, comment):
1878
1800
            self._decode_element(comment))
1879
1801
 
1880
1802
    def _handle_comment(self, comment):
1881
 
        """Deal with a comment."""
 
1803
        """
 
1804
        Deal with a comment.
 
1805
        
 
1806
        >>> filename = a.filename
 
1807
        >>> a.filename = None
 
1808
        >>> values = a.write()
 
1809
        >>> index = 0
 
1810
        >>> while index < 23:
 
1811
        ...     index += 1
 
1812
        ...     line = values[index-1]
 
1813
        ...     assert line.endswith('# comment ' + str(index))
 
1814
        >>> a.filename = filename
 
1815
        
 
1816
        >>> start_comment = ['# Initial Comment', '', '#']
 
1817
        >>> end_comment = ['', '#', '# Final Comment']
 
1818
        >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
 
1819
        >>> nc = ConfigObj(newconfig)
 
1820
        >>> nc.initial_comment
 
1821
        ['# Initial Comment', '', '#']
 
1822
        >>> nc.final_comment
 
1823
        ['', '#', '# Final Comment']
 
1824
        >>> nc.initial_comment == start_comment
 
1825
        1
 
1826
        >>> nc.final_comment == end_comment
 
1827
        1
 
1828
        """
1882
1829
        if not comment:
1883
1830
            return ''
1884
 
        start = self.indent_type
 
1831
        if self.indent_type == '\t':
 
1832
            start = self._a_to_u('\t')
 
1833
        else:
 
1834
            start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1885
1835
        if not comment.startswith('#'):
1886
 
            start += self._a_to_u(' # ')
 
1836
            start += _a_to_u('# ')
1887
1837
        return (start + comment)
1888
1838
 
 
1839
    def _compute_indent_string(self, depth):
 
1840
        """
 
1841
        Compute the indent string, according to current indent_type and depth
 
1842
        """
 
1843
        if self.indent_type == '':
 
1844
            # no indentation at all
 
1845
            return ''
 
1846
        if self.indent_type == '\t':
 
1847
            return '\t' * depth
 
1848
        if self.indent_type == ' ':
 
1849
            return ' ' * NUM_INDENT_SPACES * depth
 
1850
        raise SyntaxError
 
1851
 
1889
1852
    # Public methods
1890
1853
 
1891
1854
    def write(self, outfile=None, section=None):
1900
1863
        >>> a.filename = filename
1901
1864
        >>> a == ConfigObj('test.ini', raise_errors=True)
1902
1865
        1
 
1866
        >>> os.remove('test.ini')
 
1867
        >>> b.filename = 'test.ini'
 
1868
        >>> b.write()
 
1869
        >>> b == ConfigObj('test.ini', raise_errors=True)
 
1870
        1
 
1871
        >>> os.remove('test.ini')
 
1872
        >>> i.filename = 'test.ini'
 
1873
        >>> i.write()
 
1874
        >>> i == ConfigObj('test.ini', raise_errors=True)
 
1875
        1
 
1876
        >>> os.remove('test.ini')
 
1877
        >>> a = ConfigObj()
 
1878
        >>> a['DEFAULT'] = {'a' : 'fish'}
 
1879
        >>> a['a'] = '%(a)s'
 
1880
        >>> a.write()
 
1881
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
1903
1882
        """
1904
1883
        if self.indent_type is None:
1905
1884
            # this can be true if initialised from a dictionary
1919
1898
                    line = csp + line
1920
1899
                out.append(line)
1921
1900
        #
1922
 
        indent_string = self.indent_type * section.depth
 
1901
        indent_string = self._a_to_u(
 
1902
            self._compute_indent_string(section.depth))
1923
1903
        for entry in (section.scalars + section.sections):
1924
1904
            if entry in section.defaults:
1925
1905
                # don't write out default values
1985
1965
        if outfile is not None:
1986
1966
            outfile.write(output)
1987
1967
        else:
1988
 
            h = open(self.filename, 'wb')
 
1968
            h = open(self.filename, 'w')
1989
1969
            h.write(output)
1990
1970
            h.close()
1991
1971
 
1992
 
    def validate(self, validator, preserve_errors=False, copy=False,
1993
 
        section=None):
 
1972
    def validate(self, validator, preserve_errors=False, section=None):
1994
1973
        """
1995
1974
        Test the ConfigObj against a configspec.
1996
1975
        
2024
2003
        You can then use the ``flatten_errors`` function to turn your nested
2025
2004
        results dictionary into a flattened list of failures - useful for
2026
2005
        displaying meaningful error messages.
 
2006
        
 
2007
        >>> try:
 
2008
        ...     from validate import Validator
 
2009
        ... except ImportError:
 
2010
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
 
2011
        ... else:
 
2012
        ...     config = '''
 
2013
        ...     test1=40
 
2014
        ...     test2=hello
 
2015
        ...     test3=3
 
2016
        ...     test4=5.0
 
2017
        ...     [section]
 
2018
        ...         test1=40
 
2019
        ...         test2=hello
 
2020
        ...         test3=3
 
2021
        ...         test4=5.0
 
2022
        ...         [[sub section]]
 
2023
        ...             test1=40
 
2024
        ...             test2=hello
 
2025
        ...             test3=3
 
2026
        ...             test4=5.0
 
2027
        ... '''.split('\\n')
 
2028
        ...     configspec = '''
 
2029
        ...     test1= integer(30,50)
 
2030
        ...     test2= string
 
2031
        ...     test3=integer
 
2032
        ...     test4=float(6.0)
 
2033
        ...     [section ]
 
2034
        ...         test1=integer(30,50)
 
2035
        ...         test2=string
 
2036
        ...         test3=integer
 
2037
        ...         test4=float(6.0)
 
2038
        ...         [[sub section]]
 
2039
        ...             test1=integer(30,50)
 
2040
        ...             test2=string
 
2041
        ...             test3=integer
 
2042
        ...             test4=float(6.0)
 
2043
        ...     '''.split('\\n')
 
2044
        ...     val = Validator()
 
2045
        ...     c1 = ConfigObj(config, configspec=configspec)
 
2046
        ...     test = c1.validate(val)
 
2047
        ...     test == {
 
2048
        ...         'test1': True,
 
2049
        ...         'test2': True,
 
2050
        ...         'test3': True,
 
2051
        ...         'test4': False,
 
2052
        ...         'section': {
 
2053
        ...             'test1': True,
 
2054
        ...             'test2': True,
 
2055
        ...             'test3': True,
 
2056
        ...             'test4': False,
 
2057
        ...             'sub section': {
 
2058
        ...                 'test1': True,
 
2059
        ...                 'test2': True,
 
2060
        ...                 'test3': True,
 
2061
        ...                 'test4': False,
 
2062
        ...             },
 
2063
        ...         },
 
2064
        ...     }
 
2065
        1
 
2066
        >>> val.check(c1.configspec['test4'], c1['test4'])
 
2067
        Traceback (most recent call last):
 
2068
        VdtValueTooSmallError: the value "5.0" is too small.
 
2069
        
 
2070
        >>> val_test_config = '''
 
2071
        ...     key = 0
 
2072
        ...     key2 = 1.1
 
2073
        ...     [section]
 
2074
        ...     key = some text
 
2075
        ...     key2 = 1.1, 3.0, 17, 6.8
 
2076
        ...         [[sub-section]]
 
2077
        ...         key = option1
 
2078
        ...         key2 = True'''.split('\\n')
 
2079
        >>> val_test_configspec = '''
 
2080
        ...     key = integer
 
2081
        ...     key2 = float
 
2082
        ...     [section]
 
2083
        ...     key = string
 
2084
        ...     key2 = float_list(4)
 
2085
        ...        [[sub-section]]
 
2086
        ...        key = option(option1, option2)
 
2087
        ...        key2 = boolean'''.split('\\n')
 
2088
        >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
 
2089
        >>> val_test.validate(val)
 
2090
        1
 
2091
        >>> val_test['key'] = 'text not a digit'
 
2092
        >>> val_res = val_test.validate(val)
 
2093
        >>> val_res == {'key2': True, 'section': True, 'key': False}
 
2094
        1
 
2095
        >>> configspec = '''
 
2096
        ...     test1=integer(30,50, default=40)
 
2097
        ...     test2=string(default="hello")
 
2098
        ...     test3=integer(default=3)
 
2099
        ...     test4=float(6.0, default=6.0)
 
2100
        ...     [section ]
 
2101
        ...         test1=integer(30,50, default=40)
 
2102
        ...         test2=string(default="hello")
 
2103
        ...         test3=integer(default=3)
 
2104
        ...         test4=float(6.0, default=6.0)
 
2105
        ...         [[sub section]]
 
2106
        ...             test1=integer(30,50, default=40)
 
2107
        ...             test2=string(default="hello")
 
2108
        ...             test3=integer(default=3)
 
2109
        ...             test4=float(6.0, default=6.0)
 
2110
        ...     '''.split('\\n')
 
2111
        >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
 
2112
        >>> default_test
 
2113
        {'test1': '30', 'section': {'sub section': {}}}
 
2114
        >>> default_test.validate(val)
 
2115
        1
 
2116
        >>> default_test == {
 
2117
        ...     'test1': 30,
 
2118
        ...     'test2': 'hello',
 
2119
        ...     'test3': 3,
 
2120
        ...     'test4': 6.0,
 
2121
        ...     'section': {
 
2122
        ...         'test1': 40,
 
2123
        ...         'test2': 'hello',
 
2124
        ...         'test3': 3,
 
2125
        ...         'test4': 6.0,
 
2126
        ...         'sub section': {
 
2127
        ...             'test1': 40,
 
2128
        ...             'test3': 3,
 
2129
        ...             'test2': 'hello',
 
2130
        ...             'test4': 6.0,
 
2131
        ...         },
 
2132
        ...     },
 
2133
        ... }
 
2134
        1
 
2135
        
 
2136
        Now testing with repeated sections : BIG TEST
 
2137
        
 
2138
        >>> repeated_1 = '''
 
2139
        ... [dogs]
 
2140
        ...     [[__many__]] # spec for a dog
 
2141
        ...         fleas = boolean(default=True)
 
2142
        ...         tail = option(long, short, default=long)
 
2143
        ...         name = string(default=rover)
 
2144
        ...         [[[__many__]]]  # spec for a puppy
 
2145
        ...             name = string(default="son of rover")
 
2146
        ...             age = float(default=0.0)
 
2147
        ... [cats]
 
2148
        ...     [[__many__]] # spec for a cat
 
2149
        ...         fleas = boolean(default=True)
 
2150
        ...         tail = option(long, short, default=short)
 
2151
        ...         name = string(default=pussy)
 
2152
        ...         [[[__many__]]] # spec for a kitten
 
2153
        ...             name = string(default="son of pussy")
 
2154
        ...             age = float(default=0.0)
 
2155
        ...         '''.split('\\n')
 
2156
        >>> repeated_2 = '''
 
2157
        ... [dogs]
 
2158
        ... 
 
2159
        ...     # blank dogs with puppies
 
2160
        ...     # should be filled in by the configspec
 
2161
        ...     [[dog1]]
 
2162
        ...         [[[puppy1]]]
 
2163
        ...         [[[puppy2]]]
 
2164
        ...         [[[puppy3]]]
 
2165
        ...     [[dog2]]
 
2166
        ...         [[[puppy1]]]
 
2167
        ...         [[[puppy2]]]
 
2168
        ...         [[[puppy3]]]
 
2169
        ...     [[dog3]]
 
2170
        ...         [[[puppy1]]]
 
2171
        ...         [[[puppy2]]]
 
2172
        ...         [[[puppy3]]]
 
2173
        ... [cats]
 
2174
        ... 
 
2175
        ...     # blank cats with kittens
 
2176
        ...     # should be filled in by the configspec
 
2177
        ...     [[cat1]]
 
2178
        ...         [[[kitten1]]]
 
2179
        ...         [[[kitten2]]]
 
2180
        ...         [[[kitten3]]]
 
2181
        ...     [[cat2]]
 
2182
        ...         [[[kitten1]]]
 
2183
        ...         [[[kitten2]]]
 
2184
        ...         [[[kitten3]]]
 
2185
        ...     [[cat3]]
 
2186
        ...         [[[kitten1]]]
 
2187
        ...         [[[kitten2]]]
 
2188
        ...         [[[kitten3]]]
 
2189
        ... '''.split('\\n')
 
2190
        >>> repeated_3 = '''
 
2191
        ... [dogs]
 
2192
        ... 
 
2193
        ...     [[dog1]]
 
2194
        ...     [[dog2]]
 
2195
        ...     [[dog3]]
 
2196
        ... [cats]
 
2197
        ... 
 
2198
        ...     [[cat1]]
 
2199
        ...     [[cat2]]
 
2200
        ...     [[cat3]]
 
2201
        ... '''.split('\\n')
 
2202
        >>> repeated_4 = '''
 
2203
        ... [__many__]
 
2204
        ... 
 
2205
        ...     name = string(default=Michael)
 
2206
        ...     age = float(default=0.0)
 
2207
        ...     sex = option(m, f, default=m)
 
2208
        ... '''.split('\\n')
 
2209
        >>> repeated_5 = '''
 
2210
        ... [cats]
 
2211
        ... [[__many__]]
 
2212
        ...     fleas = boolean(default=True)
 
2213
        ...     tail = option(long, short, default=short)
 
2214
        ...     name = string(default=pussy)
 
2215
        ...     [[[description]]]
 
2216
        ...         height = float(default=3.3)
 
2217
        ...         weight = float(default=6)
 
2218
        ...         [[[[coat]]]]
 
2219
        ...             fur = option(black, grey, brown, "tortoise shell", default=black)
 
2220
        ...             condition = integer(0,10, default=5)
 
2221
        ... '''.split('\\n')
 
2222
        >>> from validate import Validator
 
2223
        >>> val= Validator()
 
2224
        >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
 
2225
        >>> repeater.validate(val)
 
2226
        1
 
2227
        >>> repeater == {
 
2228
        ...     'dogs': {
 
2229
        ...         'dog1': {
 
2230
        ...             'fleas': True,
 
2231
        ...             'tail': 'long',
 
2232
        ...             'name': 'rover',
 
2233
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2234
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2235
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2236
        ...         },
 
2237
        ...         'dog2': {
 
2238
        ...             'fleas': True,
 
2239
        ...             'tail': 'long',
 
2240
        ...             'name': 'rover',
 
2241
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2242
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2243
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2244
        ...         },
 
2245
        ...         'dog3': {
 
2246
        ...             'fleas': True,
 
2247
        ...             'tail': 'long',
 
2248
        ...             'name': 'rover',
 
2249
        ...             'puppy1': {'name': 'son of rover', 'age': 0.0},
 
2250
        ...             'puppy2': {'name': 'son of rover', 'age': 0.0},
 
2251
        ...             'puppy3': {'name': 'son of rover', 'age': 0.0},
 
2252
        ...         },
 
2253
        ...     },
 
2254
        ...     'cats': {
 
2255
        ...         'cat1': {
 
2256
        ...             'fleas': True,
 
2257
        ...             'tail': 'short',
 
2258
        ...             'name': 'pussy',
 
2259
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2260
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2261
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2262
        ...         },
 
2263
        ...         'cat2': {
 
2264
        ...             'fleas': True,
 
2265
        ...             'tail': 'short',
 
2266
        ...             'name': 'pussy',
 
2267
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2268
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2269
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2270
        ...         },
 
2271
        ...         'cat3': {
 
2272
        ...             'fleas': True,
 
2273
        ...             'tail': 'short',
 
2274
        ...             'name': 'pussy',
 
2275
        ...             'kitten1': {'name': 'son of pussy', 'age': 0.0},
 
2276
        ...             'kitten2': {'name': 'son of pussy', 'age': 0.0},
 
2277
        ...             'kitten3': {'name': 'son of pussy', 'age': 0.0},
 
2278
        ...         },
 
2279
        ...     },
 
2280
        ... }
 
2281
        1
 
2282
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
 
2283
        >>> repeater.validate(val)
 
2284
        1
 
2285
        >>> repeater == {
 
2286
        ...     'cats': {
 
2287
        ...         'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2288
        ...         'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2289
        ...         'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 
2290
        ...     },
 
2291
        ...     'dogs': {
 
2292
        ...         'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2293
        ...         'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2294
        ...         'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 
2295
        ...     },
 
2296
        ... }
 
2297
        1
 
2298
        >>> repeater = ConfigObj(configspec=repeated_4)
 
2299
        >>> repeater['Michael'] = {}
 
2300
        >>> repeater.validate(val)
 
2301
        1
 
2302
        >>> repeater == {
 
2303
        ...     'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
 
2304
        ... }
 
2305
        1
 
2306
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
 
2307
        >>> repeater == {
 
2308
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
2309
        ...     'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
 
2310
        ... }
 
2311
        1
 
2312
        >>> repeater.validate(val)
 
2313
        1
 
2314
        >>> repeater == {
 
2315
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 
2316
        ...     'cats': {
 
2317
        ...         'cat1': {
 
2318
        ...             'fleas': True,
 
2319
        ...             'tail': 'short',
 
2320
        ...             'name': 'pussy',
 
2321
        ...             'description': {
 
2322
        ...                 'weight': 6.0,
 
2323
        ...                 'height': 3.2999999999999998,
 
2324
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2325
        ...             },
 
2326
        ...         },
 
2327
        ...         'cat2': {
 
2328
        ...             'fleas': True,
 
2329
        ...             'tail': 'short',
 
2330
        ...             'name': 'pussy',
 
2331
        ...             'description': {
 
2332
        ...                 'weight': 6.0,
 
2333
        ...                 'height': 3.2999999999999998,
 
2334
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2335
        ...             },
 
2336
        ...         },
 
2337
        ...         'cat3': {
 
2338
        ...             'fleas': True,
 
2339
        ...             'tail': 'short',
 
2340
        ...             'name': 'pussy',
 
2341
        ...             'description': {
 
2342
        ...                 'weight': 6.0,
 
2343
        ...                 'height': 3.2999999999999998,
 
2344
        ...                 'coat': {'fur': 'black', 'condition': 5},
 
2345
        ...             },
 
2346
        ...         },
 
2347
        ...     },
 
2348
        ... }
 
2349
        1
 
2350
        
 
2351
        Test that interpolation is preserved for validated string values.
 
2352
        Also check that interpolation works in configspecs.
 
2353
        >>> t = ConfigObj()
 
2354
        >>> t['DEFAULT'] = {}
 
2355
        >>> t['DEFAULT']['test'] = 'a'
 
2356
        >>> t['test'] = '%(test)s'
 
2357
        >>> t['test']
 
2358
        'a'
 
2359
        >>> v = Validator()
 
2360
        >>> t.configspec = {'test': 'string'}
 
2361
        >>> t.validate(v)
 
2362
        1
 
2363
        >>> t.interpolation = False
 
2364
        >>> t
 
2365
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 
2366
        >>> specs = [
 
2367
        ...    'interpolated string  = string(default="fuzzy-%(man)s")',
 
2368
        ...    '[DEFAULT]',
 
2369
        ...    'man = wuzzy',
 
2370
        ...    ]
 
2371
        >>> c = ConfigObj(configspec=specs)
 
2372
        >>> c.validate(v)
 
2373
        1
 
2374
        >>> c['interpolated string']
 
2375
        'fuzzy-wuzzy'
 
2376
        
 
2377
        FIXME: Above tests will fail if we couldn't import Validator (the ones
 
2378
        that don't raise errors will produce different output and still fail as
 
2379
        tests)
2027
2380
        """
2028
2381
        if section is None:
2029
2382
            if self.configspec is None:
2034
2387
            section = self
2035
2388
        #
2036
2389
        spec_section = section.configspec
2037
 
        if copy and hasattr(section, '_configspec_initial_comment'):
2038
 
            section.initial_comment = section._configspec_initial_comment
2039
 
            section.final_comment = section._configspec_final_comment
2040
 
            section.encoding = section._configspec_encoding
2041
 
            section.BOM = section._configspec_BOM
2042
 
            section.newlines = section._configspec_newlines
2043
 
            section.indent_type = section._configspec_indent_type
2044
2390
        if '__many__' in section.configspec:
2045
2391
            many = spec_section['__many__']
2046
2392
            # dynamically assign the configspecs
2051
2397
        out = {}
2052
2398
        ret_true = True
2053
2399
        ret_false = True
2054
 
        order = [k for k in section._order if k in spec_section]
2055
 
        order += [k for k in spec_section if k not in order]
2056
 
        for entry in order:
 
2400
        for entry in spec_section:
2057
2401
            if entry == '__many__':
2058
2402
                continue
2059
2403
            if (not entry in section.scalars) or (entry in section.defaults):
2061
2405
                # or entries from defaults
2062
2406
                missing = True
2063
2407
                val = None
2064
 
                if copy and not entry in section.scalars:
2065
 
                    # copy comments
2066
 
                    section.comments[entry] = (
2067
 
                        section._configspec_comments.get(entry, []))
2068
 
                    section.inline_comments[entry] = (
2069
 
                        section._configspec_inline_comments.get(entry, ''))
2070
 
                #
2071
2408
            else:
2072
2409
                missing = False
2073
2410
                val = section[entry]
2101
2438
                            check = self._str(check)
2102
2439
                    if (check != val) or missing:
2103
2440
                        section[entry] = check
2104
 
                if not copy and missing and entry not in section.defaults:
 
2441
                if missing and entry not in section.defaults:
2105
2442
                    section.defaults.append(entry)
2106
2443
        #
2107
 
        # Missing sections will have been created as empty ones when the
2108
 
        # configspec was read.
 
2444
        # FIXME: Will this miss missing sections ?
2109
2445
        for entry in section.sections:
2110
 
            # FIXME: this means DEFAULT is not copied in copy mode
2111
2446
            if section is self and entry == 'DEFAULT':
2112
2447
                continue
2113
 
            if copy:
2114
 
                section.comments[entry] = section._cs_section_comments[entry]
2115
 
                section.inline_comments[entry] = (
2116
 
                    section._cs_section_inline_comments[entry])
2117
2448
            check = self.validate(validator, preserve_errors=preserve_errors,
2118
 
                copy=copy, section=section[entry])
 
2449
                section=section[entry])
2119
2450
            out[entry] = check
2120
2451
            if check == False:
2121
2452
                ret_true = False
2142
2473
    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2143
2474
    members are present, or a dictionary with True/False meaning
2144
2475
    present/missing. (Whole missing sections will be replaced with ``False``)
 
2476
    
 
2477
    >>> val = SimpleVal()
 
2478
    >>> config = '''
 
2479
    ... test1=40
 
2480
    ... test2=hello
 
2481
    ... test3=3
 
2482
    ... test4=5.0
 
2483
    ... [section]
 
2484
    ... test1=40
 
2485
    ... test2=hello
 
2486
    ... test3=3
 
2487
    ... test4=5.0
 
2488
    ...     [[sub section]]
 
2489
    ...     test1=40
 
2490
    ...     test2=hello
 
2491
    ...     test3=3
 
2492
    ...     test4=5.0
 
2493
    ... '''.split('\\n')
 
2494
    >>> configspec = '''
 
2495
    ... test1=''
 
2496
    ... test2=''
 
2497
    ... test3=''
 
2498
    ... test4=''
 
2499
    ... [section]
 
2500
    ... test1=''
 
2501
    ... test2=''
 
2502
    ... test3=''
 
2503
    ... test4=''
 
2504
    ...     [[sub section]]
 
2505
    ...     test1=''
 
2506
    ...     test2=''
 
2507
    ...     test3=''
 
2508
    ...     test4=''
 
2509
    ... '''.split('\\n')
 
2510
    >>> o = ConfigObj(config, configspec=configspec)
 
2511
    >>> o.validate(val)
 
2512
    1
 
2513
    >>> o = ConfigObj(configspec=configspec)
 
2514
    >>> o.validate(val)
 
2515
    0
2145
2516
    """
2146
2517
    
2147
2518
    def __init__(self):
2186
2557
    
2187
2558
    For example *The value "3" is of the wrong type*.
2188
2559
    
 
2560
    # FIXME: is the ordering of the output arbitrary ?
2189
2561
    >>> import validate
2190
2562
    >>> vtor = validate.Validator()
2191
2563
    >>> my_ini = '''
2276
2648
    #
2277
2649
    return results
2278
2650
 
2279
 
"""*A programming language is a medium of expression.* - Paul Graham"""
 
2651
 
 
2652
# FIXME: test error code for badly built multiline values
 
2653
# FIXME: test handling of StringIO
 
2654
# FIXME: test interpolation with writing
 
2655
 
 
2656
def _doctest():
 
2657
    """
 
2658
    Dummy function to hold some of the doctests.
 
2659
    
 
2660
    >>> a.depth
 
2661
    0
 
2662
    >>> a == {
 
2663
    ...     'key2': 'val',
 
2664
    ...     'key1': 'val',
 
2665
    ...     'lev1c': {
 
2666
    ...         'lev2c': {
 
2667
    ...             'lev3c': {
 
2668
    ...                 'key1': 'val',
 
2669
    ...             },
 
2670
    ...         },
 
2671
    ...     },
 
2672
    ...     'lev1b': {
 
2673
    ...         'key2': 'val',
 
2674
    ...         'key1': 'val',
 
2675
    ...         'lev2ba': {
 
2676
    ...             'key1': 'val',
 
2677
    ...         },
 
2678
    ...         'lev2bb': {
 
2679
    ...             'key1': 'val',
 
2680
    ...         },
 
2681
    ...     },
 
2682
    ...     'lev1a': {
 
2683
    ...         'key2': 'val',
 
2684
    ...         'key1': 'val',
 
2685
    ...     },
 
2686
    ... }
 
2687
    1
 
2688
    >>> b.depth
 
2689
    0
 
2690
    >>> b == {
 
2691
    ...     'key3': 'val3',
 
2692
    ...     'key2': 'val2',
 
2693
    ...     'key1': 'val1',
 
2694
    ...     'section 1': {
 
2695
    ...         'keys11': 'val1',
 
2696
    ...         'keys13': 'val3',
 
2697
    ...         'keys12': 'val2',
 
2698
    ...     },
 
2699
    ...     'section 2': {
 
2700
    ...         'section 2 sub 1': {
 
2701
    ...             'fish': '3',
 
2702
    ...     },
 
2703
    ...     'keys21': 'val1',
 
2704
    ...     'keys22': 'val2',
 
2705
    ...     'keys23': 'val3',
 
2706
    ...     },
 
2707
    ... }
 
2708
    1
 
2709
    >>> t = '''
 
2710
    ... 'a' = b # !"$%^&*(),::;'@~#= 33
 
2711
    ... "b" = b #= 6, 33
 
2712
    ... ''' .split('\\n')
 
2713
    >>> t2 = ConfigObj(t)
 
2714
    >>> assert t2 == {'a': 'b', 'b': 'b'}
 
2715
    >>> t2.inline_comments['b'] = ''
 
2716
    >>> del t2['a']
 
2717
    >>> assert t2.write() == ['','b = b', '']
 
2718
    
 
2719
    # Test ``list_values=False`` stuff
 
2720
    >>> c = '''
 
2721
    ...     key1 = no quotes
 
2722
    ...     key2 = 'single quotes'
 
2723
    ...     key3 = "double quotes"
 
2724
    ...     key4 = "list", 'with', several, "quotes"
 
2725
    ...     '''
 
2726
    >>> cfg = ConfigObj(c.splitlines(), list_values=False)
 
2727
    >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'", 
 
2728
    ... 'key3': '"double quotes"', 
 
2729
    ... 'key4': '"list", \\'with\\', several, "quotes"'
 
2730
    ... }
 
2731
    1
 
2732
    >>> cfg = ConfigObj(list_values=False)
 
2733
    >>> cfg['key1'] = 'Multiline\\nValue'
 
2734
    >>> cfg['key2'] = '''"Value" with 'quotes' !'''
 
2735
    >>> cfg.write()
 
2736
    ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
 
2737
    >>> cfg.list_values = True
 
2738
    >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
 
2739
    ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
 
2740
    1
 
2741
    
 
2742
    Test flatten_errors:
 
2743
    
 
2744
    >>> from validate import Validator, VdtValueTooSmallError
 
2745
    >>> config = '''
 
2746
    ...     test1=40
 
2747
    ...     test2=hello
 
2748
    ...     test3=3
 
2749
    ...     test4=5.0
 
2750
    ...     [section]
 
2751
    ...         test1=40
 
2752
    ...         test2=hello
 
2753
    ...         test3=3
 
2754
    ...         test4=5.0
 
2755
    ...         [[sub section]]
 
2756
    ...             test1=40
 
2757
    ...             test2=hello
 
2758
    ...             test3=3
 
2759
    ...             test4=5.0
 
2760
    ... '''.split('\\n')
 
2761
    >>> configspec = '''
 
2762
    ...     test1= integer(30,50)
 
2763
    ...     test2= string
 
2764
    ...     test3=integer
 
2765
    ...     test4=float(6.0)
 
2766
    ...     [section ]
 
2767
    ...         test1=integer(30,50)
 
2768
    ...         test2=string
 
2769
    ...         test3=integer
 
2770
    ...         test4=float(6.0)
 
2771
    ...         [[sub section]]
 
2772
    ...             test1=integer(30,50)
 
2773
    ...             test2=string
 
2774
    ...             test3=integer
 
2775
    ...             test4=float(6.0)
 
2776
    ...     '''.split('\\n')
 
2777
    >>> val = Validator()
 
2778
    >>> c1 = ConfigObj(config, configspec=configspec)
 
2779
    >>> res = c1.validate(val)
 
2780
    >>> flatten_errors(c1, res) == [([], 'test4', False), (['section', 
 
2781
    ...     'sub section'], 'test4', False), (['section'], 'test4', False)]
 
2782
    True
 
2783
    >>> res = c1.validate(val, preserve_errors=True)
 
2784
    >>> check = flatten_errors(c1, res)
 
2785
    >>> check[0][:2]
 
2786
    ([], 'test4')
 
2787
    >>> check[1][:2]
 
2788
    (['section', 'sub section'], 'test4')
 
2789
    >>> check[2][:2]
 
2790
    (['section'], 'test4')
 
2791
    >>> for entry in check:
 
2792
    ...     isinstance(entry[2], VdtValueTooSmallError)
 
2793
    ...     print str(entry[2])
 
2794
    True
 
2795
    the value "5.0" is too small.
 
2796
    True
 
2797
    the value "5.0" is too small.
 
2798
    True
 
2799
    the value "5.0" is too small.
 
2800
    
 
2801
    Test unicode handling, BOM, write witha file like object and line endings :
 
2802
    >>> u_base = '''
 
2803
    ... # initial comment
 
2804
    ...     # inital comment 2
 
2805
    ... 
 
2806
    ... test1 = some value
 
2807
    ... # comment
 
2808
    ... test2 = another value    # inline comment
 
2809
    ... # section comment
 
2810
    ... [section]    # inline comment
 
2811
    ...     test = test    # another inline comment
 
2812
    ...     test2 = test2
 
2813
    ... 
 
2814
    ... # final comment
 
2815
    ... # final comment2
 
2816
    ... '''
 
2817
    >>> u = u_base.encode('utf_8').splitlines(True)
 
2818
    >>> u[0] = BOM_UTF8 + u[0]
 
2819
    >>> uc = ConfigObj(u)
 
2820
    >>> uc.encoding = None
 
2821
    >>> uc.BOM == True
 
2822
    1
 
2823
    >>> uc == {'test1': 'some value', 'test2': 'another value',
 
2824
    ... 'section': {'test': 'test', 'test2': 'test2'}}
 
2825
    1
 
2826
    >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
 
2827
    >>> uc.BOM
 
2828
    1
 
2829
    >>> isinstance(uc['test1'], unicode)
 
2830
    1
 
2831
    >>> uc.encoding
 
2832
    'utf_8'
 
2833
    >>> uc.newlines
 
2834
    '\\n'
 
2835
    >>> uc['latin1'] = "This costs lot's of "
 
2836
    >>> a_list = uc.write()
 
2837
    >>> len(a_list)
 
2838
    15
 
2839
    >>> isinstance(a_list[0], str)
 
2840
    1
 
2841
    >>> a_list[0].startswith(BOM_UTF8)
 
2842
    1
 
2843
    >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
 
2844
    >>> uc = ConfigObj(u)
 
2845
    >>> uc.newlines
 
2846
    '\\r\\n'
 
2847
    >>> uc.newlines = '\\r'
 
2848
    >>> from cStringIO import StringIO
 
2849
    >>> file_like = StringIO()
 
2850
    >>> uc.write(file_like)
 
2851
    >>> file_like.seek(0)
 
2852
    >>> uc2 = ConfigObj(file_like)
 
2853
    >>> uc2 == uc
 
2854
    1
 
2855
    >>> uc2.filename is None
 
2856
    1
 
2857
    >>> uc2.newlines == '\\r'
 
2858
    1
 
2859
    """
 
2860
 
 
2861
if __name__ == '__main__':
 
2862
    # run the code tests in doctest format
 
2863
    #
 
2864
    testconfig1 = """\
 
2865
    key1= val    # comment 1
 
2866
    key2= val    # comment 2
 
2867
    # comment 3
 
2868
    [lev1a]     # comment 4
 
2869
    key1= val    # comment 5
 
2870
    key2= val    # comment 6
 
2871
    # comment 7
 
2872
    [lev1b]    # comment 8
 
2873
    key1= val    # comment 9
 
2874
    key2= val    # comment 10
 
2875
    # comment 11
 
2876
        [[lev2ba]]    # comment 12
 
2877
        key1= val    # comment 13
 
2878
        # comment 14
 
2879
        [[lev2bb]]    # comment 15
 
2880
        key1= val    # comment 16
 
2881
    # comment 17
 
2882
    [lev1c]    # comment 18
 
2883
    # comment 19
 
2884
        [[lev2c]]    # comment 20
 
2885
        # comment 21
 
2886
            [[[lev3c]]]    # comment 22
 
2887
            key1 = val    # comment 23"""
 
2888
    #
 
2889
    testconfig2 = """\
 
2890
                        key1 = 'val1'
 
2891
                        key2 =   "val2"
 
2892
                        key3 = val3
 
2893
                        ["section 1"] # comment
 
2894
                        keys11 = val1
 
2895
                        keys12 = val2
 
2896
                        keys13 = val3
 
2897
                        [section 2]
 
2898
                        keys21 = val1
 
2899
                        keys22 = val2
 
2900
                        keys23 = val3
 
2901
                        
 
2902
                            [['section 2 sub 1']]
 
2903
                            fish = 3
 
2904
    """
 
2905
    #
 
2906
    testconfig6 = '''
 
2907
    name1 = """ a single line value """ # comment
 
2908
    name2 = \''' another single line value \''' # comment
 
2909
    name3 = """ a single line value """
 
2910
    name4 = \''' another single line value \'''
 
2911
        [ "multi section" ]
 
2912
        name1 = """
 
2913
        Well, this is a
 
2914
        multiline value
 
2915
        """
 
2916
        name2 = \'''
 
2917
        Well, this is a
 
2918
        multiline value
 
2919
        \'''
 
2920
        name3 = """
 
2921
        Well, this is a
 
2922
        multiline value
 
2923
        """     # a comment
 
2924
        name4 = \'''
 
2925
        Well, this is a
 
2926
        multiline value
 
2927
        \'''  # I guess this is a comment too
 
2928
    '''
 
2929
    #
 
2930
    import doctest
 
2931
    m = sys.modules.get('__main__')
 
2932
    globs = m.__dict__.copy()
 
2933
    a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
 
2934
    b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
 
2935
    i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
 
2936
    globs.update({
 
2937
        'INTP_VER': INTP_VER,
 
2938
        'a': a,
 
2939
        'b': b,
 
2940
        'i': i,
 
2941
    })
 
2942
    doctest.testmod(m, globs=globs)
 
2943
 
 
2944
"""
 
2945
    BUGS
 
2946
    ====
 
2947
    
 
2948
    None known.
 
2949
    
 
2950
    TODO
 
2951
    ====
 
2952
    
 
2953
    Better support for configuration from multiple files, including tracking
 
2954
    *where* the original file came from and writing changes to the correct
 
2955
    file.
 
2956
    
 
2957
    
 
2958
    Make ``newline`` an option (as well as an attribute) ?
 
2959
    
 
2960
    ``UTF16`` encoded files, when returned as a list of lines, will have the
 
2961
    BOM at the start of every line. Should this be removed from all but the
 
2962
    first line ?
 
2963
    
 
2964
    Option to set warning type for unicode decode ? (Defaults to strict).
 
2965
    
 
2966
    A method to optionally remove uniform indentation from multiline values.
 
2967
    (do as an example of using ``walk`` - along with string-escape)
 
2968
    
 
2969
    Should the results dictionary from validate be an ordered dictionary if
 
2970
    `odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
 
2971
    
 
2972
    Implement a better ``__repr__`` ? (``ConfigObj({})``)
 
2973
    
 
2974
    Implement some of the sequence methods (which include slicing) from the
 
2975
    newer ``odict`` ?
 
2976
    
 
2977
    INCOMPATIBLE CHANGES
 
2978
    ====================
 
2979
    
 
2980
    (I have removed a lot of needless complications - this list is probably not
 
2981
    conclusive, many option/attribute/method names have changed)
 
2982
    
 
2983
    Case sensitive
 
2984
    
 
2985
    The only valid divider is '='
 
2986
    
 
2987
    We've removed line continuations with '\'
 
2988
    
 
2989
    No recursive lists in values
 
2990
    
 
2991
    No empty section
 
2992
    
 
2993
    No distinction between flatfiles and non flatfiles
 
2994
    
 
2995
    Change in list syntax - use commas to indicate list, not parentheses
 
2996
    (square brackets and parentheses are no longer recognised as lists)
 
2997
    
 
2998
    ';' is no longer valid for comments and no multiline comments
 
2999
    
 
3000
    No attribute access
 
3001
    
 
3002
    We don't allow empty values - have to use '' or ""
 
3003
    
 
3004
    In ConfigObj 3 - setting a non-flatfile member to ``None`` would
 
3005
    initialise it as an empty section.
 
3006
    
 
3007
    The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
 
3008
    replaced by triple quote, multiple line values.
 
3009
    
 
3010
    The ``newline``, ``force_return``, and ``default`` options have gone
 
3011
    
 
3012
    The ``encoding`` and ``backup_encoding`` methods have gone - replaced
 
3013
    with the ``encode`` and ``decode`` methods.
 
3014
    
 
3015
    ``fileerror`` and ``createempty`` options have become ``file_error`` and
 
3016
    ``create_empty``
 
3017
    
 
3018
    Partial configspecs (for specifying the order members should be written
 
3019
    out and which should be present) have gone. The configspec is no longer
 
3020
    used to specify order for the ``write`` method.
 
3021
    
 
3022
    Exceeding the maximum depth of recursion in string interpolation now
 
3023
    raises an error ``InterpolationDepthError``.
 
3024
    
 
3025
    Specifying a value for interpolation which doesn't exist now raises an
 
3026
    error ``MissingInterpolationOption`` (instead of merely being ignored).
 
3027
    
 
3028
    The ``writein`` method has been removed.
 
3029
    
 
3030
    The comments attribute is now a list (``inline_comments`` equates to the
 
3031
    old comments attribute)
 
3032
    
 
3033
    ISSUES
 
3034
    ======
 
3035
    
 
3036
    ``validate`` doesn't report *extra* values or sections.
 
3037
    
 
3038
    You can't have a keyword with the same name as a section (in the same
 
3039
    section). They are both dictionary keys - so they would overlap.
 
3040
    
 
3041
    ConfigObj doesn't quote and unquote values if ``list_values=False``.
 
3042
    This means that leading or trailing whitespace in values will be lost when
 
3043
    writing. (Unless you manually quote).
 
3044
    
 
3045
    Interpolation checks first the 'DEFAULT' subsection of the current
 
3046
    section, next it checks the 'DEFAULT' section of the parent section,
 
3047
    last it checks the 'DEFAULT' section of the main section.
 
3048
    
 
3049
    Logically a 'DEFAULT' section should apply to all subsections of the *same
 
3050
    parent* - this means that checking the 'DEFAULT' subsection in the
 
3051
    *current section* is not necessarily logical ?
 
3052
    
 
3053
    In order to simplify unicode support (which is possibly of limited value
 
3054
    in a config file) I have removed automatic support and added the
 
3055
    ``encode`` and ``decode methods, which can be used to transform keys and
 
3056
    entries. Because the regex looks for specific values on inital parsing
 
3057
    (i.e. the quotes and the equals signs) it can only read ascii compatible
 
3058
    encodings. For unicode use ``UTF8``, which is ASCII compatible.
 
3059
    
 
3060
    Does it matter that we don't support the ':' divider, which is supported
 
3061
    by ``ConfigParser`` ?
 
3062
    
 
3063
    The regular expression correctly removes the value -
 
3064
    ``"'hello', 'goodbye'"`` and then unquote just removes the front and
 
3065
    back quotes (called from ``_handle_value``). What should we do ??
 
3066
    (*ought* to raise exception because it's an invalid value if lists are
 
3067
    off *sigh*. This is not what you want if you want to do your own list
 
3068
    processing - would be *better* in this case not to unquote.)
 
3069
    
 
3070
    String interpolation and validation don't play well together. When
 
3071
    validation changes type it sets the value. This will correctly fetch the
 
3072
    value using interpolation - but then overwrite the interpolation reference.
 
3073
    If the value is unchanged by validation (it's a string) - but other types
 
3074
    will be.
 
3075
    
 
3076
    
 
3077
    List Value Syntax
 
3078
    =================
 
3079
    
 
3080
    List values allow you to specify multiple values for a keyword. This
 
3081
    maps to a list as the resulting Python object when parsed.
 
3082
    
 
3083
    The syntax for lists is easy. A list is a comma separated set of values.
 
3084
    If these values contain quotes, the hash mark, or commas, then the values
 
3085
    can be surrounded by quotes. e.g. : ::
 
3086
    
 
3087
        keyword = value1, 'value 2', "value 3"
 
3088
    
 
3089
    If a value needs to be a list, but only has one member, then you indicate
 
3090
    this with a trailing comma. e.g. : ::
 
3091
    
 
3092
        keyword = "single value",
 
3093
    
 
3094
    If a value needs to be a list, but it has no members, then you indicate
 
3095
    this with a single comma. e.g. : ::
 
3096
    
 
3097
        keyword = ,     # an empty list
 
3098
    
 
3099
    Using triple quotes it will be possible for single values to contain
 
3100
    newlines and *both* single quotes and double quotes. Triple quotes aren't
 
3101
    allowed in list values. This means that the members of list values can't
 
3102
    contain carriage returns (or line feeds :-) or both quote values.
 
3103
      
 
3104
    CHANGELOG
 
3105
    =========
 
3106
    
 
3107
    2006/02/04
 
3108
    ----------
 
3109
    
 
3110
    Removed ``BOM_UTF8`` from ``__all__``.
 
3111
    
 
3112
    The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
 
3113
    *only* ``True`` for the ``UTF16`` encoding.
 
3114
    
 
3115
    File like objects no longer need a ``seek`` attribute.
 
3116
    
 
3117
    ConfigObj no longer keeps a reference to file like objects. Instead the
 
3118
    ``write`` method takes a file like object as an optional argument. (Which
 
3119
    will be used in preference of the ``filename`` attribute if htat exists as
 
3120
    well.)
 
3121
    
 
3122
    Full unicode support added. New options/attributes ``encoding``,
 
3123
    ``default_encoding``.
 
3124
    
 
3125
    utf16 files decoded to unicode.
 
3126
    
 
3127
    If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
 
3128
    written out at the start of the file. (It will normally only be ``True`` if
 
3129
    the utf8 BOM was found when the file was read.)
 
3130
    
 
3131
    File paths are *not* converted to absolute paths, relative paths will
 
3132
    remain relative as the ``filename`` attribute.
 
3133
    
 
3134
    Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
 
3135
    a list of lines.
 
3136
    
 
3137
    2006/01/31
 
3138
    ----------
 
3139
    
 
3140
    Added ``True``, ``False``, and ``enumerate`` if they are not defined.
 
3141
    (``True`` and ``False`` are needed for *early* versions of Python 2.2,
 
3142
    ``enumerate`` is needed for all versions ofPython 2.2)
 
3143
    
 
3144
    Deprecated ``istrue``, replaced it with ``as_bool``.
 
3145
    
 
3146
    Added ``as_int`` and ``as_float``.
 
3147
    
 
3148
    utf8 and utf16 BOM handled in an endian agnostic way.
 
3149
    
 
3150
    2005/12/14
 
3151
    ----------
 
3152
    
 
3153
    Validation no longer done on the 'DEFAULT' section (only in the root
 
3154
    level). This allows interpolation in configspecs.
 
3155
    
 
3156
    Change in validation syntax implemented in validate 0.2.1
 
3157
    
 
3158
    4.1.0
 
3159
    
 
3160
    2005/12/10
 
3161
    ----------
 
3162
    
 
3163
    Added ``merge``, a recursive update.
 
3164
    
 
3165
    Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
 
3166
    example function.
 
3167
    
 
3168
    Thanks to Matthew Brett for suggestions and helping me iron out bugs.
 
3169
    
 
3170
    Fixed bug where a config file is *all* comment, the comment will now be
 
3171
    ``initial_comment`` rather than ``final_comment``.
 
3172
    
 
3173
    2005/12/02
 
3174
    ----------
 
3175
    
 
3176
    Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
 
3177
    
 
3178
    2005/11/04
 
3179
    ----------
 
3180
    
 
3181
    Fixed bug in ``Section.walk`` when transforming names as well as values.
 
3182
    
 
3183
    Added the ``istrue`` method. (Fetches the boolean equivalent of a string
 
3184
    value).
 
3185
    
 
3186
    Fixed ``list_values=False`` - they are now only quoted/unquoted if they
 
3187
    are multiline values.
 
3188
    
 
3189
    List values are written as ``item, item`` rather than ``item,item``.
 
3190
    
 
3191
    4.0.1
 
3192
    
 
3193
    2005/10/09
 
3194
    ----------
 
3195
    
 
3196
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
 
3197
    ``interpolation``).
 
3198
 
 
3199
    4.0.0 Final
 
3200
    
 
3201
    2005/09/16
 
3202
    ----------
 
3203
    
 
3204
    Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
 
3205
    a reference to the new section.
 
3206
    
 
3207
    2005/09/09
 
3208
    ----------
 
3209
    
 
3210
    Removed ``PositionError``.
 
3211
    
 
3212
    Allowed quotes around keys as documented.
 
3213
    
 
3214
    Fixed bug with commas in comments. (matched as a list value)
 
3215
    
 
3216
    Beta 5
 
3217
    
 
3218
    2005/09/07
 
3219
    ----------
 
3220
    
 
3221
    Fixed bug in initialising ConfigObj from a ConfigObj.
 
3222
    
 
3223
    Changed the mailing list address.
 
3224
    
 
3225
    Beta 4
 
3226
    
 
3227
    2005/09/03
 
3228
    ----------
 
3229
    
 
3230
    Fixed bug in ``Section.__delitem__`` oops.
 
3231
    
 
3232
    2005/08/28
 
3233
    ----------
 
3234
    
 
3235
    Interpolation is switched off before writing out files.
 
3236
    
 
3237
    Fixed bug in handling ``StringIO`` instances. (Thanks to report from
 
3238
    "Gustavo Niemeyer" <gustavo@niemeyer.net>)
 
3239
    
 
3240
    Moved the doctests from the ``__init__`` method to a separate function.
 
3241
    (For the sake of IDE calltips).
 
3242
    
 
3243
    Beta 3
 
3244
    
 
3245
    2005/08/26
 
3246
    ----------
 
3247
    
 
3248
    String values unchanged by validation *aren't* reset. This preserves
 
3249
    interpolation in string values.
 
3250
    
 
3251
    2005/08/18
 
3252
    ----------
 
3253
    
 
3254
    None from a default is turned to '' if stringify is off - because setting 
 
3255
    a value to None raises an error.
 
3256
    
 
3257
    Version 4.0.0-beta2
 
3258
    
 
3259
    2005/08/16
 
3260
    ----------
 
3261
    
 
3262
    By Nicola Larosa
 
3263
    
 
3264
    Actually added the RepeatSectionError class ;-)
 
3265
    
 
3266
    2005/08/15
 
3267
    ----------
 
3268
    
 
3269
    If ``stringify`` is off - list values are preserved by the ``validate``
 
3270
    method. (Bugfix)
 
3271
    
 
3272
    2005/08/14
 
3273
    ----------
 
3274
    
 
3275
    By Michael Foord
 
3276
    
 
3277
    Fixed ``simpleVal``.
 
3278
    
 
3279
    Added ``RepeatSectionError`` error if you have additional sections in a
 
3280
    section with a ``__many__`` (repeated) section.
 
3281
    
 
3282
    By Nicola Larosa
 
3283
    
 
3284
    Reworked the ConfigObj._parse, _handle_error and _multiline methods:
 
3285
    mutated the self._infile, self._index and self._maxline attributes into
 
3286
    local variables and method parameters
 
3287
    
 
3288
    Reshaped the ConfigObj._multiline method to better reflect its semantics
 
3289
    
 
3290
    Changed the "default_test" test in ConfigObj.validate to check the fix for
 
3291
    the bug in validate.Validator.check
 
3292
    
 
3293
    2005/08/13
 
3294
    ----------
 
3295
    
 
3296
    By Nicola Larosa
 
3297
    
 
3298
    Updated comments at top
 
3299
    
 
3300
    2005/08/11
 
3301
    ----------
 
3302
    
 
3303
    By Michael Foord
 
3304
    
 
3305
    Implemented repeated sections.
 
3306
    
 
3307
    By Nicola Larosa
 
3308
    
 
3309
    Added test for interpreter version: raises RuntimeError if earlier than
 
3310
    2.2
 
3311
    
 
3312
    2005/08/10
 
3313
    ----------
 
3314
   
 
3315
    By Michael Foord
 
3316
     
 
3317
    Implemented default values in configspecs.
 
3318
    
 
3319
    By Nicola Larosa
 
3320
    
 
3321
    Fixed naked except: clause in validate that was silencing the fact
 
3322
    that Python2.2 does not have dict.pop
 
3323
    
 
3324
    2005/08/08
 
3325
    ----------
 
3326
    
 
3327
    By Michael Foord
 
3328
    
 
3329
    Bug fix causing error if file didn't exist.
 
3330
    
 
3331
    2005/08/07
 
3332
    ----------
 
3333
    
 
3334
    By Nicola Larosa
 
3335
    
 
3336
    Adjusted doctests for Python 2.2.3 compatibility
 
3337
    
 
3338
    2005/08/04
 
3339
    ----------
 
3340
    
 
3341
    By Michael Foord
 
3342
    
 
3343
    Added the inline_comments attribute
 
3344
    
 
3345
    We now preserve and rewrite all comments in the config file
 
3346
    
 
3347
    configspec is now a section attribute
 
3348
    
 
3349
    The validate method changes values in place
 
3350
    
 
3351
    Added InterpolationError
 
3352
    
 
3353
    The errors now have line number, line, and message attributes. This
 
3354
    simplifies error handling
 
3355
    
 
3356
    Added __docformat__
 
3357
    
 
3358
    2005/08/03
 
3359
    ----------
 
3360
    
 
3361
    By Michael Foord
 
3362
    
 
3363
    Fixed bug in Section.pop (now doesn't raise KeyError if a default value
 
3364
    is specified)
 
3365
    
 
3366
    Replaced ``basestring`` with ``types.StringTypes``
 
3367
    
 
3368
    Removed the ``writein`` method
 
3369
    
 
3370
    Added __version__
 
3371
    
 
3372
    2005/07/29
 
3373
    ----------
 
3374
    
 
3375
    By Nicola Larosa
 
3376
    
 
3377
    Indentation in config file is not significant anymore, subsections are
 
3378
    designated by repeating square brackets
 
3379
    
 
3380
    Adapted all tests and docs to the new format
 
3381
    
 
3382
    2005/07/28
 
3383
    ----------
 
3384
    
 
3385
    By Nicola Larosa
 
3386
    
 
3387
    Added more tests
 
3388
    
 
3389
    2005/07/23
 
3390
    ----------
 
3391
    
 
3392
    By Nicola Larosa
 
3393
    
 
3394
    Reformatted final docstring in ReST format, indented it for easier folding
 
3395
    
 
3396
    Code tests converted to doctest format, and scattered them around
 
3397
    in various docstrings
 
3398
    
 
3399
    Walk method rewritten using scalars and sections attributes
 
3400
    
 
3401
    2005/07/22
 
3402
    ----------
 
3403
    
 
3404
    By Nicola Larosa
 
3405
    
 
3406
    Changed Validator and SimpleVal "test" methods to "check"
 
3407
    
 
3408
    More code cleanup
 
3409
    
 
3410
    2005/07/21
 
3411
    ----------
 
3412
    
 
3413
    Changed Section.sequence to Section.scalars and Section.sections
 
3414
    
 
3415
    Added Section.configspec
 
3416
    
 
3417
    Sections in the root section now have no extra indentation
 
3418
    
 
3419
    Comments now better supported in Section and preserved by ConfigObj
 
3420
    
 
3421
    Comments also written out
 
3422
    
 
3423
    Implemented initial_comment and final_comment
 
3424
    
 
3425
    A scalar value after a section will now raise an error
 
3426
    
 
3427
    2005/07/20
 
3428
    ----------
 
3429
    
 
3430
    Fixed a couple of bugs
 
3431
    
 
3432
    Can now pass a tuple instead of a list
 
3433
    
 
3434
    Simplified dict and walk methods
 
3435
    
 
3436
    Added __str__ to Section
 
3437
    
 
3438
    2005/07/10
 
3439
    ----------
 
3440
    
 
3441
    By Nicola Larosa
 
3442
    
 
3443
    More code cleanup
 
3444
    
 
3445
    2005/07/08
 
3446
    ----------
 
3447
    
 
3448
    The stringify option implemented. On by default.
 
3449
    
 
3450
    2005/07/07
 
3451
    ----------
 
3452
    
 
3453
    Renamed private attributes with a single underscore prefix.
 
3454
    
 
3455
    Changes to interpolation - exceeding recursion depth, or specifying a
 
3456
    missing value, now raise errors.
 
3457
    
 
3458
    Changes for Python 2.2 compatibility. (changed boolean tests - removed
 
3459
    ``is True`` and ``is False``)
 
3460
    
 
3461
    Added test for duplicate section and member (and fixed bug)
 
3462
    
 
3463
    2005/07/06
 
3464
    ----------
 
3465
    
 
3466
    By Nicola Larosa
 
3467
    
 
3468
    Code cleanup
 
3469
    
 
3470
    2005/07/02
 
3471
    ----------
 
3472
    
 
3473
    Version 0.1.0
 
3474
    
 
3475
    Now properly handles values including comments and lists.
 
3476
    
 
3477
    Better error handling.
 
3478
    
 
3479
    String interpolation.
 
3480
    
 
3481
    Some options implemented.
 
3482
    
 
3483
    You can pass a Section a dictionary to initialise it.
 
3484
    
 
3485
    Setting a Section member to a dictionary will create a Section instance.
 
3486
    
 
3487
    2005/06/26
 
3488
    ----------
 
3489
    
 
3490
    Version 0.0.1
 
3491
    
 
3492
    Experimental reader.
 
3493
    
 
3494
    A reasonably elegant implementation - a basic reader in 160 lines of code.
 
3495
    
 
3496
    *A programming language is a medium of expression.* - Paul Graham
 
3497
"""
 
3498