149
165
'indent_type': None,
150
166
'encoding': None,
151
167
'default_encoding': None,
169
'write_empty_values': False,
177
raise ImportError('compiler module not available')
178
p = compiler.parse(s)
179
return p.getChildren()[1].getChildren()[0].getChildren()[1]
182
class UnknownType(Exception):
186
class Builder(object):
189
m = getattr(self, 'build_' + o.__class__.__name__, None)
191
raise UnknownType(o.__class__.__name__)
194
def build_List(self, o):
195
return map(self.build, o.getChildren())
197
def build_Const(self, o):
200
def build_Dict(self, o):
202
i = iter(map(self.build, o.getChildren()))
207
def build_Tuple(self, o):
208
return tuple(self.build_List(o))
210
def build_Name(self, o):
215
if o.name == 'False':
219
raise UnknownType('Undefined Name')
221
def build_Add(self, o):
222
real, imag = map(self.build_Const, o.getChildren())
226
raise UnknownType('Add')
227
if not isinstance(imag, complex) or imag.real != 0.0:
228
raise UnknownType('Add')
231
def build_Getattr(self, o):
232
parent = self.build(o.expr)
233
return getattr(parent, o.attrname)
235
def build_UnarySub(self, o):
236
return -self.build_Const(o.getChildren()[0])
238
def build_UnaryAdd(self, o):
239
return self.build_Const(o.getChildren()[0])
248
return _builder.build(getObj(s))
154
252
class ConfigObjError(SyntaxError):
156
254
This is the base class for all errors that ConfigObj raises.
157
255
It is a subclass of SyntaxError.
159
>>> raise ConfigObjError
160
Traceback (most recent call last):
163
def __init__(self, message='', line_number=None, line=''):
257
def __init__(self, msg='', line_number=None, line=''):
165
259
self.line_number = line_number
166
self.message = message
167
SyntaxError.__init__(self, message)
261
SyntaxError.__init__(self, msg)
169
264
class NestingError(ConfigObjError):
171
266
This error indicates a level of nesting that doesn't match.
173
>>> raise NestingError
174
Traceback (most recent call last):
178
270
class ParseError(ConfigObjError):
180
272
This error indicates that a line is badly written.
181
273
It is neither a valid ``key = value`` line,
182
274
nor a valid section marker line.
185
Traceback (most recent call last):
278
class ReloadError(IOError):
280
A 'reload' operation failed.
281
This exception is a subclass of ``IOError``.
284
IOError.__init__(self, 'reload failed, filename is not set.')
189
287
class DuplicateError(ConfigObjError):
191
289
The keyword or section specified already exists.
193
>>> raise DuplicateError
194
Traceback (most recent call last):
198
293
class ConfigspecError(ConfigObjError):
200
295
An error occured whilst parsing a configspec.
202
>>> raise ConfigspecError
203
Traceback (most recent call last):
207
299
class InterpolationError(ConfigObjError):
208
300
"""Base class for the two interpolation errors."""
210
class InterpolationDepthError(InterpolationError):
303
class InterpolationLoopError(InterpolationError):
211
304
"""Maximum interpolation depth exceeded in string interpolation."""
213
306
def __init__(self, option):
215
>>> raise InterpolationDepthError('yoda')
216
Traceback (most recent call last):
217
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
219
307
InterpolationError.__init__(
221
'max interpolation depth exceeded in value "%s".' % option)
309
'interpolation loop detected in value "%s".' % option)
223
312
class RepeatSectionError(ConfigObjError):
225
314
This error indicates additional sections in a section with a
226
315
``__many__`` (repeated) section.
228
>>> raise RepeatSectionError
229
Traceback (most recent call last):
233
319
class MissingInterpolationOption(InterpolationError):
234
320
"""A value specified for interpolation was missing."""
236
322
def __init__(self, option):
238
>>> raise MissingInterpolationOption('yoda')
239
Traceback (most recent call last):
240
MissingInterpolationOption: missing option "yoda" in interpolation.
242
323
InterpolationError.__init__(
244
325
'missing option "%s" in interpolation.' % option)
328
class UnreprError(ConfigObjError):
329
"""An error parsing in unrepr mode."""
333
class InterpolationEngine(object):
335
A helper class to help perform string interpolation.
337
This class is an abstract base class; its descendants perform
341
# compiled regexp to use in self.interpolate()
342
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
344
def __init__(self, section):
345
# the Section instance that "owns" this engine
346
self.section = section
349
def interpolate(self, key, value):
350
def recursive_interpolate(key, value, section, backtrail):
351
"""The function that does the actual work.
353
``value``: the string we're trying to interpolate.
354
``section``: the section in which that string was found
355
``backtrail``: a dict to keep track of where we've been,
356
to detect and prevent infinite recursion loops
358
This is similar to a depth-first-search algorithm.
360
# Have we been here already?
361
if backtrail.has_key((key, section.name)):
362
# Yes - infinite loop detected
363
raise InterpolationLoopError(key)
364
# Place a marker on our backtrail so we won't come back here again
365
backtrail[(key, section.name)] = 1
367
# Now start the actual work
368
match = self._KEYCRE.search(value)
370
# The actual parsing of the match is implementation-dependent,
371
# so delegate to our helper function
372
k, v, s = self._parse_match(match)
374
# That's the signal that no further interpolation is needed
377
# Further interpolation may be needed to obtain final value
378
replacement = recursive_interpolate(k, v, s, backtrail)
379
# Replace the matched string with its final value
380
start, end = match.span()
381
value = ''.join((value[:start], replacement, value[end:]))
382
new_search_start = start + len(replacement)
383
# Pick up the next interpolation key, if any, for next time
384
# through the while loop
385
match = self._KEYCRE.search(value, new_search_start)
387
# Now safe to come back here again; remove marker from backtrail
388
del backtrail[(key, section.name)]
392
# Back in interpolate(), all we have to do is kick off the recursive
393
# function with appropriate starting values
394
value = recursive_interpolate(key, value, self.section, {})
398
def _fetch(self, key):
399
"""Helper function to fetch values from owning section.
401
Returns a 2-tuple: the value, and the section where it was found.
403
# switch off interpolation before we try and fetch anything !
404
save_interp = self.section.main.interpolation
405
self.section.main.interpolation = False
407
# Start at section that "owns" this InterpolationEngine
408
current_section = self.section
410
# try the current section first
411
val = current_section.get(key)
415
val = current_section.get('DEFAULT', {}).get(key)
418
# move up to parent and try again
419
# top-level's parent is itself
420
if current_section.parent is current_section:
421
# reached top level, time to give up
423
current_section = current_section.parent
425
# restore interpolation to previous value before returning
426
self.section.main.interpolation = save_interp
428
raise MissingInterpolationOption(key)
429
return val, current_section
432
def _parse_match(self, match):
433
"""Implementation-dependent helper function.
435
Will be passed a match object corresponding to the interpolation
436
key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
437
key in the appropriate config file section (using the ``_fetch()``
438
helper function) and return a 3-tuple: (key, value, section)
440
``key`` is the name of the key we're looking for
441
``value`` is the value found for that key
442
``section`` is a reference to the section where it was found
444
``key`` and ``section`` should be None if no further
445
interpolation should be performed on the resulting value
446
(e.g., if we interpolated "$$" and returned "$").
448
raise NotImplementedError()
452
class ConfigParserInterpolation(InterpolationEngine):
453
"""Behaves like ConfigParser."""
454
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
456
def _parse_match(self, match):
458
value, section = self._fetch(key)
459
return key, value, section
463
class TemplateInterpolation(InterpolationEngine):
464
"""Behaves like string.Template."""
466
_KEYCRE = re.compile(r"""
468
(?P<escaped>\$) | # Two $ signs
469
(?P<named>[_a-z][_a-z0-9]*) | # $name format
470
{(?P<braced>[^}]*)} # ${name} format
472
""", re.IGNORECASE | re.VERBOSE)
474
def _parse_match(self, match):
475
# Valid name (in or out of braces): fetch value from section
476
key = match.group('named') or match.group('braced')
478
value, section = self._fetch(key)
479
return key, value, section
480
# Escaped delimiter (e.g., $$): return single delimiter
481
if match.group('escaped') is not None:
482
# Return None for key and section to indicate it's time to stop
483
return None, self._delimiter, None
484
# Anything else: ignore completely, just return it unchanged
485
return None, match.group(), None
488
interpolation_engines = {
489
'configparser': ConfigParserInterpolation,
490
'template': TemplateInterpolation,
246
495
class Section(dict):
248
497
A dictionary-like object that represents a section in a config file.
250
It does string interpolation if the 'interpolate' attribute
499
It does string interpolation if the 'interpolation' attribute
251
500
of the 'main' object is set to True.
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.
502
Interpolation is tried first from this object, then from the 'DEFAULT'
503
section of this object, next from the parent and its 'DEFAULT' section,
504
and so on until the main object is reached.
256
506
A Section will behave like an ordered dictionary - following the
257
507
order of the ``scalars`` and ``sections`` attributes.
279
527
# level of nesting depth of this Section
280
528
self.depth = depth
529
# purely for information
533
# we do this explicitly so that __setitem__ is used properly
534
# (rather than just passing to ``dict.__init__``)
535
for entry, value in indict.iteritems():
539
def _initialise(self):
281
540
# the sequence of scalar values in this Section
282
541
self.scalars = []
283
542
# the sequence of sections in this Section
284
543
self.sections = []
285
# purely for information
287
544
# for comments :-)
288
545
self.comments = {}
289
546
self.inline_comments = {}
290
547
# for the configspec
291
548
self.configspec = {}
550
self._configspec_comments = {}
551
self._configspec_inline_comments = {}
552
self._cs_section_comments = {}
553
self._cs_section_inline_comments = {}
293
555
self.defaults = []
295
# we do this explicitly so that __setitem__ is used properly
296
# (rather than just passing to ``dict.__init__``)
298
self[entry] = indict[entry]
300
def _interpolate(self, value):
301
"""Nicked from ConfigParser."""
302
depth = MAX_INTERPOL_DEPTH
303
# loop through this until it's done
306
if value.find("%(") != -1:
307
value = self._KEYCRE.sub(self._interpolation_replace, value)
556
self.default_values = {}
559
def _interpolate(self, key, value):
561
# do we already have an interpolation engine?
562
engine = self._interpolation_engine
563
except AttributeError:
564
# not yet: first time running _interpolate(), so pick the engine
565
name = self.main.interpolation
566
if name == True: # note that "if name:" would be incorrect here
567
# backwards-compatibility: interpolation=True means use default
568
name = DEFAULT_INTERPOLATION
569
name = name.lower() # so that "Template", "template", etc. all work
570
class_ = interpolation_engines.get(name, None)
572
# invalid value for self.main.interpolation
573
self.main.interpolation = False
311
raise InterpolationDepthError(value)
576
# save reference to engine so we don't have to do this again
577
engine = self._interpolation_engine = class_(self)
578
# let the engine do the actual work
579
return engine.interpolate(key, value)
314
def _interpolation_replace(self, match):
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
326
val = self.parent.get('DEFAULT', {}).get(s)
327
# last, try the 'DEFAULT' member of the *main section*
329
val = self.main.get('DEFAULT', {}).get(s)
330
self.main.interpolation = True
332
raise MissingInterpolationOption(s)
335
582
def __getitem__(self, key):
336
583
"""Fetch the item and do string interpolation."""
337
584
val = dict.__getitem__(self, key)
338
585
if self.main.interpolation and isinstance(val, StringTypes):
339
return self._interpolate(val)
586
return self._interpolate(key, val)
342
def __setitem__(self, key, value):
590
def __setitem__(self, key, value, unrepr=False):
344
592
Correctly set a value.
950
1242
'true': True, 'false': False,
953
1246
def __init__(self, infile=None, options=None, **kwargs):
955
Parse or create a config file object.
1248
Parse a config file or create a config file object.
957
1250
``ConfigObj(infile=None, options=None, **kwargs)``
1252
# init the superclass
1253
Section.__init__(self, self, 0, self)
959
1255
if infile is None:
961
1257
if options is None:
1260
options = dict(options)
963
1262
# keyword arguments take precedence over an options dictionary
964
1263
options.update(kwargs)
965
# init the superclass
966
Section.__init__(self, self, 0, self)
968
1265
defaults = OPTION_DEFAULTS.copy()
969
for entry in options.keys():
970
if entry not in defaults.keys():
971
raise TypeError, 'Unrecognised option "%s".' % entry
972
1266
# TODO: check the values too.
1267
for entry in options:
1268
if entry not in defaults:
1269
raise TypeError('Unrecognised option "%s".' % entry)
974
1271
# Add any explicit options to the defaults
975
1272
defaults.update(options)
977
# initialise a few variables
980
self.raise_errors = defaults['raise_errors']
981
self.interpolation = defaults['interpolation']
982
self.list_values = defaults['list_values']
983
self.create_empty = defaults['create_empty']
984
self.file_error = defaults['file_error']
985
self.stringify = defaults['stringify']
986
self.indent_type = defaults['indent_type']
987
self.encoding = defaults['encoding']
988
self.default_encoding = defaults['default_encoding']
992
self.initial_comment = []
993
self.final_comment = []
1273
self._initialise(defaults)
1274
configspec = defaults['configspec']
1275
self._original_configspec = configspec
1276
self._load(infile, configspec)
1279
def _load(self, infile, configspec):
995
1280
if isinstance(infile, StringTypes):
996
1281
self.filename = infile
997
1282
if os.path.isfile(infile):
998
infile = open(infile).read() or []
1283
h = open(infile, 'rb')
1284
infile = h.read() or []
999
1286
elif self.file_error:
1000
1287
# raise an error if the file doesn't exist
1001
raise IOError, 'Config file not found: "%s".' % self.filename
1288
raise IOError('Config file not found: "%s".' % self.filename)
1003
1290
# file doesn't already exist
1004
1291
if self.create_empty:
1005
1292
# this is a good test that the filename specified
1006
# isn't impossible - like on a non existent device
1293
# isn't impossible - like on a non-existent device
1007
1294
h = open(infile, 'w')
1011
1299
elif isinstance(infile, (list, tuple)):
1012
1300
infile = list(infile)
1013
1302
elif isinstance(infile, dict):
1014
1303
# initialise self
1015
1304
# the Section class handles creating subsections
1016
1305
if isinstance(infile, ConfigObj):
1017
1306
# get a copy of our ConfigObj
1018
1307
infile = infile.dict()
1019
1309
for entry in infile:
1020
1310
self[entry] = infile[entry]
1021
1311
del self._errors
1022
if defaults['configspec'] is not None:
1023
self._handle_configspec(defaults['configspec'])
1313
if configspec is not None:
1314
self._handle_configspec(configspec)
1025
1316
self.configspec = None
1027
elif hasattr(infile, 'read'):
1319
elif getattr(infile, 'read', None) is not None:
1028
1320
# This supports file like objects
1029
1321
infile = infile.read() or []
1030
1322
# needs splitting into lines - but needs doing *after* decoding
1031
1323
# in case it's not an 8 bit encoding
1033
raise TypeError, ('infile must be a filename,'
1034
' file like object, or list of lines.')
1325
raise TypeError('infile must be a filename, file like object, or list of lines.')
1037
1328
# don't do it for the empty ConfigObj
1038
1329
infile = self._handle_bom(infile)
1382
1655
'Parse error in value at line %s.',
1383
1656
ParseError, infile, cur_index)
1662
value = unrepr(value)
1663
except Exception, e:
1664
if type(e) == UnknownType:
1665
msg = 'Unknown name or type in value at line %s.'
1667
msg = 'Parse error in value at line %s.'
1668
self._handle_error(msg, UnreprError, infile,
1386
# extract comment and lists
1388
(value, comment) = self._handle_value(value)
1391
'Parse error in value at line %s.',
1392
ParseError, infile, cur_index)
1675
value = unrepr(value)
1676
except Exception, e:
1677
if isinstance(e, UnknownType):
1678
msg = 'Unknown name or type in value at line %s.'
1680
msg = 'Parse error in value at line %s.'
1681
self._handle_error(msg, UnreprError, infile,
1685
# extract comment and lists
1687
(value, comment) = self._handle_value(value)
1690
'Parse error in value at line %s.',
1691
ParseError, infile, cur_index)
1395
## print >> sys.stderr, sline
1396
1694
key = self._unquote(key)
1397
1695
if this_section.has_key(key):
1398
1696
self._handle_error(
1399
1697
'Duplicate keyword name at line %s.',
1400
1698
DuplicateError, infile, cur_index)
1403
## print >> sys.stderr, this_section.name
1404
this_section[key] = value
1701
# we set unrepr because if we have got this far we will never
1702
# be creating a new section
1703
this_section.__setitem__(key, value, unrepr=True)
1405
1704
this_section.inline_comments[key] = comment
1406
1705
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]
1414
# it neither matched as a keyword
1415
# or a section marker
1417
'Invalid line at line "%s".',
1418
ParseError, infile, cur_index)
1419
1708
if self.indent_type is None:
1420
1709
# no indentation used, set the type accordingly
1421
1710
self.indent_type = ''
1422
1712
# preserve the final comment
1423
1713
if not self and not self.initial_comment:
1424
1714
self.initial_comment = comment_list
1715
elif not reset_comment:
1426
1716
self.final_comment = comment_list
1717
self.list_values = temp_list_values
1428
1720
def _match_depth(self, sect, depth):
1493
1797
if self.stringify:
1494
1798
value = str(value)
1496
raise TypeError, 'Value "%s" is not a string.' % value
1500
wspace_plus = ' \r\t\n\v\t\'"'
1800
raise TypeError('Value "%s" is not a string.' % value)
1505
if (not self.list_values and '\n' not in value) or not (multiline and
1506
((("'" in value) and ('"' in value)) or ('\n' in value))):
1805
no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1806
need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1807
hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1808
check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1810
if check_for_single:
1507
1811
if not self.list_values:
1508
1812
# we don't quote if ``list_values=False``
1510
1814
# for normal values either single or double quotes will do
1511
1815
elif '\n' in value:
1512
1816
# will only happen if multiline is off - e.g. '\n' in key
1513
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1817
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1515
1818
elif ((value[0] not in wspace_plus) and
1516
1819
(value[-1] not in wspace_plus) and
1517
1820
(',' not in value)):
1520
if ("'" in value) and ('"' in value):
1521
raise ConfigObjError, (
1522
'Value "%s" cannot be safely quoted.' % value)
1823
quot = self._get_single_quote(value)
1528
1825
# if value has '\n' or "'" *and* '"', it will need triple quotes
1529
if (value.find('"""') != -1) and (value.find("'''") != -1):
1530
raise ConfigObjError, (
1531
'Value "%s" cannot be safely quoted.' % value)
1532
if value.find('"""') == -1:
1826
quot = self._get_triple_quote(value)
1828
if quot == noquot and '#' in value and self.list_values:
1829
quot = self._get_single_quote(value)
1536
1831
return quot % value
1834
def _get_single_quote(self, value):
1835
if ("'" in value) and ('"' in value):
1836
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1844
def _get_triple_quote(self, value):
1845
if (value.find('"""') != -1) and (value.find("'''") != -1):
1846
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1847
if value.find('"""') == -1:
1538
1854
def _handle_value(self, value):
1540
1856
Given a value string, unquote, remove comment,
1541
1857
handle lists. (including empty and single member lists)
1543
Testing list values.
1545
>>> testconfig3 = '''
1548
... c = test1, test2 , test3
1549
... d = test1, test2, test3,
1551
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1554
>>> d['b'] == ['test']
1556
>>> d['c'] == ['test1', 'test2', 'test3']
1558
>>> d['d'] == ['test1', 'test2', 'test3']
1561
Testing with list values off.
1564
... testconfig3.split('\\n'),
1565
... raise_errors=True,
1566
... list_values=False)
1569
>>> e['b'] == 'test,'
1571
>>> e['c'] == 'test1, test2 , test3'
1573
>>> e['d'] == 'test1, test2, test3,'
1576
Testing creating from a dictionary.
1599
>>> g = ConfigObj(f)
1603
Testing we correctly detect badly built list values (4 of them).
1605
>>> testconfig4 = '''
1609
... dummy = ,,hello, goodbye
1612
... ConfigObj(testconfig4.split('\\n'))
1613
... except ConfigObjError, e:
1617
Testing we correctly detect badly quoted values (4 of them).
1619
>>> testconfig5 = '''
1620
... config = "hello # comment
1622
... fish = 'goodbye # comment
1623
... dummy = "hello again
1626
... ConfigObj(testconfig5.split('\\n'))
1627
... except ConfigObjError, e:
1631
1859
# do we look for lists in values ?
1632
1860
if not self.list_values:
1633
1861
mat = self._nolistvalue.match(value)
1634
1862
if mat is None:
1636
(value, comment) = mat.groups()
1637
1864
# NOTE: we don't unquote here
1638
return (value, comment)
1639
1867
mat = self._valueexp.match(value)
1640
1868
if mat is None:
1641
1869
# the value is badly constructed, probably badly quoted,
1642
1870
# or an invalid list
1644
1872
(list_values, single, empty_list, comment) = mat.groups()
1645
1873
if (list_values == '') and (single is None):
1646
1874
# change this if you want to accept empty values
1648
1876
# NOTE: note there is no error handling from here if the regex
1649
1877
# is wrong: then incorrect values will slip through
1650
1878
if empty_list is not None:
1651
1879
# the single comma - meaning an empty list
1652
1880
return ([], comment)
1653
1881
if single is not None:
1654
single = self._unquote(single)
1882
# handle empty values
1883
if list_values and not single:
1884
# FIXME: the '' is a workaround because our regex now matches
1885
# '' at the end of a list if it has a trailing comma
1888
single = single or '""'
1889
single = self._unquote(single)
1655
1890
if list_values == '':
1656
1891
# not a list value
1657
1892
return (single, comment)
1773
2011
scalars[entry] = val
1775
2013
sections[entry] = val
1777
2015
section.configspec = scalars
1778
2016
for entry in sections:
1779
2017
if not section.has_key(entry):
1780
2018
section[entry] = {}
1781
2019
self._handle_repeat(section[entry], sections[entry])
1783
2022
def _write_line(self, indent_string, entry, this_entry, comment):
1784
2023
"""Write an individual line, for the write method"""
1785
2024
# NOTE: the calls to self._quote here handles non-StringType values.
1786
return '%s%s%s%s%s' % (
1788
self._decode_element(self._quote(entry, multiline=False)),
1789
self._a_to_u(' = '),
1790
self._decode_element(self._quote(this_entry)),
1791
self._decode_element(comment))
2026
val = self._decode_element(self._quote(this_entry))
2028
val = repr(this_entry)
2029
return '%s%s%s%s%s' % (indent_string,
2030
self._decode_element(self._quote(entry, multiline=False)),
2031
self._a_to_u(' = '),
2033
self._decode_element(comment))
1793
2036
def _write_marker(self, indent_string, depth, entry, comment):
1794
2037
"""Write a section marker line"""
1795
return '%s%s%s%s%s' % (
1797
self._a_to_u('[' * depth),
1798
self._quote(self._decode_element(entry), multiline=False),
1799
self._a_to_u(']' * depth),
1800
self._decode_element(comment))
2038
return '%s%s%s%s%s' % (indent_string,
2039
self._a_to_u('[' * depth),
2040
self._quote(self._decode_element(entry), multiline=False),
2041
self._a_to_u(']' * depth),
2042
self._decode_element(comment))
1802
2045
def _handle_comment(self, comment):
1804
Deal with a comment.
1806
>>> filename = a.filename
1807
>>> a.filename = None
1808
>>> values = a.write()
1810
>>> while index < 23:
1812
... line = values[index-1]
1813
... assert line.endswith('# comment ' + str(index))
1814
>>> a.filename = filename
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
1826
>>> nc.final_comment == end_comment
2046
"""Deal with a comment."""
1829
2047
if not comment:
1831
if self.indent_type == '\t':
1832
start = self._a_to_u('\t')
1834
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
2049
start = self.indent_type
1835
2050
if not comment.startswith('#'):
1836
start += _a_to_u('# ')
2051
start += self._a_to_u(' # ')
1837
2052
return (start + comment)
1839
def _compute_indent_string(self, depth):
1841
Compute the indent string, according to current indent_type and depth
1843
if self.indent_type == '':
1844
# no indentation at all
1846
if self.indent_type == '\t':
1848
if self.indent_type == ' ':
1849
return ' ' * NUM_INDENT_SPACES * depth
1852
2055
# Public methods
2003
2193
You can then use the ``flatten_errors`` function to turn your nested
2004
2194
results dictionary into a flattened list of failures - useful for
2005
2195
displaying meaningful error messages.
2008
... from validate import Validator
2009
... except ImportError:
2010
... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
2027
... '''.split('\\n')
2028
... configspec = '''
2029
... test1= integer(30,50)
2032
... test4=float(6.0)
2034
... test1=integer(30,50)
2037
... test4=float(6.0)
2039
... test1=integer(30,50)
2042
... test4=float(6.0)
2043
... '''.split('\\n')
2044
... val = Validator()
2045
... c1 = ConfigObj(config, configspec=configspec)
2046
... test = c1.validate(val)
2057
... 'sub section': {
2066
>>> val.check(c1.configspec['test4'], c1['test4'])
2067
Traceback (most recent call last):
2068
VdtValueTooSmallError: the value "5.0" is too small.
2070
>>> val_test_config = '''
2075
... key2 = 1.1, 3.0, 17, 6.8
2078
... key2 = True'''.split('\\n')
2079
>>> val_test_configspec = '''
2084
... key2 = float_list(4)
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)
2091
>>> val_test['key'] = 'text not a digit'
2092
>>> val_res = val_test.validate(val)
2093
>>> val_res == {'key2': True, 'section': True, 'key': False}
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)
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)
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)
2113
{'test1': '30', 'section': {'sub section': {}}}
2114
>>> default_test.validate(val)
2116
>>> default_test == {
2118
... 'test2': 'hello',
2123
... 'test2': 'hello',
2126
... 'sub section': {
2129
... 'test2': 'hello',
2136
Now testing with repeated sections : BIG TEST
2138
>>> repeated_1 = '''
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)
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 = '''
2159
... # blank dogs with puppies
2160
... # should be filled in by the configspec
2175
... # blank cats with kittens
2176
... # should be filled in by the configspec
2189
... '''.split('\\n')
2190
>>> repeated_3 = '''
2201
... '''.split('\\n')
2202
>>> repeated_4 = '''
2205
... name = string(default=Michael)
2206
... age = float(default=0.0)
2207
... sex = option(m, f, default=m)
2208
... '''.split('\\n')
2209
>>> repeated_5 = '''
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)
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)
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},
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},
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},
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},
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},
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},
2282
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2283
>>> repeater.validate(val)
2287
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2288
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2289
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2292
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2293
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2294
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2298
>>> repeater = ConfigObj(configspec=repeated_4)
2299
>>> repeater['Michael'] = {}
2300
>>> repeater.validate(val)
2303
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2306
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2308
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2309
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2312
>>> repeater.validate(val)
2315
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2319
... 'tail': 'short',
2320
... 'name': 'pussy',
2321
... 'description': {
2323
... 'height': 3.2999999999999998,
2324
... 'coat': {'fur': 'black', 'condition': 5},
2329
... 'tail': 'short',
2330
... 'name': 'pussy',
2331
... 'description': {
2333
... 'height': 3.2999999999999998,
2334
... 'coat': {'fur': 'black', 'condition': 5},
2339
... 'tail': 'short',
2340
... 'name': 'pussy',
2341
... 'description': {
2343
... 'height': 3.2999999999999998,
2344
... 'coat': {'fur': 'black', 'condition': 5},
2351
Test that interpolation is preserved for validated string values.
2352
Also check that interpolation works in configspecs.
2354
>>> t['DEFAULT'] = {}
2355
>>> t['DEFAULT']['test'] = 'a'
2356
>>> t['test'] = '%(test)s'
2360
>>> t.configspec = {'test': 'string'}
2363
>>> t.interpolation = False
2365
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2367
... 'interpolated string = string(default="fuzzy-%(man)s")',
2371
>>> c = ConfigObj(configspec=specs)
2374
>>> c['interpolated string']
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
2381
2197
if section is None:
2382
2198
if self.configspec is None:
2383
raise ValueError, 'No configspec supplied.'
2199
raise ValueError('No configspec supplied.')
2384
2200
if preserve_errors:
2385
if VdtMissingValue is None:
2386
raise ImportError('Missing validate module.')
2201
# We do this once to remove a top level dependency on the validate module
2202
# Which makes importing configobj faster
2203
from validate import VdtMissingValue
2204
self._vdtMissingValue = VdtMissingValue
2389
2207
spec_section = section.configspec
2208
if copy and getattr(section, '_configspec_initial_comment', None) is not None:
2209
section.initial_comment = section._configspec_initial_comment
2210
section.final_comment = section._configspec_final_comment
2211
section.encoding = section._configspec_encoding
2212
section.BOM = section._configspec_BOM
2213
section.newlines = section._configspec_newlines
2214
section.indent_type = section._configspec_indent_type
2390
2216
if '__many__' in section.configspec:
2391
2217
many = spec_section['__many__']
2392
2218
# dynamically assign the configspecs
2652
# FIXME: test error code for badly built multiline values
2653
# FIXME: test handling of StringIO
2654
# FIXME: test interpolation with writing
2658
Dummy function to hold some of the doctests.
2695
... 'keys11': 'val1',
2696
... 'keys13': 'val3',
2697
... 'keys12': 'val2',
2700
... 'section 2 sub 1': {
2703
... 'keys21': 'val1',
2704
... 'keys22': 'val2',
2705
... 'keys23': 'val3',
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'] = ''
2717
>>> assert t2.write() == ['','b = b', '']
2719
# Test ``list_values=False`` stuff
2721
... key1 = no quotes
2722
... key2 = 'single quotes'
2723
... key3 = "double quotes"
2724
... key4 = "list", 'with', several, "quotes"
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"'
2732
>>> cfg = ConfigObj(list_values=False)
2733
>>> cfg['key1'] = 'Multiline\\nValue'
2734
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2736
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2737
>>> cfg.list_values = True
2738
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2739
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2742
Test flatten_errors:
2744
>>> from validate import Validator, VdtValueTooSmallError
2760
... '''.split('\\n')
2761
>>> configspec = '''
2762
... test1= integer(30,50)
2765
... test4=float(6.0)
2767
... test1=integer(30,50)
2770
... test4=float(6.0)
2772
... test1=integer(30,50)
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)]
2783
>>> res = c1.validate(val, preserve_errors=True)
2784
>>> check = flatten_errors(c1, res)
2788
(['section', 'sub section'], 'test4')
2790
(['section'], 'test4')
2791
>>> for entry in check:
2792
... isinstance(entry[2], VdtValueTooSmallError)
2793
... print str(entry[2])
2795
the value "5.0" is too small.
2797
the value "5.0" is too small.
2799
the value "5.0" is too small.
2801
Test unicode handling, BOM, write witha file like object and line endings :
2803
... # initial comment
2804
... # inital comment 2
2806
... test1 = some value
2808
... test2 = another value # inline comment
2809
... # section comment
2810
... [section] # inline comment
2811
... test = test # another inline comment
2815
... # final comment2
2817
>>> u = u_base.encode('utf_8').splitlines(True)
2818
>>> u[0] = BOM_UTF8 + u[0]
2819
>>> uc = ConfigObj(u)
2820
>>> uc.encoding = None
2823
>>> uc == {'test1': 'some value', 'test2': 'another value',
2824
... 'section': {'test': 'test', 'test2': 'test2'}}
2826
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2829
>>> isinstance(uc['test1'], unicode)
2835
>>> uc['latin1'] = "This costs lot's of "
2836
>>> a_list = uc.write()
2839
>>> isinstance(a_list[0], str)
2841
>>> a_list[0].startswith(BOM_UTF8)
2843
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2844
>>> uc = ConfigObj(u)
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)
2855
>>> uc2.filename == None
2857
>>> uc2.newlines == '\\r'
2861
if __name__ == '__main__':
2862
# run the code tests in doctest format
2865
key1= val # comment 1
2866
key2= val # comment 2
2869
key1= val # comment 5
2870
key2= val # comment 6
2873
key1= val # comment 9
2874
key2= val # comment 10
2876
[[lev2ba]] # comment 12
2877
key1= val # comment 13
2879
[[lev2bb]] # comment 15
2880
key1= val # comment 16
2882
[lev1c] # comment 18
2884
[[lev2c]] # comment 20
2886
[[[lev3c]]] # comment 22
2887
key1 = val # comment 23"""
2893
["section 1"] # comment
2902
[['section 2 sub 1']]
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 \'''
2927
\''' # I guess this is a comment too
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)
2937
'INTP_VER': INTP_VER,
2942
doctest.testmod(m, globs=globs)
2953
Better support for configuration from multiple files, including tracking
2954
*where* the original file came from and writing changes to the correct
2958
Make ``newline`` an option (as well as an attribute) ?
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
2964
Option to set warning type for unicode decode ? (Defaults to strict).
2966
A method to optionally remove uniform indentation from multiline values.
2967
(do as an example of using ``walk`` - along with string-escape)
2969
Should the results dictionary from validate be an ordered dictionary if
2970
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2972
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2974
Implement some of the sequence methods (which include slicing) from the
2977
INCOMPATIBLE CHANGES
2978
====================
2980
(I have removed a lot of needless complications - this list is probably not
2981
conclusive, many option/attribute/method names have changed)
2985
The only valid divider is '='
2987
We've removed line continuations with '\'
2989
No recursive lists in values
2993
No distinction between flatfiles and non flatfiles
2995
Change in list syntax - use commas to indicate list, not parentheses
2996
(square brackets and parentheses are no longer recognised as lists)
2998
';' is no longer valid for comments and no multiline comments
3002
We don't allow empty values - have to use '' or ""
3004
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
3005
initialise it as an empty section.
3007
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
3008
replaced by triple quote, multiple line values.
3010
The ``newline``, ``force_return``, and ``default`` options have gone
3012
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
3013
with the ``encode`` and ``decode`` methods.
3015
``fileerror`` and ``createempty`` options have become ``file_error`` and
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.
3022
Exceeding the maximum depth of recursion in string interpolation now
3023
raises an error ``InterpolationDepthError``.
3025
Specifying a value for interpolation which doesn't exist now raises an
3026
error ``MissingInterpolationOption`` (instead of merely being ignored).
3028
The ``writein`` method has been removed.
3030
The comments attribute is now a list (``inline_comments`` equates to the
3031
old comments attribute)
3036
``validate`` doesn't report *extra* values or sections.
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.
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).
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.
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 ?
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.
3060
Does it matter that we don't support the ':' divider, which is supported
3061
by ``ConfigParser`` ?
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.)
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
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.
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. : ::
3087
keyword = value1, 'value 2', "value 3"
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. : ::
3092
keyword = "single value",
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. : ::
3097
keyword = , # an empty list
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.
3110
Removed ``BOM_UTF8`` from ``__all__``.
3112
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3113
*only* ``True`` for the ``UTF16`` encoding.
3115
File like objects no longer need a ``seek`` attribute.
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
3122
Full unicode support added. New options/attributes ``encoding``,
3123
``default_encoding``.
3125
utf16 files decoded to unicode.
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.)
3131
File paths are *not* converted to absolute paths, relative paths will
3132
remain relative as the ``filename`` attribute.
3134
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
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)
3144
Deprecated ``istrue``, replaced it with ``as_bool``.
3146
Added ``as_int`` and ``as_float``.
3148
utf8 and utf16 BOM handled in an endian agnostic way.
3153
Validation no longer done on the 'DEFAULT' section (only in the root
3154
level). This allows interpolation in configspecs.
3156
Change in validation syntax implemented in validate 0.2.1
3163
Added ``merge``, a recursive update.
3165
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3168
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3170
Fixed bug where a config file is *all* comment, the comment will now be
3171
``initial_comment`` rather than ``final_comment``.
3176
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3181
Fixed bug in ``Section.walk`` when transforming names as well as values.
3183
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3186
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3187
are multiline values.
3189
List values are written as ``item, item`` rather than ``item,item``.
3196
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3204
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3205
a reference to the new section.
3210
Removed ``PositionError``.
3212
Allowed quotes around keys as documented.
3214
Fixed bug with commas in comments. (matched as a list value)
3221
Fixed bug in initialising ConfigObj from a ConfigObj.
3223
Changed the mailing list address.
3230
Fixed bug in ``Section.__delitem__`` oops.
3235
Interpolation is switched off before writing out files.
3237
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3238
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3240
Moved the doctests from the ``__init__`` method to a separate function.
3241
(For the sake of IDE calltips).
3248
String values unchanged by validation *aren't* reset. This preserves
3249
interpolation in string values.
3254
None from a default is turned to '' if stringify is off - because setting
3255
a value to None raises an error.
3264
Actually added the RepeatSectionError class ;-)
3269
If ``stringify`` is off - list values are preserved by the ``validate``
3277
Fixed ``simpleVal``.
3279
Added ``RepeatSectionError`` error if you have additional sections in a
3280
section with a ``__many__`` (repeated) section.
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
3288
Reshaped the ConfigObj._multiline method to better reflect its semantics
3290
Changed the "default_test" test in ConfigObj.validate to check the fix for
3291
the bug in validate.Validator.check
3298
Updated comments at top
3305
Implemented repeated sections.
3309
Added test for interpreter version: raises RuntimeError if earlier than
3317
Implemented default values in configspecs.
3321
Fixed naked except: clause in validate that was silencing the fact
3322
that Python2.2 does not have dict.pop
3329
Bug fix causing error if file didn't exist.
3336
Adjusted doctests for Python 2.2.3 compatibility
3343
Added the inline_comments attribute
3345
We now preserve and rewrite all comments in the config file
3347
configspec is now a section attribute
3349
The validate method changes values in place
3351
Added InterpolationError
3353
The errors now have line number, line, and message attributes. This
3354
simplifies error handling
3363
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3366
Replaced ``basestring`` with ``types.StringTypes``
3368
Removed the ``writein`` method
3377
Indentation in config file is not significant anymore, subsections are
3378
designated by repeating square brackets
3380
Adapted all tests and docs to the new format
3394
Reformatted final docstring in ReST format, indented it for easier folding
3396
Code tests converted to doctest format, and scattered them around
3397
in various docstrings
3399
Walk method rewritten using scalars and sections attributes
3406
Changed Validator and SimpleVal "test" methods to "check"
3413
Changed Section.sequence to Section.scalars and Section.sections
3415
Added Section.configspec
3417
Sections in the root section now have no extra indentation
3419
Comments now better supported in Section and preserved by ConfigObj
3421
Comments also written out
3423
Implemented initial_comment and final_comment
3425
A scalar value after a section will now raise an error
3430
Fixed a couple of bugs
3432
Can now pass a tuple instead of a list
3434
Simplified dict and walk methods
3436
Added __str__ to Section
3448
The stringify option implemented. On by default.
3453
Renamed private attributes with a single underscore prefix.
3455
Changes to interpolation - exceeding recursion depth, or specifying a
3456
missing value, now raise errors.
3458
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3459
``is True`` and ``is False``)
3461
Added test for duplicate section and member (and fixed bug)
3475
Now properly handles values including comments and lists.
3477
Better error handling.
3479
String interpolation.
3481
Some options implemented.
3483
You can pass a Section a dictionary to initialise it.
3485
Setting a Section member to a dictionary will create a Section instance.
3492
Experimental reader.
3494
A reasonably elegant implementation - a basic reader in 160 lines of code.
3496
*A programming language is a medium of expression.* - Paul Graham
2505
"""*A programming language is a medium of expression.* - Paul Graham"""