2
# A config file reader/writer that supports nested sections in config files.
3
# Copyright (C) 2005-2006 Michael Foord, Nicola Larosa
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
# nico AT tekNico DOT net
8
# http://www.voidspace.org.uk/python/configobj.html
10
# Released subject to the BSD License
11
# Please see http://www.voidspace.org.uk/python/license.shtml
13
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14
# For information about bugfixes, updates and support, please join the
15
# ConfigObj mailing list:
16
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17
# Comments, suggestions and bug reports welcome.
19
from __future__ import generators
29
... 'member': 'value',
31
>>> x = ConfigObj(z.write())
37
INTP_VER = sys.version_info[:2]
39
raise RuntimeError("Python v.2.2 or later needed")
43
from types import StringTypes
44
from warnings import warn
45
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
47
# A dictionary mapping BOM to
48
# the encoding to decode with, and what to set the
49
# encoding attribute to.
51
BOM_UTF8: ('utf_8', None),
52
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
53
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
54
BOM_UTF16: ('utf_16', 'utf_16'),
56
# All legal variants of the BOM codecs.
57
# TODO: the list of aliases is not meant to be exhaustive, is there a
64
'utf16_be': 'utf16_be',
65
'utf_16_be': 'utf16_be',
66
'utf-16be': 'utf16_be',
67
'utf16_le': 'utf16_le',
68
'utf_16_le': 'utf16_le',
69
'utf-16le': 'utf16_le',
77
# Map of encodings to the BOM to write.
81
'utf16_be': BOM_UTF16_BE,
82
'utf16_le': BOM_UTF16_LE,
87
from validate import VdtMissingValue
89
VdtMissingValue = None
95
"""enumerate for Python 2.2."""
107
__version__ = '4.3.0alpha2'
109
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
111
__docformat__ = "restructuredtext en"
113
# NOTE: Does it make sense to have the following in __all__ ?
114
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
115
# NOTE: If used via ``from configobj import...``
116
# NOTE: They are effectively read only
119
'DEFAULT_INDENT_TYPE',
121
'MAX_INTERPOL_DEPTH',
129
'InterpolationError',
130
'InterpolationDepthError',
131
'MissingInterpolationOption',
132
'RepeatSectionError',
137
DEFAULT_INDENT_TYPE = ' '
138
NUM_INDENT_SPACES = 4
139
MAX_INTERPOL_DEPTH = 10
142
'interpolation': True,
143
'raise_errors': False,
145
'create_empty': False,
149
# option may be set to one of ('', ' ', '\t')
152
'default_encoding': None,
154
'write_empty_values': False,
159
p = compiler.parse(s)
160
return p.getChildren()[1].getChildren()[0].getChildren()[1]
162
class UnknownType(Exception):
168
m = getattr(self, 'build_' + o.__class__.__name__, None)
170
raise UnknownType(o.__class__.__name__)
173
def build_List(self, o):
174
return map(self.build, o.getChildren())
176
def build_Const(self, o):
179
def build_Dict(self, o):
181
i = iter(map(self.build, o.getChildren()))
186
def build_Tuple(self, o):
187
return tuple(self.build_List(o))
189
def build_Name(self, o):
194
if o.name == 'False':
198
raise UnknownType('Undefined Name')
200
def build_Add(self, o):
201
real, imag = map(self.build_Const, o.getChildren())
205
raise UnknownType('Add')
206
if not isinstance(imag, complex) or imag.real != 0.0:
207
raise UnknownType('Add')
210
def build_Getattr(self, o):
211
parent = self.build(o.expr)
212
return getattr(parent, o.attrname)
217
return Builder().build(getObj(s))
219
class ConfigObjError(SyntaxError):
221
This is the base class for all errors that ConfigObj raises.
222
It is a subclass of SyntaxError.
224
>>> raise ConfigObjError
225
Traceback (most recent call last):
228
def __init__(self, message='', line_number=None, line=''):
230
self.line_number = line_number
231
self.message = message
232
SyntaxError.__init__(self, message)
234
class NestingError(ConfigObjError):
236
This error indicates a level of nesting that doesn't match.
238
>>> raise NestingError
239
Traceback (most recent call last):
243
class ParseError(ConfigObjError):
245
This error indicates that a line is badly written.
246
It is neither a valid ``key = value`` line,
247
nor a valid section marker line.
250
Traceback (most recent call last):
254
class DuplicateError(ConfigObjError):
256
The keyword or section specified already exists.
258
>>> raise DuplicateError
259
Traceback (most recent call last):
263
class ConfigspecError(ConfigObjError):
265
An error occured whilst parsing a configspec.
267
>>> raise ConfigspecError
268
Traceback (most recent call last):
272
class InterpolationError(ConfigObjError):
273
"""Base class for the two interpolation errors."""
275
class InterpolationDepthError(InterpolationError):
276
"""Maximum interpolation depth exceeded in string interpolation."""
278
def __init__(self, option):
280
>>> raise InterpolationDepthError('yoda')
281
Traceback (most recent call last):
282
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
284
InterpolationError.__init__(
286
'max interpolation depth exceeded in value "%s".' % option)
288
class RepeatSectionError(ConfigObjError):
290
This error indicates additional sections in a section with a
291
``__many__`` (repeated) section.
293
>>> raise RepeatSectionError
294
Traceback (most recent call last):
298
class MissingInterpolationOption(InterpolationError):
299
"""A value specified for interpolation was missing."""
301
def __init__(self, option):
303
>>> raise MissingInterpolationOption('yoda')
304
Traceback (most recent call last):
305
MissingInterpolationOption: missing option "yoda" in interpolation.
307
InterpolationError.__init__(
309
'missing option "%s" in interpolation.' % option)
313
A dictionary-like object that represents a section in a config file.
315
It does string interpolation if the 'interpolate' attribute
316
of the 'main' object is set to True.
318
Interpolation is tried first from the 'DEFAULT' section of this object,
319
next from the 'DEFAULT' section of the parent, lastly the main object.
321
A Section will behave like an ordered dictionary - following the
322
order of the ``scalars`` and ``sections`` attributes.
323
You can use this to change the order of members.
325
Iteration follows the order: scalars, then sections.
328
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
330
def __init__(self, parent, depth, main, indict=None, name=None):
332
* parent is the section above
333
* depth is the depth level of this section
334
* main is the main ConfigObj
335
* indict is a dictionary to initialise the section with
340
# used for nesting level *and* interpolation
342
# used for the interpolation attribute
344
# level of nesting depth of this Section
346
# the sequence of scalar values in this Section
348
# the sequence of sections in this Section
350
# purely for information
354
self.inline_comments = {}
358
self._configspec_comments = {}
359
self._configspec_inline_comments = {}
360
self._cs_section_comments = {}
361
self._cs_section_inline_comments = {}
365
# we do this explicitly so that __setitem__ is used properly
366
# (rather than just passing to ``dict.__init__``)
368
self[entry] = indict[entry]
370
def _interpolate(self, value):
371
"""Nicked from ConfigParser."""
372
depth = MAX_INTERPOL_DEPTH
373
# loop through this until it's done
376
if value.find("%(") != -1:
377
value = self._KEYCRE.sub(self._interpolation_replace, value)
381
raise InterpolationDepthError(value)
384
def _interpolation_replace(self, match):
390
# switch off interpolation before we try and fetch anything !
391
self.main.interpolation = False
392
# try the 'DEFAULT' member of *this section* first
393
val = self.get('DEFAULT', {}).get(s)
394
# try the 'DEFAULT' member of the *parent section* next
396
val = self.parent.get('DEFAULT', {}).get(s)
397
# last, try the 'DEFAULT' member of the *main section*
399
val = self.main.get('DEFAULT', {}).get(s)
400
self.main.interpolation = True
402
raise MissingInterpolationOption(s)
405
def __getitem__(self, key):
406
"""Fetch the item and do string interpolation."""
407
val = dict.__getitem__(self, key)
408
if self.main.interpolation and isinstance(val, StringTypes):
409
return self._interpolate(val)
412
def __setitem__(self, key, value, unrepr=False):
414
Correctly set a value.
416
Making dictionary values Section instances.
417
(We have to special case 'Section' instances - which are also dicts)
419
Keys must be strings.
420
Values need only be strings (or lists of strings) if
421
``main.stringify`` is set.
423
`unrepr`` must be set when setting a value to a dictionary, without
424
creating a new sub-section.
426
if not isinstance(key, StringTypes):
427
raise ValueError, 'The key "%s" is not a string.' % key
429
if not self.comments.has_key(key):
430
self.comments[key] = []
431
self.inline_comments[key] = ''
432
# remove the entry from defaults
433
if key in self.defaults:
434
self.defaults.remove(key)
436
if isinstance(value, Section):
437
if not self.has_key(key):
438
self.sections.append(key)
439
dict.__setitem__(self, key, value)
440
elif isinstance(value, dict)and not unrepr:
441
# First create the new depth level,
442
# then create the section
443
if not self.has_key(key):
444
self.sections.append(key)
445
new_depth = self.depth + 1
456
if not self.has_key(key):
457
self.scalars.append(key)
458
if not self.main.stringify:
459
if isinstance(value, StringTypes):
461
elif isinstance(value, (list, tuple)):
463
if not isinstance(entry, StringTypes):
465
'Value is not a string "%s".' % entry)
467
raise TypeError, 'Value is not a string "%s".' % value
468
dict.__setitem__(self, key, value)
470
def __delitem__(self, key):
471
"""Remove items from the sequence when deleting."""
472
dict. __delitem__(self, key)
473
if key in self.scalars:
474
self.scalars.remove(key)
476
self.sections.remove(key)
477
del self.comments[key]
478
del self.inline_comments[key]
480
def get(self, key, default=None):
481
"""A version of ``get`` that doesn't bypass string interpolation."""
487
def update(self, indict):
489
A version of update that uses our ``__setitem__``.
492
self[entry] = indict[entry]
494
def pop(self, key, *args):
496
val = dict.pop(self, key, *args)
497
if key in self.scalars:
498
del self.comments[key]
499
del self.inline_comments[key]
500
self.scalars.remove(key)
501
elif key in self.sections:
502
del self.comments[key]
503
del self.inline_comments[key]
504
self.sections.remove(key)
505
if self.main.interpolation and isinstance(val, StringTypes):
506
return self._interpolate(val)
510
"""Pops the first (key,val)"""
511
sequence = (self.scalars + self.sections)
513
raise KeyError, ": 'popitem(): dictionary is empty'"
521
A version of clear that also affects scalars/sections
522
Also clears comments and configspec.
524
Leaves other attributes alone :
525
depth/main/parent are not affected
531
self.inline_comments = {}
534
def setdefault(self, key, default=None):
535
"""A version of setdefault that sets sequence if appropriate."""
544
return zip((self.scalars + self.sections), self.values())
548
return (self.scalars + self.sections)
552
return [self[key] for key in (self.scalars + self.sections)]
556
return iter(self.items())
560
return iter((self.scalars + self.sections))
564
def itervalues(self):
566
return iter(self.values())
569
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
570
for key in (self.scalars + self.sections)])
574
# Extra methods - not in a normal dictionary
578
Return a deepcopy of self as a dictionary.
580
All members that are ``Section`` instances are recursively turned to
581
ordinary dictionaries - by calling their ``dict`` method.
591
this_entry = self[entry]
592
if isinstance(this_entry, Section):
593
this_entry = this_entry.dict()
594
# XXX Modified to return tuples as tuples. -- niemeyer, 2006-04-24
595
elif isinstance(this_entry, list):
596
this_entry = list(this_entry)
597
elif isinstance(this_entry, tuple):
598
this_entry = tuple(this_entry)
599
newdict[entry] = this_entry
602
def merge(self, indict):
604
A recursive update - useful for merging config files.
606
>>> a = '''[section1]
609
... more_options = False
610
... # end of file'''.splitlines()
611
>>> b = '''# File is user.ini
614
... # end of file'''.splitlines()
615
>>> c1 = ConfigObj(b)
616
>>> c2 = ConfigObj(a)
619
{'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
621
for key, val in indict.items():
622
if (key in self and isinstance(self[key], dict) and
623
isinstance(val, dict)):
628
def rename(self, oldkey, newkey):
630
Change a keyname to another, without changing position in sequence.
632
Implemented so that transformations can be made on keys,
633
as well as on values. (used by encode and decode)
635
Also renames comments.
637
if oldkey in self.scalars:
638
the_list = self.scalars
639
elif oldkey in self.sections:
640
the_list = self.sections
642
raise KeyError, 'Key "%s" not found.' % oldkey
643
pos = the_list.index(oldkey)
646
dict.__delitem__(self, oldkey)
647
dict.__setitem__(self, newkey, val)
648
the_list.remove(oldkey)
649
the_list.insert(pos, newkey)
650
comm = self.comments[oldkey]
651
inline_comment = self.inline_comments[oldkey]
652
del self.comments[oldkey]
653
del self.inline_comments[oldkey]
654
self.comments[newkey] = comm
655
self.inline_comments[newkey] = inline_comment
657
def walk(self, function, raise_errors=True,
658
call_on_sections=False, **keywargs):
660
Walk every member and call a function on the keyword and value.
662
Return a dictionary of the return values
664
If the function raises an exception, raise the errror
665
unless ``raise_errors=False``, in which case set the return value to
668
Any unrecognised keyword arguments you pass to walk, will be pased on
669
to the function you pass in.
671
Note: if ``call_on_sections`` is ``True`` then - on encountering a
672
subsection, *first* the function is called for the *whole* subsection,
673
and then recurses into it's members. This means your function must be
674
able to handle strings, dictionaries and lists. This allows you
675
to change the key of subsections as well as for ordinary members. The
676
return value when called on the whole subsection has to be discarded.
678
See the encode and decode methods for examples, including functions.
682
You can use ``walk`` to transform the names of members of a section
683
but you mustn't add or delete members.
685
>>> config = '''[XXXXsection]
686
... XXXXkey = XXXXvalue'''.splitlines()
687
>>> cfg = ConfigObj(config)
689
{'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
690
>>> def transform(section, key):
691
... val = section[key]
692
... newkey = key.replace('XXXX', 'CLIENT1')
693
... section.rename(key, newkey)
694
... if isinstance(val, (tuple, list, dict)):
697
... val = val.replace('XXXX', 'CLIENT1')
698
... section[newkey] = val
699
>>> cfg.walk(transform, call_on_sections=True)
700
{'CLIENT1section': {'CLIENT1key': None}}
702
{'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
706
for i in range(len(self.scalars)):
707
entry = self.scalars[i]
709
val = function(self, entry, **keywargs)
710
# bound again in case name has changed
711
entry = self.scalars[i]
717
entry = self.scalars[i]
720
for i in range(len(self.sections)):
721
entry = self.sections[i]
724
function(self, entry, **keywargs)
729
entry = self.sections[i]
731
# bound again in case name has changed
732
entry = self.sections[i]
733
# previous result is discarded
734
out[entry] = self[entry].walk(
736
raise_errors=raise_errors,
737
call_on_sections=call_on_sections,
741
def decode(self, encoding):
743
Decode all strings and values to unicode, using the specified encoding.
745
Works with subsections and list values.
747
Uses the ``walk`` method.
749
Testing ``encode`` and ``decode``.
751
>>> m.decode('ascii')
752
>>> def testuni(val):
753
... for entry in val:
754
... if not isinstance(entry, unicode):
755
... print >> sys.stderr, type(entry)
756
... raise AssertionError, 'decode failed.'
757
... if isinstance(val[entry], dict):
758
... testuni(val[entry])
759
... elif not isinstance(val[entry], unicode):
760
... raise AssertionError, 'decode failed.'
762
>>> m.encode('ascii')
766
warn('use of ``decode`` is deprecated.', DeprecationWarning)
767
def decode(section, key, encoding=encoding, warn=True):
770
if isinstance(val, (list, tuple)):
773
newval.append(entry.decode(encoding))
774
elif isinstance(val, dict):
777
newval = val.decode(encoding)
778
newkey = key.decode(encoding)
779
section.rename(key, newkey)
780
section[newkey] = newval
781
# using ``call_on_sections`` allows us to modify section names
782
self.walk(decode, call_on_sections=True)
784
def encode(self, encoding):
786
Encode all strings and values from unicode,
787
using the specified encoding.
789
Works with subsections and list values.
790
Uses the ``walk`` method.
792
warn('use of ``encode`` is deprecated.', DeprecationWarning)
793
def encode(section, key, encoding=encoding):
796
if isinstance(val, (list, tuple)):
799
newval.append(entry.encode(encoding))
800
elif isinstance(val, dict):
803
newval = val.encode(encoding)
804
newkey = key.encode(encoding)
805
section.rename(key, newkey)
806
section[newkey] = newval
807
self.walk(encode, call_on_sections=True)
809
def istrue(self, key):
810
"""A deprecated version of ``as_bool``."""
811
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
812
'instead.', DeprecationWarning)
813
return self.as_bool(key)
815
def as_bool(self, key):
817
Accepts a key as input. The corresponding value must be a string or
818
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
819
retain compatibility with Python 2.2.
821
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
824
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
827
``as_bool`` is not case sensitive.
829
Any other input will raise a ``ValueError``.
834
Traceback (most recent call last):
835
ValueError: Value "fish" is neither True nor False
850
if not isinstance(val, StringTypes):
853
return self.main._bools[val.lower()]
855
raise ValueError('Value "%s" is neither True nor False' % val)
857
def as_int(self, key):
859
A convenience method which coerces the specified value to an integer.
861
If the value is an invalid literal for ``int``, a ``ValueError`` will
867
Traceback (most recent call last):
868
ValueError: invalid literal for int(): fish
874
Traceback (most recent call last):
875
ValueError: invalid literal for int(): 3.2
877
return int(self[key])
879
def as_float(self, key):
881
A convenience method which coerces the specified value to a float.
883
If the value is an invalid literal for ``float``, a ``ValueError`` will
889
Traceback (most recent call last):
890
ValueError: invalid literal for float(): fish
898
return float(self[key])
901
class ConfigObj(Section):
903
An object to read, create, and write config files.
905
Testing with duplicate keys and sections.
915
>>> ConfigObj(c.split('\\n'), raise_errors = True)
916
Traceback (most recent call last):
917
DuplicateError: Duplicate section name at line 5.
925
... 'member1' = value
929
>>> ConfigObj(d.split('\\n'), raise_errors = True)
930
Traceback (most recent call last):
931
DuplicateError: Duplicate keyword name at line 6.
934
_keyword = re.compile(r'''^ # line start
937
(?:".*?")| # double quotes
938
(?:'.*?')| # single quotes
939
(?:[^'"=].*?) # no quotes
942
(.*) # value (including list values and comments)
947
_sectionmarker = re.compile(r'''^
948
(\s*) # 1: indentation
949
((?:\[\s*)+) # 2: section marker open
950
( # 3: section name open
951
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
952
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
953
(?:[^'"\s].*?) # at least one non-space unquoted
954
) # section name close
955
((?:\s*\])+) # 4: section marker close
956
\s*(\#.*)? # 5: optional comment
960
# this regexp pulls list values out as a single string
961
# or single values and comments
962
# FIXME: this regex adds a '' to the end of comma terminated lists
963
# workaround in ``_handle_value``
964
_valueexp = re.compile(r'''^
970
(?:".*?")| # double quotes
971
(?:'.*?')| # single quotes
972
(?:[^'",\#][^,\#]*?) # unquoted
975
)* # match all list items ending in a comma (if any)
978
(?:".*?")| # double quotes
979
(?:'.*?')| # single quotes
980
(?:[^'",\#\s][^,]*?)| # unquoted
981
(?:(?<!,)) # Empty value
982
)? # last item in a list - or string value
984
(,) # alternatively a single comma - empty list
986
\s*(\#.*)? # optional comment
990
# use findall to get the members of a list value
991
_listvalueexp = re.compile(r'''
993
(?:".*?")| # double quotes
994
(?:'.*?')| # single quotes
995
(?:[^'",\#].*?) # unquoted
1001
# this regexp is used for the value
1002
# when lists are switched off
1003
_nolistvalue = re.compile(r'''^
1005
(?:".*?")| # double quotes
1006
(?:'.*?')| # single quotes
1007
(?:[^'"\#].*?)| # unquoted
1010
\s*(\#.*)? # optional comment
1014
# regexes for finding triple quoted values on one line
1015
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1016
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1017
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1018
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1021
"'''": (_single_line_single, _multi_line_single),
1022
'"""': (_single_line_double, _multi_line_double),
1025
# Used by the ``istrue`` Section method
1027
'yes': True, 'no': False,
1028
'on': True, 'off': False,
1029
'1': True, '0': False,
1030
'true': True, 'false': False,
1033
def __init__(self, infile=None, options=None, **kwargs):
1035
Parse or create a config file object.
1037
``ConfigObj(infile=None, options=None, **kwargs)``
1043
# keyword arguments take precedence over an options dictionary
1044
options.update(kwargs)
1045
# init the superclass
1046
Section.__init__(self, self, 0, self)
1048
defaults = OPTION_DEFAULTS.copy()
1049
for entry in options.keys():
1050
if entry not in defaults.keys():
1051
raise TypeError, 'Unrecognised option "%s".' % entry
1052
# TODO: check the values too.
1054
# Add any explicit options to the defaults
1055
defaults.update(options)
1057
# initialise a few variables
1058
self.filename = None
1060
self.raise_errors = defaults['raise_errors']
1061
self.interpolation = defaults['interpolation']
1062
self.list_values = defaults['list_values']
1063
self.create_empty = defaults['create_empty']
1064
self.file_error = defaults['file_error']
1065
self.stringify = defaults['stringify']
1066
self.indent_type = defaults['indent_type']
1067
self.encoding = defaults['encoding']
1068
self.default_encoding = defaults['default_encoding']
1070
self.newlines = None
1071
self.write_empty_values = defaults['write_empty_values']
1072
self.unrepr = defaults['unrepr']
1074
self.initial_comment = []
1075
self.final_comment = []
1077
if isinstance(infile, StringTypes):
1078
self.filename = infile
1079
if os.path.isfile(infile):
1080
infile = open(infile).read() or []
1081
elif self.file_error:
1082
# raise an error if the file doesn't exist
1083
raise IOError, 'Config file not found: "%s".' % self.filename
1085
# file doesn't already exist
1086
if self.create_empty:
1087
# this is a good test that the filename specified
1088
# isn't impossible - like on a non existent device
1089
h = open(infile, 'w')
1093
elif isinstance(infile, (list, tuple)):
1094
infile = list(infile)
1095
elif isinstance(infile, dict):
1097
# the Section class handles creating subsections
1098
if isinstance(infile, ConfigObj):
1099
# get a copy of our ConfigObj
1100
infile = infile.dict()
1101
for entry in infile:
1102
self[entry] = infile[entry]
1104
if defaults['configspec'] is not None:
1105
self._handle_configspec(defaults['configspec'])
1107
self.configspec = None
1109
elif hasattr(infile, 'read'):
1110
# This supports file like objects
1111
infile = infile.read() or []
1112
# needs splitting into lines - but needs doing *after* decoding
1113
# in case it's not an 8 bit encoding
1115
raise TypeError, ('infile must be a filename,'
1116
' file like object, or list of lines.')
1119
# don't do it for the empty ConfigObj
1120
infile = self._handle_bom(infile)
1121
# infile is now *always* a list
1123
# Set the newlines attribute (first line ending it finds)
1124
# and strip trailing '\n' or '\r' from lines
1126
if (not line) or (line[-1] not in '\r\n'):
1128
for end in ('\r\n', '\n', '\r'):
1129
if line.endswith(end):
1133
infile = [line.rstrip('\r\n') for line in infile]
1136
# if we had any errors, now is the time to raise them
1138
error = ConfigObjError("Parsing failed.")
1139
# set the errors attribute; it's a list of tuples:
1140
# (error_type, message, line_number)
1141
error.errors = self._errors
1142
# set the config attribute
1145
# delete private attributes
1148
if defaults['configspec'] is None:
1149
self.configspec = None
1151
self._handle_configspec(defaults['configspec'])
1153
def _handle_bom(self, infile):
1155
Handle any BOM, and decode if necessary.
1157
If an encoding is specified, that *must* be used - but the BOM should
1158
still be removed (and the BOM attribute set).
1160
(If the encoding is wrongly specified, then a BOM for an alternative
1161
encoding won't be discovered or removed.)
1163
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1164
removed. The BOM attribute will be set. UTF16 will be decoded to
1167
NOTE: This method must not be called with an empty ``infile``.
1169
Specifying the *wrong* encoding is likely to cause a
1170
``UnicodeDecodeError``.
1172
``infile`` must always be returned as a list of lines, but may be
1173
passed in as a single string.
1175
if ((self.encoding is not None) and
1176
(self.encoding.lower() not in BOM_LIST)):
1177
# No need to check for a BOM
1178
# encoding specified doesn't have one
1180
return self._decode(infile, self.encoding)
1182
if isinstance(infile, (list, tuple)):
1186
if self.encoding is not None:
1187
# encoding explicitly supplied
1188
# And it could have an associated BOM
1189
# TODO: if encoding is just UTF16 - we ought to check for both
1190
# TODO: big endian and little endian versions.
1191
enc = BOM_LIST[self.encoding.lower()]
1193
# For UTF16 we try big endian and little endian
1194
for BOM, (encoding, final_encoding) in BOMS.items():
1195
if not final_encoding:
1198
if infile.startswith(BOM):
1201
# Don't need to remove BOM
1202
return self._decode(infile, encoding)
1204
# If we get this far, will *probably* raise a DecodeError
1205
# As it doesn't appear to start with a BOM
1206
return self._decode(infile, self.encoding)
1210
if not line.startswith(BOM):
1211
return self._decode(infile, self.encoding)
1213
newline = line[len(BOM):]
1216
if isinstance(infile, (list, tuple)):
1221
return self._decode(infile, self.encoding)
1223
# No encoding specified - so we need to check for UTF8/UTF16
1224
for BOM, (encoding, final_encoding) in BOMS.items():
1225
if not line.startswith(BOM):
1229
self.encoding = final_encoding
1230
if not final_encoding:
1234
newline = line[len(BOM):]
1235
if isinstance(infile, (list, tuple)):
1239
# UTF8 - don't decode
1240
if isinstance(infile, StringTypes):
1241
return infile.splitlines(True)
1244
# UTF16 - have to decode
1245
return self._decode(infile, encoding)
1247
# No BOM discovered and no encoding specified, just return
1248
if isinstance(infile, StringTypes):
1249
# infile read from a file will be a single string
1250
return infile.splitlines(True)
1254
def _a_to_u(self, string):
1255
"""Decode ascii strings to unicode if a self.encoding is specified."""
1256
if not self.encoding:
1259
return string.decode('ascii')
1261
def _decode(self, infile, encoding):
1263
Decode infile to unicode. Using the specified encoding.
1265
if is a string, it also needs converting to a list.
1267
if isinstance(infile, StringTypes):
1269
# NOTE: Could raise a ``UnicodeDecodeError``
1270
return infile.decode(encoding).splitlines(True)
1271
for i, line in enumerate(infile):
1272
if not isinstance(line, unicode):
1273
# NOTE: The isinstance test here handles mixed lists of unicode/string
1274
# NOTE: But the decode will break on any non-string values
1275
# NOTE: Or could raise a ``UnicodeDecodeError``
1276
infile[i] = line.decode(encoding)
1279
def _decode_element(self, line):
1280
"""Decode element to unicode if necessary."""
1281
if not self.encoding:
1283
if isinstance(line, str) and self.default_encoding:
1284
return line.decode(self.default_encoding)
1287
def _str(self, value):
1289
Used by ``stringify`` within validate, to turn non-string values
1292
if not isinstance(value, StringTypes):
1297
def _parse(self, infile):
1299
Actually parse the config file
1301
Testing Interpolation
1304
>>> c['DEFAULT'] = {
1306
... 'userdir': 'c:\\\\home',
1310
>>> c['section'] = {
1311
... 'a': '%(datadir)s\\\\some path\\\\file.py',
1312
... 'b': '%(userdir)s\\\\some path\\\\file.py',
1313
... 'c': 'Yo %(a)s',
1314
... 'd': '%(not_here)s',
1317
>>> c['section']['DEFAULT'] = {
1318
... 'datadir': 'c:\\\\silly_test',
1319
... 'a': 'hello - %(b)s',
1321
>>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
1323
>>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
1325
>>> c['section']['c'] == 'Yo hello - goodbye'
1328
Switching Interpolation Off
1330
>>> c.interpolation = False
1331
>>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
1333
>>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
1335
>>> c['section']['c'] == 'Yo %(a)s'
1338
Testing the interpolation errors.
1340
>>> c.interpolation = True
1341
>>> c['section']['d']
1342
Traceback (most recent call last):
1343
MissingInterpolationOption: missing option "not_here" in interpolation.
1344
>>> c['section']['e']
1345
Traceback (most recent call last):
1346
InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
1348
Testing our quoting.
1350
>>> i._quote('\"""\'\'\'')
1351
Traceback (most recent call last):
1352
SyntaxError: EOF while scanning triple-quoted string
1354
... i._quote('\\n', multiline=False)
1355
... except ConfigObjError, e:
1357
'Value "\\n" cannot be safely quoted.'
1358
>>> k._quote(' "\' ', multiline=False)
1359
Traceback (most recent call last):
1360
SyntaxError: EOL while scanning single-quoted string
1362
Testing with "stringify" off.
1363
>>> c.stringify = False
1365
Traceback (most recent call last):
1366
TypeError: Value is not a string "1".
1368
Testing Empty values.
1369
>>> cfg_with_empty = '''
1371
... k2 =# comment test
1376
... val5 = 1, 2, '''.splitlines()
1377
>>> cwe = ConfigObj(cfg_with_empty)
1378
>>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [],
1379
... 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']}
1381
>>> cwe = ConfigObj(cfg_with_empty, list_values=False)
1382
>>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',',
1383
... 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'}
1386
temp_list_values = self.list_values
1388
self.list_values = False
1392
maxline = len(infile) - 1
1394
reset_comment = False
1395
while cur_index < maxline:
1399
line = infile[cur_index]
1400
sline = line.strip()
1401
# do we have anything on the line ?
1402
if not sline or sline.startswith('#'):
1403
reset_comment = False
1404
comment_list.append(line)
1407
# preserve initial comment
1408
self.initial_comment = comment_list
1411
reset_comment = True
1412
# first we check if it's a section marker
1413
mat = self._sectionmarker.match(line)
1414
## print >> sys.stderr, sline, mat
1417
(indent, sect_open, sect_name, sect_close, comment) = (
1419
if indent and (self.indent_type is None):
1420
self.indent_type = indent[0]
1421
cur_depth = sect_open.count('[')
1422
if cur_depth != sect_close.count(']'):
1424
"Cannot compute the section depth at line %s.",
1425
NestingError, infile, cur_index)
1427
if cur_depth < this_section.depth:
1428
# the new section is dropping back to a previous level
1430
parent = self._match_depth(
1435
"Cannot compute nesting level at line %s.",
1436
NestingError, infile, cur_index)
1438
elif cur_depth == this_section.depth:
1439
# the new section is a sibling of the current section
1440
parent = this_section.parent
1441
elif cur_depth == this_section.depth + 1:
1442
# the new section is a child the current section
1443
parent = this_section
1446
"Section too nested at line %s.",
1447
NestingError, infile, cur_index)
1449
sect_name = self._unquote(sect_name)
1450
if parent.has_key(sect_name):
1451
## print >> sys.stderr, sect_name
1453
'Duplicate section name at line %s.',
1454
DuplicateError, infile, cur_index)
1456
# create the new section
1457
this_section = Section(
1462
parent[sect_name] = this_section
1463
parent.inline_comments[sect_name] = comment
1464
parent.comments[sect_name] = comment_list
1465
## print >> sys.stderr, parent[sect_name] is this_section
1468
# it's not a section marker,
1469
# so it should be a valid ``key = value`` line
1470
mat = self._keyword.match(line)
1471
## print >> sys.stderr, sline, mat
1473
# is a keyword value
1474
# value will include any inline comment
1475
(indent, key, value) = mat.groups()
1476
if indent and (self.indent_type is None):
1477
self.indent_type = indent[0]
1478
# check for a multiline value
1479
if value[:3] in ['"""', "'''"]:
1481
(value, comment, cur_index) = self._multiline(
1482
value, infile, cur_index, maxline)
1485
'Parse error in value at line %s.',
1486
ParseError, infile, cur_index)
1490
value = unrepr(value)
1492
# extract comment and lists
1494
(value, comment) = self._handle_value(value)
1497
'Parse error in value at line %s.',
1498
ParseError, infile, cur_index)
1501
## print >> sys.stderr, sline
1502
key = self._unquote(key)
1503
if this_section.has_key(key):
1505
'Duplicate keyword name at line %s.',
1506
DuplicateError, infile, cur_index)
1509
## print >> sys.stderr, this_section.name
1510
# we set unrepr because if we have got this far we will never
1511
# be creating a new section
1512
this_section.__setitem__(key, value, unrepr=True)
1513
this_section.inline_comments[key] = comment
1514
this_section.comments[key] = comment_list
1515
## print >> sys.stderr, key, this_section[key]
1516
## if this_section.name is not None:
1517
## print >> sys.stderr, this_section
1518
## print >> sys.stderr, this_section.parent
1519
## print >> sys.stderr, this_section.parent[this_section.name]
1522
# it neither matched as a keyword
1523
# or a section marker
1525
'Invalid line at line "%s".',
1526
ParseError, infile, cur_index)
1527
if self.indent_type is None:
1528
# no indentation used, set the type accordingly
1529
self.indent_type = ''
1530
# preserve the final comment
1531
if not self and not self.initial_comment:
1532
self.initial_comment = comment_list
1533
elif not reset_comment:
1534
self.final_comment = comment_list
1535
self.list_values = temp_list_values
1537
def _match_depth(self, sect, depth):
1539
Given a section and a depth level, walk back through the sections
1540
parents to see if the depth level matches a previous section.
1542
Return a reference to the right section,
1543
or raise a SyntaxError.
1545
while depth < sect.depth:
1546
if sect is sect.parent:
1547
# we've reached the top level already
1550
if sect.depth == depth:
1552
# shouldn't get here
1555
def _handle_error(self, text, ErrorClass, infile, cur_index):
1557
Handle an error according to the error settings.
1559
Either raise the error or store it.
1560
The error will have occured at ``cur_index``
1562
line = infile[cur_index]
1563
message = text % cur_index
1564
error = ErrorClass(message, cur_index, line)
1565
if self.raise_errors:
1566
# raise the error - parsing stops here
1569
# reraise when parsing has finished
1570
self._errors.append(error)
1572
def _unquote(self, value):
1573
"""Return an unquoted version of a value"""
1574
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1578
def _quote(self, value, multiline=True):
1580
Return a safely quoted version of a value.
1582
Raise a ConfigObjError if the value cannot be safely quoted.
1583
If multiline is ``True`` (default) then use triple quotes
1586
Don't quote values that don't need it.
1587
Recursively quote members of a list and return a comma joined list.
1588
Multiline is ``False`` for lists.
1589
Obey list syntax for empty and single member lists.
1591
If ``list_values=False`` then the value is only quoted if it contains
1592
a ``\n`` (is multiline).
1594
If ``write_empty_values`` is set, and the value is an empty string, it
1597
if multiline and self.write_empty_values and value == '':
1598
# Only if multiline is set, so that it is used for values not
1599
# keys, and not values that are part of a list
1601
if multiline and isinstance(value, (list, tuple)):
1604
elif len(value) == 1:
1605
return self._quote(value[0], multiline=False) + ','
1606
return ', '.join([self._quote(val, multiline=False)
1608
if not isinstance(value, StringTypes):
1612
raise TypeError, 'Value "%s" is not a string.' % value
1616
wspace_plus = ' \r\t\n\v\t\'"'
1621
if (not self.list_values and '\n' not in value) or not (multiline and
1622
((("'" in value) and ('"' in value)) or ('\n' in value))):
1623
if not self.list_values:
1624
# we don't quote if ``list_values=False``
1626
# for normal values either single or double quotes will do
1628
# will only happen if multiline is off - e.g. '\n' in key
1629
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1631
elif ((value[0] not in wspace_plus) and
1632
(value[-1] not in wspace_plus) and
1633
(',' not in value)):
1636
if ("'" in value) and ('"' in value):
1637
raise ConfigObjError, (
1638
'Value "%s" cannot be safely quoted.' % value)
1644
# if value has '\n' or "'" *and* '"', it will need triple quotes
1645
if (value.find('"""') != -1) and (value.find("'''") != -1):
1646
raise ConfigObjError, (
1647
'Value "%s" cannot be safely quoted.' % value)
1648
if value.find('"""') == -1:
1654
def _handle_value(self, value):
1656
Given a value string, unquote, remove comment,
1657
handle lists. (including empty and single member lists)
1659
Testing list values.
1661
>>> testconfig3 = '''
1664
... c = test1, test2 , test3
1665
... d = test1, test2, test3,
1667
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1670
>>> d['b'] == ['test']
1672
>>> d['c'] == ['test1', 'test2', 'test3']
1674
>>> d['d'] == ['test1', 'test2', 'test3']
1677
Testing with list values off.
1680
... testconfig3.split('\\n'),
1681
... raise_errors=True,
1682
... list_values=False)
1685
>>> e['b'] == 'test,'
1687
>>> e['c'] == 'test1, test2 , test3'
1689
>>> e['d'] == 'test1, test2, test3,'
1692
Testing creating from a dictionary.
1715
>>> g = ConfigObj(f)
1719
Testing we correctly detect badly built list values (4 of them).
1721
>>> testconfig4 = '''
1725
... dummy = ,,hello, goodbye
1728
... ConfigObj(testconfig4.split('\\n'))
1729
... except ConfigObjError, e:
1733
Testing we correctly detect badly quoted values (4 of them).
1735
>>> testconfig5 = '''
1736
... config = "hello # comment
1738
... fish = 'goodbye # comment
1739
... dummy = "hello again
1742
... ConfigObj(testconfig5.split('\\n'))
1743
... except ConfigObjError, e:
1747
# do we look for lists in values ?
1748
if not self.list_values:
1749
mat = self._nolistvalue.match(value)
1752
(value, comment) = mat.groups()
1754
# NOTE: we don't unquote here
1755
return (value, comment)
1757
return (unrepr(value), comment)
1758
mat = self._valueexp.match(value)
1760
# the value is badly constructed, probably badly quoted,
1761
# or an invalid list
1763
(list_values, single, empty_list, comment) = mat.groups()
1764
if (list_values == '') and (single is None):
1765
# change this if you want to accept empty values
1767
# NOTE: note there is no error handling from here if the regex
1768
# is wrong: then incorrect values will slip through
1769
if empty_list is not None:
1770
# the single comma - meaning an empty list
1771
return ([], comment)
1772
if single is not None:
1773
# handle empty values
1774
if list_values and not single:
1775
# FIXME: the '' is a workaround because our regex now matches
1776
# '' at the end of a list if it has a trailing comma
1779
single = single or '""'
1780
single = self._unquote(single)
1781
if list_values == '':
1783
return (single, comment)
1784
the_list = self._listvalueexp.findall(list_values)
1785
the_list = [self._unquote(val) for val in the_list]
1786
if single is not None:
1787
the_list += [single]
1788
return (the_list, comment)
1790
def _multiline(self, value, infile, cur_index, maxline):
1792
Extract the value, where we are in a multiline situation
1794
Testing multiline values.
1797
... 'name4': ' another single line value ',
1798
... 'multi section': {
1799
... 'name4': '\\n Well, this is a\\n multiline '
1801
... 'name2': '\\n Well, this is a\\n multiline '
1803
... 'name3': '\\n Well, this is a\\n multiline '
1805
... 'name1': '\\n Well, this is a\\n multiline '
1808
... 'name2': ' another single line value ',
1809
... 'name3': ' a single line value ',
1810
... 'name1': ' a single line value ',
1815
newvalue = value[3:]
1816
single_line = self._triple_quote[quot][0]
1817
multi_line = self._triple_quote[quot][1]
1818
mat = single_line.match(value)
1820
retval = list(mat.groups())
1821
retval.append(cur_index)
1823
elif newvalue.find(quot) != -1:
1824
# somehow the triple quote is missing
1827
while cur_index < maxline:
1830
line = infile[cur_index]
1831
if line.find(quot) == -1:
1834
# end of multiline, process it
1837
# we've got to the end of the config, oops...
1839
mat = multi_line.match(line)
1841
# a badly formed line
1843
(value, comment) = mat.groups()
1844
return (newvalue + value, comment, cur_index)
1846
def _handle_configspec(self, configspec):
1847
"""Parse the configspec."""
1848
# FIXME: Should we check that the configspec was created with the
1849
# correct settings ? (i.e. ``list_values=False``)
1850
if not isinstance(configspec, ConfigObj):
1852
configspec = ConfigObj(
1857
except ConfigObjError, e:
1858
# FIXME: Should these errors have a reference
1859
# to the already parsed ConfigObj ?
1860
raise ConfigspecError('Parsing configspec failed: %s' % e)
1862
raise IOError('Reading configspec failed: %s' % e)
1863
self._set_configspec_value(configspec, self)
1865
def _set_configspec_value(self, configspec, section):
1866
"""Used to recursively set configspec values."""
1867
if '__many__' in configspec.sections:
1868
section.configspec['__many__'] = configspec['__many__']
1869
if len(configspec.sections) > 1:
1870
# FIXME: can we supply any useful information here ?
1871
raise RepeatSectionError
1872
if hasattr(configspec, 'initial_comment'):
1873
section._configspec_initial_comment = configspec.initial_comment
1874
section._configspec_final_comment = configspec.final_comment
1875
section._configspec_encoding = configspec.encoding
1876
section._configspec_BOM = configspec.BOM
1877
section._configspec_newlines = configspec.newlines
1878
section._configspec_indent_type = configspec.indent_type
1879
for entry in configspec.scalars:
1880
section._configspec_comments[entry] = configspec.comments[entry]
1881
section._configspec_inline_comments[entry] = (
1882
configspec.inline_comments[entry])
1883
section.configspec[entry] = configspec[entry]
1884
section._order.append(entry)
1885
for entry in configspec.sections:
1886
if entry == '__many__':
1888
section._cs_section_comments[entry] = configspec.comments[entry]
1889
section._cs_section_inline_comments[entry] = (
1890
configspec.inline_comments[entry])
1891
if not section.has_key(entry):
1893
self._set_configspec_value(configspec[entry], section[entry])
1895
def _handle_repeat(self, section, configspec):
1896
"""Dynamically assign configspec for repeated section."""
1898
section_keys = configspec.sections
1899
scalar_keys = configspec.scalars
1900
except AttributeError:
1901
section_keys = [entry for entry in configspec
1902
if isinstance(configspec[entry], dict)]
1903
scalar_keys = [entry for entry in configspec
1904
if not isinstance(configspec[entry], dict)]
1905
if '__many__' in section_keys and len(section_keys) > 1:
1906
# FIXME: can we supply any useful information here ?
1907
raise RepeatSectionError
1910
for entry in scalar_keys:
1911
val = configspec[entry]
1912
scalars[entry] = val
1913
for entry in section_keys:
1914
val = configspec[entry]
1915
if entry == '__many__':
1916
scalars[entry] = val
1918
sections[entry] = val
1920
section.configspec = scalars
1921
for entry in sections:
1922
if not section.has_key(entry):
1924
self._handle_repeat(section[entry], sections[entry])
1926
def _write_line(self, indent_string, entry, this_entry, comment):
1927
"""Write an individual line, for the write method"""
1928
# NOTE: the calls to self._quote here handles non-StringType values.
1930
val = self._decode_element(self._quote(this_entry))
1932
val = repr(this_entry)
1933
return '%s%s%s%s%s' % (
1935
self._decode_element(self._quote(entry, multiline=False)),
1936
self._a_to_u(' = '),
1938
self._decode_element(comment))
1940
def _write_marker(self, indent_string, depth, entry, comment):
1941
"""Write a section marker line"""
1942
return '%s%s%s%s%s' % (
1944
self._a_to_u('[' * depth),
1945
self._quote(self._decode_element(entry), multiline=False),
1946
self._a_to_u(']' * depth),
1947
self._decode_element(comment))
1949
def _handle_comment(self, comment):
1951
Deal with a comment.
1953
>>> filename = a.filename
1954
>>> a.filename = None
1955
>>> values = a.write()
1957
>>> while index < 23:
1959
... line = values[index-1]
1960
... assert line.endswith('# comment ' + str(index))
1961
>>> a.filename = filename
1963
>>> start_comment = ['# Initial Comment', '', '#']
1964
>>> end_comment = ['', '#', '# Final Comment']
1965
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1966
>>> nc = ConfigObj(newconfig)
1967
>>> nc.initial_comment
1968
['# Initial Comment', '', '#']
1969
>>> nc.final_comment
1970
['', '#', '# Final Comment']
1971
>>> nc.initial_comment == start_comment
1973
>>> nc.final_comment == end_comment
1978
if self.indent_type == '\t':
1979
start = self._a_to_u('\t')
1981
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1982
if not comment.startswith('#'):
1983
start += self._a_to_u('# ')
1984
return (start + comment)
1986
def _compute_indent_string(self, depth):
1988
Compute the indent string, according to current indent_type and depth
1990
if self.indent_type == '':
1991
# no indentation at all
1993
if self.indent_type == '\t':
1995
if self.indent_type == ' ':
1996
return ' ' * NUM_INDENT_SPACES * depth
2001
def write(self, outfile=None, section=None):
2003
Write the current ConfigObj as a file
2005
tekNico: FIXME: use StringIO instead of real files
2007
>>> filename = a.filename
2008
>>> a.filename = 'test.ini'
2010
>>> a.filename = filename
2011
>>> a == ConfigObj('test.ini', raise_errors=True)
2013
>>> os.remove('test.ini')
2014
>>> b.filename = 'test.ini'
2016
>>> b == ConfigObj('test.ini', raise_errors=True)
2018
>>> os.remove('test.ini')
2019
>>> i.filename = 'test.ini'
2021
>>> i == ConfigObj('test.ini', raise_errors=True)
2023
>>> os.remove('test.ini')
2025
>>> a['DEFAULT'] = {'a' : 'fish'}
2026
>>> a['a'] = '%(a)s'
2028
['a = %(a)s', '[DEFAULT]', 'a = fish']
2030
if self.indent_type is None:
2031
# this can be true if initialised from a dictionary
2032
self.indent_type = DEFAULT_INDENT_TYPE
2035
cs = self._a_to_u('#')
2036
csp = self._a_to_u('# ')
2038
int_val = self.interpolation
2039
self.interpolation = False
2041
for line in self.initial_comment:
2042
line = self._decode_element(line)
2043
stripped_line = line.strip()
2044
if stripped_line and not stripped_line.startswith(cs):
2048
indent_string = self._a_to_u(
2049
self._compute_indent_string(section.depth))
2050
for entry in (section.scalars + section.sections):
2051
if entry in section.defaults:
2052
# don't write out default values
2054
for comment_line in section.comments[entry]:
2055
comment_line = self._decode_element(comment_line.lstrip())
2056
if comment_line and not comment_line.startswith(cs):
2057
comment_line = csp + comment_line
2058
out.append(indent_string + comment_line)
2059
this_entry = section[entry]
2060
comment = self._handle_comment(section.inline_comments[entry])
2062
if isinstance(this_entry, dict):
2064
out.append(self._write_marker(
2069
out.extend(self.write(section=this_entry))
2071
out.append(self._write_line(
2078
for line in self.final_comment:
2079
line = self._decode_element(line)
2080
stripped_line = line.strip()
2081
if stripped_line and not stripped_line.startswith(cs):
2084
self.interpolation = int_val
2086
if section is not self:
2089
if (self.filename is None) and (outfile is None):
2090
# output a list of lines
2091
# might need to encode
2092
# NOTE: This will *screw* UTF16, each line will start with the BOM
2094
out = [l.encode(self.encoding) for l in out]
2095
if (self.BOM and ((self.encoding is None) or
2096
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2100
out[0] = BOM_UTF8 + out[0]
2103
# Turn the list to a string, joined with correct newlines
2104
output = (self._a_to_u(self.newlines or os.linesep)
2107
output = output.encode(self.encoding)
2108
if (self.BOM and ((self.encoding is None) or
2109
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2111
output = BOM_UTF8 + output
2112
if outfile is not None:
2113
outfile.write(output)
2115
h = open(self.filename, 'wb')
2119
def validate(self, validator, preserve_errors=False, copy=False,
2122
Test the ConfigObj against a configspec.
2124
It uses the ``validator`` object from *validate.py*.
2126
To run ``validate`` on the current ConfigObj, call: ::
2128
test = config.validate(validator)
2130
(Normally having previously passed in the configspec when the ConfigObj
2131
was created - you can dynamically assign a dictionary of checks to the
2132
``configspec`` attribute of a section though).
2134
It returns ``True`` if everything passes, or a dictionary of
2135
pass/fails (True/False). If every member of a subsection passes, it
2136
will just have the value ``True``. (It also returns ``False`` if all
2139
In addition, it converts the values from strings to their native
2140
types if their checks pass (and ``stringify`` is set).
2142
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2143
of a marking a fail with a ``False``, it will preserve the actual
2144
exception object. This can contain info about the reason for failure.
2145
For example the ``VdtValueTooSmallError`` indeicates that the value
2146
supplied was too small. If a value (or section) is missing it will
2147
still be marked as ``False``.
2149
You must have the validate module to use ``preserve_errors=True``.
2151
You can then use the ``flatten_errors`` function to turn your nested
2152
results dictionary into a flattened list of failures - useful for
2153
displaying meaningful error messages.
2156
... from validate import Validator
2157
... except ImportError:
2158
... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
2175
... '''.split('\\n')
2176
... configspec = '''
2177
... test1= integer(30,50)
2180
... test4=float(6.0)
2182
... test1=integer(30,50)
2185
... test4=float(6.0)
2187
... test1=integer(30,50)
2190
... test4=float(6.0)
2191
... '''.split('\\n')
2192
... val = Validator()
2193
... c1 = ConfigObj(config, configspec=configspec)
2194
... test = c1.validate(val)
2205
... 'sub section': {
2214
>>> val.check(c1.configspec['test4'], c1['test4'])
2215
Traceback (most recent call last):
2216
VdtValueTooSmallError: the value "5.0" is too small.
2218
>>> val_test_config = '''
2223
... key2 = 1.1, 3.0, 17, 6.8
2226
... key2 = True'''.split('\\n')
2227
>>> val_test_configspec = '''
2232
... key2 = float_list(4)
2234
... key = option(option1, option2)
2235
... key2 = boolean'''.split('\\n')
2236
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
2237
>>> val_test.validate(val)
2239
>>> val_test['key'] = 'text not a digit'
2240
>>> val_res = val_test.validate(val)
2241
>>> val_res == {'key2': True, 'section': True, 'key': False}
2243
>>> configspec = '''
2244
... test1=integer(30,50, default=40)
2245
... test2=string(default="hello")
2246
... test3=integer(default=3)
2247
... test4=float(6.0, default=6.0)
2249
... test1=integer(30,50, default=40)
2250
... test2=string(default="hello")
2251
... test3=integer(default=3)
2252
... test4=float(6.0, default=6.0)
2254
... test1=integer(30,50, default=40)
2255
... test2=string(default="hello")
2256
... test3=integer(default=3)
2257
... test4=float(6.0, default=6.0)
2258
... '''.split('\\n')
2259
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2261
{'test1': '30', 'section': {'sub section': {}}}
2262
>>> default_test.validate(val)
2264
>>> default_test == {
2266
... 'test2': 'hello',
2271
... 'test2': 'hello',
2274
... 'sub section': {
2277
... 'test2': 'hello',
2284
Now testing with repeated sections : BIG TEST
2286
>>> repeated_1 = '''
2288
... [[__many__]] # spec for a dog
2289
... fleas = boolean(default=True)
2290
... tail = option(long, short, default=long)
2291
... name = string(default=rover)
2292
... [[[__many__]]] # spec for a puppy
2293
... name = string(default="son of rover")
2294
... age = float(default=0.0)
2296
... [[__many__]] # spec for a cat
2297
... fleas = boolean(default=True)
2298
... tail = option(long, short, default=short)
2299
... name = string(default=pussy)
2300
... [[[__many__]]] # spec for a kitten
2301
... name = string(default="son of pussy")
2302
... age = float(default=0.0)
2303
... '''.split('\\n')
2304
>>> repeated_2 = '''
2307
... # blank dogs with puppies
2308
... # should be filled in by the configspec
2323
... # blank cats with kittens
2324
... # should be filled in by the configspec
2337
... '''.split('\\n')
2338
>>> repeated_3 = '''
2349
... '''.split('\\n')
2350
>>> repeated_4 = '''
2353
... name = string(default=Michael)
2354
... age = float(default=0.0)
2355
... sex = option(m, f, default=m)
2356
... '''.split('\\n')
2357
>>> repeated_5 = '''
2360
... fleas = boolean(default=True)
2361
... tail = option(long, short, default=short)
2362
... name = string(default=pussy)
2363
... [[[description]]]
2364
... height = float(default=3.3)
2365
... weight = float(default=6)
2367
... fur = option(black, grey, brown, "tortoise shell", default=black)
2368
... condition = integer(0,10, default=5)
2369
... '''.split('\\n')
2370
>>> from validate import Validator
2371
>>> val= Validator()
2372
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
2373
>>> repeater.validate(val)
2380
... 'name': 'rover',
2381
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2382
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2383
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2388
... 'name': 'rover',
2389
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2390
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2391
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2396
... 'name': 'rover',
2397
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2398
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2399
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2405
... 'tail': 'short',
2406
... 'name': 'pussy',
2407
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2408
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2409
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2413
... 'tail': 'short',
2414
... 'name': 'pussy',
2415
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2416
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2417
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2421
... 'tail': 'short',
2422
... 'name': 'pussy',
2423
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2424
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2425
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2430
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2431
>>> repeater.validate(val)
2435
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2436
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2437
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2440
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2441
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2442
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2446
>>> repeater = ConfigObj(configspec=repeated_4)
2447
>>> repeater['Michael'] = {}
2448
>>> repeater.validate(val)
2451
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2454
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2456
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2457
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2460
>>> repeater.validate(val)
2463
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2467
... 'tail': 'short',
2468
... 'name': 'pussy',
2469
... 'description': {
2471
... 'height': 3.2999999999999998,
2472
... 'coat': {'fur': 'black', 'condition': 5},
2477
... 'tail': 'short',
2478
... 'name': 'pussy',
2479
... 'description': {
2481
... 'height': 3.2999999999999998,
2482
... 'coat': {'fur': 'black', 'condition': 5},
2487
... 'tail': 'short',
2488
... 'name': 'pussy',
2489
... 'description': {
2491
... 'height': 3.2999999999999998,
2492
... 'coat': {'fur': 'black', 'condition': 5},
2499
Test that interpolation is preserved for validated string values.
2500
Also check that interpolation works in configspecs.
2502
>>> t['DEFAULT'] = {}
2503
>>> t['DEFAULT']['test'] = 'a'
2504
>>> t['test'] = '%(test)s'
2508
>>> t.configspec = {'test': 'string'}
2511
>>> t.interpolation = False
2513
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2515
... 'interpolated string = string(default="fuzzy-%(man)s")',
2519
>>> c = ConfigObj(configspec=specs)
2522
>>> c['interpolated string']
2525
FIXME: Above tests will fail if we couldn't import Validator (the ones
2526
that don't raise errors will produce different output and still fail as
2532
if self.configspec is None:
2533
raise ValueError, 'No configspec supplied.'
2535
if VdtMissingValue is None:
2536
raise ImportError('Missing validate module.')
2539
spec_section = section.configspec
2540
if copy and hasattr(section, '_configspec_initial_comment'):
2541
section.initial_comment = section._configspec_initial_comment
2542
section.final_comment = section._configspec_final_comment
2543
section.encoding = section._configspec_encoding
2544
section.BOM = section._configspec_BOM
2545
section.newlines = section._configspec_newlines
2546
section.indent_type = section._configspec_indent_type
2547
if '__many__' in section.configspec:
2548
many = spec_section['__many__']
2549
# dynamically assign the configspecs
2550
# for the sections below
2551
for entry in section.sections:
2552
self._handle_repeat(section[entry], many)
2557
order = [k for k in section._order if k in spec_section]
2558
order += [k for k in spec_section if k not in order]
2560
if entry == '__many__':
2562
if (not entry in section.scalars) or (entry in section.defaults):
2564
# or entries from defaults
2567
if copy and not entry in section.scalars:
2569
section.comments[entry] = (
2570
section._configspec_comments.get(entry, []))
2571
section.inline_comments[entry] = (
2572
section._configspec_inline_comments.get(entry, ''))
2576
val = section[entry]
2578
check = validator.check(spec_section[entry],
2582
except validator.baseErrorClass, e:
2583
if not preserve_errors or isinstance(e, VdtMissingValue):
2586
# preserve the error
2593
if self.stringify or missing:
2594
# if we are doing type conversion
2595
# or the value is a supplied default
2596
if not self.stringify:
2597
if isinstance(check, (list, tuple)):
2599
check = [self._str(item) for item in check]
2600
elif missing and check is None:
2601
# convert the None from a default to a ''
2604
check = self._str(check)
2605
if (check != val) or missing:
2606
section[entry] = check
2607
if not copy and missing and entry not in section.defaults:
2608
section.defaults.append(entry)
2610
# Missing sections will have been created as empty ones when the
2611
# configspec was read.
2612
for entry in section.sections:
2613
# FIXME: this means DEFAULT is not copied in copy mode
2614
if section is self and entry == 'DEFAULT':
2617
section.comments[entry] = section._cs_section_comments[entry]
2618
section.inline_comments[entry] = (
2619
section._cs_section_inline_comments[entry])
2620
check = self.validate(validator, preserve_errors=preserve_errors,
2621
copy=copy, section=section[entry])
2638
class SimpleVal(object):
2641
Can be used to check that all members expected are present.
2643
To use it, provide a configspec with all your members in (the value given
2644
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2645
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2646
members are present, or a dictionary with True/False meaning
2647
present/missing. (Whole missing sections will be replaced with ``False``)
2649
>>> val = SimpleVal()
2665
... '''.split('\\n')
2666
>>> configspec = '''
2681
... '''.split('\\n')
2682
>>> o = ConfigObj(config, configspec=configspec)
2685
>>> o = ConfigObj(configspec=configspec)
2691
self.baseErrorClass = ConfigObjError
2693
def check(self, check, member, missing=False):
2694
"""A dummy check method, always returns the value unchanged."""
2696
raise self.baseErrorClass
2699
# Check / processing functions for options
2700
def flatten_errors(cfg, res, levels=None, results=None):
2702
An example function that will turn a nested dictionary of results
2703
(as returned by ``ConfigObj.validate``) into a flat list.
2705
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2706
dictionary returned by ``validate``.
2708
(This is a recursive function, so you shouldn't use the ``levels`` or
2709
``results`` arguments - they are used by the function.
2711
Returns a list of keys that failed. Each member of the list is a tuple :
2714
([list of sections...], key, result)
2716
If ``validate`` was called with ``preserve_errors=False`` (the default)
2717
then ``result`` will always be ``False``.
2719
*list of sections* is a flattened list of sections that the key was found
2722
If the section was missing then key will be ``None``.
2724
If the value (or section) was missing then ``result`` will be ``False``.
2726
If ``validate`` was called with ``preserve_errors=True`` and a value
2727
was present, but failed the check, then ``result`` will be the exception
2728
object returned. You can use this as a string that describes the failure.
2730
For example *The value "3" is of the wrong type*.
2732
# FIXME: is the ordering of the output arbitrary ?
2734
>>> vtor = validate.Validator()
2740
... another_option = Probably
2742
... another_option = True
2749
... option1 = boolean()
2750
... option2 = boolean()
2751
... option3 = boolean(default=Bad_value)
2753
... option1 = boolean()
2754
... option2 = boolean()
2755
... option3 = boolean(default=Bad_value)
2757
... another_option = boolean()
2759
... another_option = boolean()
2762
... value2 = integer
2763
... value3 = integer(0, 10)
2764
... [[[section3b-sub]]]
2767
... another_option = boolean()
2769
>>> cs = my_cfg.split('\\n')
2770
>>> ini = my_ini.split('\\n')
2771
>>> cfg = ConfigObj(ini, configspec=cs)
2772
>>> res = cfg.validate(vtor, preserve_errors=True)
2774
>>> for entry in flatten_errors(cfg, res):
2775
... section_list, key, error = entry
2776
... section_list.insert(0, '[root]')
2777
... if key is not None:
2778
... section_list.append(key)
2780
... section_list.append('[missing]')
2781
... section_string = ', '.join(section_list)
2782
... errors.append((section_string, ' = ', error))
2784
>>> for entry in errors:
2785
... print entry[0], entry[1], (entry[2] or 0)
2787
[root], option3 = the value "Bad_value" is of the wrong type.
2788
[root], section1, option2 = 0
2789
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2790
[root], section2, another_option = the value "Probably" is of the wrong type.
2791
[root], section3, section3b, section3b-sub, [missing] = 0
2792
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2793
[root], section3, section3b, value3 = the value "11" is too big.
2794
[root], section4, [missing] = 0
2803
results.append((levels[:], None, False))
2807
for (key, val) in res.items():
2810
if isinstance(cfg.get(key), dict):
2813
flatten_errors(cfg[key], val, levels, results)
2815
results.append((levels[:], key, val))
2824
# FIXME: test error code for badly built multiline values
2825
# FIXME: test handling of StringIO
2826
# FIXME: test interpolation with writing
2830
Dummy function to hold some of the doctests.
2867
... 'keys11': 'val1',
2868
... 'keys13': 'val3',
2869
... 'keys12': 'val2',
2872
... 'section 2 sub 1': {
2875
... 'keys21': 'val1',
2876
... 'keys22': 'val2',
2877
... 'keys23': 'val3',
2882
... 'a' = b # !"$%^&*(),::;'@~#= 33
2883
... "b" = b #= 6, 33
2884
... ''' .split('\\n')
2885
>>> t2 = ConfigObj(t)
2886
>>> assert t2 == {'a': 'b', 'b': 'b'}
2887
>>> t2.inline_comments['b'] = ''
2889
>>> assert t2.write() == ['','b = b', '']
2891
# Test ``list_values=False`` stuff
2893
... key1 = no quotes
2894
... key2 = 'single quotes'
2895
... key3 = "double quotes"
2896
... key4 = "list", 'with', several, "quotes"
2898
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2899
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2900
... 'key3': '"double quotes"',
2901
... 'key4': '"list", \\'with\\', several, "quotes"'
2904
>>> cfg = ConfigObj(list_values=False)
2905
>>> cfg['key1'] = 'Multiline\\nValue'
2906
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2908
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2909
>>> cfg.list_values = True
2910
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2911
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2914
Test flatten_errors:
2916
>>> from validate import Validator, VdtValueTooSmallError
2932
... '''.split('\\n')
2933
>>> configspec = '''
2934
... test1= integer(30,50)
2937
... test4=float(6.0)
2939
... test1=integer(30,50)
2942
... test4=float(6.0)
2944
... test1=integer(30,50)
2947
... test4=float(6.0)
2948
... '''.split('\\n')
2949
>>> val = Validator()
2950
>>> c1 = ConfigObj(config, configspec=configspec)
2951
>>> res = c1.validate(val)
2952
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2953
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2955
>>> res = c1.validate(val, preserve_errors=True)
2956
>>> check = flatten_errors(c1, res)
2960
(['section', 'sub section'], 'test4')
2962
(['section'], 'test4')
2963
>>> for entry in check:
2964
... isinstance(entry[2], VdtValueTooSmallError)
2965
... print str(entry[2])
2967
the value "5.0" is too small.
2969
the value "5.0" is too small.
2971
the value "5.0" is too small.
2973
Test unicode handling, BOM, write witha file like object and line endings :
2975
... # initial comment
2976
... # inital comment 2
2978
... test1 = some value
2980
... test2 = another value # inline comment
2981
... # section comment
2982
... [section] # inline comment
2983
... test = test # another inline comment
2987
... # final comment2
2989
>>> u = u_base.encode('utf_8').splitlines(True)
2990
>>> u[0] = BOM_UTF8 + u[0]
2991
>>> uc = ConfigObj(u)
2992
>>> uc.encoding = None
2995
>>> uc == {'test1': 'some value', 'test2': 'another value',
2996
... 'section': {'test': 'test', 'test2': 'test2'}}
2998
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
3001
>>> isinstance(uc['test1'], unicode)
3007
>>> uc['latin1'] = "This costs lot's of "
3008
>>> a_list = uc.write()
3011
>>> isinstance(a_list[0], str)
3013
>>> a_list[0].startswith(BOM_UTF8)
3015
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
3016
>>> uc = ConfigObj(u)
3019
>>> uc.newlines = '\\r'
3020
>>> from cStringIO import StringIO
3021
>>> file_like = StringIO()
3022
>>> uc.write(file_like)
3023
>>> file_like.seek(0)
3024
>>> uc2 = ConfigObj(file_like)
3027
>>> uc2.filename == None
3029
>>> uc2.newlines == '\\r'
3032
Test validate in copy mode
3034
... # Initial Comment
3036
... key1 = string(default=Hello) # comment 1
3038
... # section comment
3039
... [section] # inline comment
3041
... key1 = integer(default=6) # an integer value
3043
... key2 = boolean(default=True) # a boolean
3045
... # subsection comment
3046
... [[sub-section]] # inline comment
3047
... # another key1 comment
3048
... key1 = float(default=3.0) # a float'''.splitlines()
3049
>>> b = ConfigObj(configspec=a)
3050
>>> b.validate(val, copy=True)
3052
>>> b.write() == ['',
3053
... '# Initial Comment',
3055
... 'key1 = Hello # comment 1',
3057
... '# section comment',
3058
... '[section] # inline comment',
3059
... ' # key1 comment',
3060
... ' key1 = 6 # an integer value',
3061
... ' # key2 comment',
3062
... ' key2 = True # a boolean',
3064
... ' # subsection comment',
3065
... ' [[sub-section]] # inline comment',
3066
... ' # another key1 comment',
3067
... ' key1 = 3.0 # a float']
3070
Test Writing Empty Values
3073
... key2 =# a comment'''
3074
>>> b = ConfigObj(a.splitlines())
3076
['', 'key1 = ""', 'key2 = "" # a comment']
3077
>>> b.write_empty_values = True
3079
['', 'key1 = ', 'key2 = # a comment']
3081
Test unrepr when reading
3083
... key1 = (1, 2, 3) # comment
3085
... key3 = 'a string'
3086
... key4 = [1, 2, 3, 'a mixed list']
3087
... '''.splitlines()
3088
>>> b = ConfigObj(a, unrepr=True)
3089
>>> b == {'key1': (1, 2, 3),
3091
... 'key3': 'a string',
3092
... 'key4': [1, 2, 3, 'a mixed list']}
3095
Test unrepr when writing
3096
>>> c = ConfigObj(b.write(), unrepr=True)
3100
Test unrepr with multiline values
3101
>>> a = '''k = \"""{
3104
... '''.splitlines()
3105
>>> c = ConfigObj(a, unrepr=True)
3106
>>> c == {'k': {'k1': 3, 'k2': 6.0}}
3109
Test unrepr with a dictionary
3110
>>> a = 'k = {"a": 1}'.splitlines()
3111
>>> c = ConfigObj(a, unrepr=True)
3112
>>> type(c['k']) == dict
3116
if __name__ == '__main__':
3117
# run the code tests in doctest format
3120
key1= val # comment 1
3121
key2= val # comment 2
3124
key1= val # comment 5
3125
key2= val # comment 6
3128
key1= val # comment 9
3129
key2= val # comment 10
3131
[[lev2ba]] # comment 12
3132
key1= val # comment 13
3134
[[lev2bb]] # comment 15
3135
key1= val # comment 16
3137
[lev1c] # comment 18
3139
[[lev2c]] # comment 20
3141
[[[lev3c]]] # comment 22
3142
key1 = val # comment 23"""
3148
["section 1"] # comment
3157
[['section 2 sub 1']]
3162
name1 = """ a single line value """ # comment
3163
name2 = \''' another single line value \''' # comment
3164
name3 = """ a single line value """
3165
name4 = \''' another single line value \'''
3182
\''' # I guess this is a comment too
3186
m = sys.modules.get('__main__')
3187
globs = m.__dict__.copy()
3188
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
3189
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
3190
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
3192
'INTP_VER': INTP_VER,
3197
doctest.testmod(m, globs=globs)
3208
Better support for configuration from multiple files, including tracking
3209
*where* the original file came from and writing changes to the correct
3212
Make ``newline`` an option (as well as an attribute) ?
3214
``UTF16`` encoded files, when returned as a list of lines, will have the
3215
BOM at the start of every line. Should this be removed from all but the
3218
Option to set warning type for unicode decode ? (Defaults to strict).
3220
A method to optionally remove uniform indentation from multiline values.
3221
(do as an example of using ``walk`` - along with string-escape)
3223
Should the results dictionary from validate be an ordered dictionary if
3224
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
3226
Implement a better ``__repr__`` ? (``ConfigObj({})``)
3228
Implement some of the sequence methods (which include slicing) from the
3234
There is currently no way to specify the encoding of a configspec.
3236
When using ``copy`` mode for validation, it won't copy ``DEFAULT``
3237
sections. This is so that you *can* use interpolation in configspec
3240
``validate`` doesn't report *extra* values or sections.
3242
You can't have a keyword with the same name as a section (in the same
3243
section). They are both dictionary keys - so they would overlap.
3245
ConfigObj doesn't quote and unquote values if ``list_values=False``.
3246
This means that leading or trailing whitespace in values will be lost when
3247
writing. (Unless you manually quote).
3249
Interpolation checks first the 'DEFAULT' subsection of the current
3250
section, next it checks the 'DEFAULT' section of the parent section,
3251
last it checks the 'DEFAULT' section of the main section.
3253
Logically a 'DEFAULT' section should apply to all subsections of the *same
3254
parent* - this means that checking the 'DEFAULT' subsection in the
3255
*current section* is not necessarily logical ?
3257
Does it matter that we don't support the ':' divider, which is supported
3258
by ``ConfigParser`` ?
3260
String interpolation and validation don't play well together. When
3261
validation changes type it sets the value. This will correctly fetch the
3262
value using interpolation - but then overwrite the interpolation reference.
3263
If the value is unchanged by validation (it's a string) - but other types
3270
List values allow you to specify multiple values for a keyword. This
3271
maps to a list as the resulting Python object when parsed.
3273
The syntax for lists is easy. A list is a comma separated set of values.
3274
If these values contain quotes, the hash mark, or commas, then the values
3275
can be surrounded by quotes. e.g. : ::
3277
keyword = value1, 'value 2', "value 3"
3279
If a value needs to be a list, but only has one member, then you indicate
3280
this with a trailing comma. e.g. : ::
3282
keyword = "single value",
3284
If a value needs to be a list, but it has no members, then you indicate
3285
this with a single comma. e.g. : ::
3287
keyword = , # an empty list
3289
Using triple quotes it will be possible for single values to contain
3290
newlines and *both* single quotes and double quotes. Triple quotes aren't
3291
allowed in list values. This means that the members of list values can't
3292
contain carriage returns (or line feeds :-) or both quote values.
3300
Empty values are now valid syntax. They are read as an empty string ``''``.
3301
(``key =``, or ``key = # comment``.)
3303
``validate`` now honours the order of the configspec.
3305
Added the ``copy`` mode to validate.
3307
Fixed bug where files written on windows could be given '\r\r\n' line
3310
Fixed bug where last occuring comment line could be interpreted as the
3311
final comment if the last line isn't terminated.
3313
Fixed bug where nested list values would be flattened when ``write`` is
3314
called. Now sub-lists have a string representation written instead.
3316
Deprecated ``encode`` and ``decode`` methods instead.
3318
You can now pass in a COnfigObj instance as a configspec (remember to read
3319
the file using ``list_values=False``).
3324
Removed ``BOM_UTF8`` from ``__all__``.
3326
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It can
3327
be ``True`` for the ``UTF16/UTF8`` encodings.
3329
File like objects no longer need a ``seek`` attribute.
3331
ConfigObj no longer keeps a reference to file like objects. Instead the
3332
``write`` method takes a file like object as an optional argument. (Which
3333
will be used in preference of the ``filename`` attribute if htat exists as
3336
Full unicode support added. New options/attributes ``encoding``,
3337
``default_encoding``.
3339
utf16 files decoded to unicode.
3341
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3342
written out at the start of the file. (It will normally only be ``True`` if
3343
the utf8 BOM was found when the file was read.)
3345
File paths are *not* converted to absolute paths, relative paths will
3346
remain relative as the ``filename`` attribute.
3348
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3354
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3355
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3356
``enumerate`` is needed for all versions ofPython 2.2)
3358
Deprecated ``istrue``, replaced it with ``as_bool``.
3360
Added ``as_int`` and ``as_float``.
3362
utf8 and utf16 BOM handled in an endian agnostic way.
3367
Validation no longer done on the 'DEFAULT' section (only in the root
3368
level). This allows interpolation in configspecs.
3370
Change in validation syntax implemented in validate 0.2.1
3377
Added ``merge``, a recursive update.
3379
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3382
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3384
Fixed bug where a config file is *all* comment, the comment will now be
3385
``initial_comment`` rather than ``final_comment``.
3390
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3395
Fixed bug in ``Section.walk`` when transforming names as well as values.
3397
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3400
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3401
are multiline values.
3403
List values are written as ``item, item`` rather than ``item,item``.
3410
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3418
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3419
a reference to the new section.
3424
Removed ``PositionError``.
3426
Allowed quotes around keys as documented.
3428
Fixed bug with commas in comments. (matched as a list value)
3435
Fixed bug in initialising ConfigObj from a ConfigObj.
3437
Changed the mailing list address.
3444
Fixed bug in ``Section.__delitem__`` oops.
3449
Interpolation is switched off before writing out files.
3451
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3452
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3454
Moved the doctests from the ``__init__`` method to a separate function.
3455
(For the sake of IDE calltips).
3462
String values unchanged by validation *aren't* reset. This preserves
3463
interpolation in string values.
3468
None from a default is turned to '' if stringify is off - because setting
3469
a value to None raises an error.
3478
Actually added the RepeatSectionError class ;-)
3483
If ``stringify`` is off - list values are preserved by the ``validate``
3491
Fixed ``simpleVal``.
3493
Added ``RepeatSectionError`` error if you have additional sections in a
3494
section with a ``__many__`` (repeated) section.
3498
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
3499
mutated the self._infile, self._index and self._maxline attributes into
3500
local variables and method parameters
3502
Reshaped the ConfigObj._multiline method to better reflect its semantics
3504
Changed the "default_test" test in ConfigObj.validate to check the fix for
3505
the bug in validate.Validator.check
3512
Updated comments at top
3519
Implemented repeated sections.
3523
Added test for interpreter version: raises RuntimeError if earlier than
3531
Implemented default values in configspecs.
3535
Fixed naked except: clause in validate that was silencing the fact
3536
that Python2.2 does not have dict.pop
3543
Bug fix causing error if file didn't exist.
3550
Adjusted doctests for Python 2.2.3 compatibility
3557
Added the inline_comments attribute
3559
We now preserve and rewrite all comments in the config file
3561
configspec is now a section attribute
3563
The validate method changes values in place
3565
Added InterpolationError
3567
The errors now have line number, line, and message attributes. This
3568
simplifies error handling
3577
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3580
Replaced ``basestring`` with ``types.StringTypes``
3582
Removed the ``writein`` method
3591
Indentation in config file is not significant anymore, subsections are
3592
designated by repeating square brackets
3594
Adapted all tests and docs to the new format
3608
Reformatted final docstring in ReST format, indented it for easier folding
3610
Code tests converted to doctest format, and scattered them around
3611
in various docstrings
3613
Walk method rewritten using scalars and sections attributes
3620
Changed Validator and SimpleVal "test" methods to "check"
3627
Changed Section.sequence to Section.scalars and Section.sections
3629
Added Section.configspec
3631
Sections in the root section now have no extra indentation
3633
Comments now better supported in Section and preserved by ConfigObj
3635
Comments also written out
3637
Implemented initial_comment and final_comment
3639
A scalar value after a section will now raise an error
3644
Fixed a couple of bugs
3646
Can now pass a tuple instead of a list
3648
Simplified dict and walk methods
3650
Added __str__ to Section
3662
The stringify option implemented. On by default.
3667
Renamed private attributes with a single underscore prefix.
3669
Changes to interpolation - exceeding recursion depth, or specifying a
3670
missing value, now raise errors.
3672
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3673
``is True`` and ``is False``)
3675
Added test for duplicate section and member (and fixed bug)
3689
Now properly handles values including comments and lists.
3691
Better error handling.
3693
String interpolation.
3695
Some options implemented.
3697
You can pass a Section a dictionary to initialise it.
3699
Setting a Section member to a dictionary will create a Section instance.
3706
Experimental reader.
3708
A reasonably elegant implementation - a basic reader in 160 lines of code.
3710
*A programming language is a medium of expression.* - Paul Graham