2
# A config file reader/writer that supports nested sections in config files.
3
# Copyright (C) 2005 Michael Foord, Nicola Larosa
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
# nico AT tekNico DOT net
9
# Released subject to the BSD License
10
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
12
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
13
# For information about bugfixes, updates and support, please join the
14
# ConfigObj mailing list:
15
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
16
# Comments, suggestions and bug reports welcome.
26
... 'member': 'value',
28
>>> x = ConfigObj(z.write())
34
INTP_VER = sys.version_info[:2]
36
raise RuntimeError("Python v.2.2 or later needed")
39
from types import StringTypes
41
# the UTF8 BOM - from codecs module
42
BOM_UTF8 = '\xef\xbb\xbf'
46
__revision__ = '$Id: configobj.py 135 2005-10-12 14:52:28Z fuzzyman $'
48
__docformat__ = "restructuredtext en"
53
'DEFAULT_INDENT_TYPE',
64
'InterpolationDepthError',
65
'MissingInterpolationOption',
70
DEFAULT_INDENT_TYPE = ' '
72
MAX_INTERPOL_DEPTH = 10
75
'interpolation': True,
76
'raise_errors': False,
78
'create_empty': False,
82
# option may be set to one of ('', ' ', '\t')
86
class ConfigObjError(SyntaxError):
88
This is the base class for all errors that ConfigObj raises.
89
It is a subclass of SyntaxError.
91
>>> raise ConfigObjError
92
Traceback (most recent call last):
95
def __init__(self, message='', line_number=None, line=''):
97
self.line_number = line_number
98
self.message = message
99
SyntaxError.__init__(self, message)
101
class NestingError(ConfigObjError):
103
This error indicates a level of nesting that doesn't match.
105
>>> raise NestingError
106
Traceback (most recent call last):
110
class ParseError(ConfigObjError):
112
This error indicates that a line is badly written.
113
It is neither a valid ``key = value`` line,
114
nor a valid section marker line.
117
Traceback (most recent call last):
121
class DuplicateError(ConfigObjError):
123
The keyword or section specified already exists.
125
>>> raise DuplicateError
126
Traceback (most recent call last):
130
class ConfigspecError(ConfigObjError):
132
An error occured whilst parsing a configspec.
134
>>> raise ConfigspecError
135
Traceback (most recent call last):
139
class InterpolationError(ConfigObjError):
140
"""Base class for the two interpolation errors."""
142
class InterpolationDepthError(InterpolationError):
143
"""Maximum interpolation depth exceeded in string interpolation."""
145
def __init__(self, option):
147
>>> raise InterpolationDepthError('yoda')
148
Traceback (most recent call last):
149
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
151
InterpolationError.__init__(
153
'max interpolation depth exceeded in value "%s".' % option)
155
class RepeatSectionError(ConfigObjError):
157
This error indicates additional sections in a section with a
158
``__many__`` (repeated) section.
160
>>> raise RepeatSectionError
161
Traceback (most recent call last):
165
class MissingInterpolationOption(InterpolationError):
166
"""A value specified for interpolation was missing."""
168
def __init__(self, option):
170
>>> raise MissingInterpolationOption('yoda')
171
Traceback (most recent call last):
172
MissingInterpolationOption: missing option "yoda" in interpolation.
174
InterpolationError.__init__(
176
'missing option "%s" in interpolation.' % option)
180
A dictionary-like object that represents a section in a config file.
182
It does string interpolation if the 'interpolate' attribute
183
of the 'main' object is set to True.
185
Interpolation is tried first from the 'DEFAULT' section of this object,
186
next from the 'DEFAULT' section of the parent, lastly the main object.
188
A Section will behave like an ordered dictionary - following the
189
order of the ``scalars`` and ``sections`` attributes.
190
You can use this to change the order of members.
192
Iteration follows the order: scalars, then sections.
195
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
197
def __init__(self, parent, depth, main, indict=None, name=None):
199
parent is the section above
200
depth is the depth level of this section
201
main is the main ConfigObj
202
indict is a dictionary to initialise the section with
207
# used for nesting level *and* interpolation
209
# used for the interpolation attribute
211
# level of nesting depth of this Section
213
# the sequence of scalar values in this Section
215
# the sequence of sections in this Section
217
# purely for information
221
self.inline_comments = {}
227
# we do this explicitly so that __setitem__ is used properly
228
# (rather than just passing to ``dict.__init__``)
230
self[entry] = indict[entry]
232
def _interpolate(self, value):
233
"""Nicked from ConfigParser."""
234
depth = MAX_INTERPOL_DEPTH
235
# loop through this until it's done
238
if value.find("%(") != -1:
239
value = self._KEYCRE.sub(self._interpolation_replace, value)
243
raise InterpolationDepthError(value)
246
def _interpolation_replace(self, match):
252
# switch off interpolation before we try and fetch anything !
253
self.main.interpolation = False
254
# try the 'DEFAULT' member of *this section* first
255
val = self.get('DEFAULT', {}).get(s)
256
# try the 'DEFAULT' member of the *parent section* next
258
val = self.parent.get('DEFAULT', {}).get(s)
259
# last, try the 'DEFAULT' member of the *main section*
261
val = self.main.get('DEFAULT', {}).get(s)
262
self.main.interpolation = True
264
raise MissingInterpolationOption(s)
267
def __getitem__(self, key):
268
"""Fetch the item and do string interpolation."""
269
val = dict.__getitem__(self, key)
270
if self.main.interpolation and isinstance(val, StringTypes):
271
return self._interpolate(val)
274
def __setitem__(self, key, value):
276
Correctly set a value.
278
Making dictionary values Section instances.
279
(We have to special case 'Section' instances - which are also dicts)
281
Keys must be strings.
282
Values need only be strings (or lists of strings) if
283
``main.stringify`` is set.
285
if not isinstance(key, StringTypes):
286
raise ValueError, 'The key "%s" is not a string.' % key
287
## if self.depth is None:
290
if not self.comments.has_key(key):
291
self.comments[key] = []
292
self.inline_comments[key] = ''
293
# remove the entry from defaults
294
if key in self.defaults:
295
self.defaults.remove(key)
297
if isinstance(value, Section):
298
if not self.has_key(key):
299
self.sections.append(key)
300
dict.__setitem__(self, key, value)
301
elif isinstance(value, dict):
302
# First create the new depth level,
303
# then create the section
304
if not self.has_key(key):
305
self.sections.append(key)
306
new_depth = self.depth + 1
317
if not self.has_key(key):
318
self.scalars.append(key)
319
if not self.main.stringify:
320
if isinstance(value, StringTypes):
322
elif isinstance(value, (list, tuple)):
324
if not isinstance(entry, StringTypes):
326
'Value is not a string "%s".' % entry)
328
raise TypeError, 'Value is not a string "%s".' % value
329
dict.__setitem__(self, key, value)
331
def __delitem__(self, key):
332
"""Remove items from the sequence when deleting."""
333
dict. __delitem__(self, key)
334
if key in self.scalars:
335
self.scalars.remove(key)
337
self.sections.remove(key)
338
del self.comments[key]
339
del self.inline_comments[key]
341
def get(self, key, default=None):
342
"""A version of ``get`` that doesn't bypass string interpolation."""
348
def update(self, indict):
349
"""A version of update that uses our ``__setitem__``."""
351
self[entry] = indict[entry]
353
def pop(self, key, *args):
355
val = dict.pop(self, key, *args)
356
if key in self.scalars:
357
del self.comments[key]
358
del self.inline_comments[key]
359
self.scalars.remove(key)
360
elif key in self.sections:
361
del self.comments[key]
362
del self.inline_comments[key]
363
self.sections.remove(key)
364
if self.main.interpolation and isinstance(val, StringTypes):
365
return self._interpolate(val)
369
"""Pops the first (key,val)"""
370
sequence = (self.scalars + self.sections)
372
raise KeyError, ": 'popitem(): dictionary is empty'"
380
A version of clear that also affects scalars/sections
381
Also clears comments and configspec.
383
Leaves other attributes alone :
384
depth/main/parent are not affected
390
self.inline_comments = {}
393
def setdefault(self, key, default=None):
394
"""A version of setdefault that sets sequence if appropriate."""
403
return zip((self.scalars + self.sections), self.values())
407
return (self.scalars + self.sections)
411
return [self[key] for key in (self.scalars + self.sections)]
415
return iter(self.items())
419
return iter((self.scalars + self.sections))
423
def itervalues(self):
425
return iter(self.values())
428
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
429
for key in (self.scalars + self.sections)])
433
# Extra methods - not in a normal dictionary
437
Return a deepcopy of self as a dictionary.
439
All members that are ``Section`` instances are recursively turned to
440
ordinary dictionaries - by calling their ``dict`` method.
450
this_entry = self[entry]
451
if isinstance(this_entry, Section):
452
this_entry = this_entry.dict()
453
elif isinstance(this_entry, (list, tuple)):
454
# create a copy rather than a reference
455
this_entry = list(this_entry)
456
newdict[entry] = this_entry
459
def rename(self, oldkey, newkey):
461
Change a keyname to another, without changing position in sequence.
463
Implemented so that transformations can be made on keys,
464
as well as on values. (used by encode and decode)
466
Also renames comments.
468
if oldkey in self.scalars:
469
the_list = self.scalars
470
elif oldkey in self.sections:
471
the_list = self.sections
473
raise KeyError, 'Key "%s" not found.' % oldkey
474
pos = the_list.index(oldkey)
477
dict.__delitem__(self, oldkey)
478
dict.__setitem__(self, newkey, val)
479
the_list.remove(oldkey)
480
the_list.insert(pos, newkey)
481
comm = self.comments[oldkey]
482
inline_comment = self.inline_comments[oldkey]
483
del self.comments[oldkey]
484
del self.inline_comments[oldkey]
485
self.comments[newkey] = comm
486
self.inline_comments[newkey] = inline_comment
488
def walk(self, function, raise_errors=True,
489
call_on_sections=False, **keywargs):
491
Walk every member and call a function on the keyword and value.
493
Return a dictionary of the return values
495
If the function raises an exception, raise the errror
496
unless ``raise_errors=False``, in which case set the return value to
499
Any unrecognised keyword arguments you pass to walk, will be pased on
500
to the function you pass in.
502
Note: if ``call_on_sections`` is ``True`` then - on encountering a
503
subsection, *first* the function is called for the *whole* subsection,
504
and then recurses into it's members. This means your function must be
505
able to handle strings, dictionaries and lists. This allows you
506
to change the key of subsections as well as for ordinary members. The
507
return value when called on the whole subsection has to be discarded.
509
See the encode and decode methods for examples, including functions.
513
for entry in self.scalars[:]:
515
out[entry] = function(self, entry, **keywargs)
522
for entry in self.sections[:]:
525
function(self, entry, **keywargs)
531
# previous result is discarded
532
out[entry] = self[entry].walk(
534
raise_errors=raise_errors,
535
call_on_sections=call_on_sections,
539
def decode(self, encoding):
541
Decode all strings and values to unicode, using the specified encoding.
543
Works with subsections and list values.
545
Uses the ``walk`` method.
547
Testing ``encode`` and ``decode``.
549
>>> m.decode('ascii')
550
>>> def testuni(val):
551
... for entry in val:
552
... if not isinstance(entry, unicode):
553
... print >> sys.stderr, type(entry)
554
... raise AssertionError, 'decode failed.'
555
... if isinstance(val[entry], dict):
556
... testuni(val[entry])
557
... elif not isinstance(val[entry], unicode):
558
... raise AssertionError, 'decode failed.'
560
>>> m.encode('ascii')
564
def decode(section, key, encoding=encoding):
567
if isinstance(val, (list, tuple)):
570
newval.append(entry.decode(encoding))
571
elif isinstance(val, dict):
574
newval = val.decode(encoding)
575
newkey = key.decode(encoding)
576
section.rename(key, newkey)
577
section[newkey] = newval
578
# using ``call_on_sections`` allows us to modify section names
579
self.walk(decode, call_on_sections=True)
581
def encode(self, encoding):
583
Encode all strings and values from unicode,
584
using the specified encoding.
586
Works with subsections and list values.
587
Uses the ``walk`` method.
589
def encode(section, key, encoding=encoding):
592
if isinstance(val, (list, tuple)):
595
newval.append(entry.encode(encoding))
596
elif isinstance(val, dict):
599
newval = val.encode(encoding)
600
newkey = key.encode(encoding)
601
section.rename(key, newkey)
602
section[newkey] = newval
603
self.walk(encode, call_on_sections=True)
605
class ConfigObj(Section):
607
An object to read, create, and write config files.
609
Testing with duplicate keys and sections.
619
>>> ConfigObj(c.split('\\n'), raise_errors = True)
620
Traceback (most recent call last):
621
DuplicateError: Duplicate section name at line 5.
629
... 'member1' = value
633
>>> ConfigObj(d.split('\\n'), raise_errors = True)
634
Traceback (most recent call last):
635
DuplicateError: Duplicate keyword name at line 6.
638
_keyword = re.compile(r'''^ # line start
641
(?:".*?")| # double quotes
642
(?:'.*?')| # single quotes
643
(?:[^'"=].*?) # no quotes
646
(.*) # value (including list values and comments)
651
_sectionmarker = re.compile(r'''^
652
(\s*) # 1: indentation
653
((?:\[\s*)+) # 2: section marker open
654
( # 3: section name open
655
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
656
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
657
(?:[^'"\s].*?) # at least one non-space unquoted
658
) # section name close
659
((?:\s*\])+) # 4: section marker close
660
\s*(\#.*)? # 5: optional comment
664
# this regexp pulls list values out as a single string
665
# or single values and comments
666
_valueexp = re.compile(r'''^
672
(?:".*?")| # double quotes
673
(?:'.*?')| # single quotes
674
(?:[^'",\#][^,\#]*?) # unquoted
677
)* # match all list items ending in a comma (if any)
680
(?:".*?")| # double quotes
681
(?:'.*?')| # single quotes
682
(?:[^'",\#\s][^,]*?) # unquoted
683
)? # last item in a list - or string value
685
(,) # alternatively a single comma - empty list
687
\s*(\#.*)? # optional comment
691
# use findall to get the members of a list value
692
_listvalueexp = re.compile(r'''
694
(?:".*?")| # double quotes
695
(?:'.*?')| # single quotes
696
(?:[^'",\#].*?) # unquoted
702
# this regexp is used for the value
703
# when lists are switched off
704
_nolistvalue = re.compile(r'''^
706
(?:".*?")| # double quotes
707
(?:'.*?')| # single quotes
708
(?:[^'"\#].*?) # unquoted
710
\s*(\#.*)? # optional comment
714
# regexes for finding triple quoted values on one line
715
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
716
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
717
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
718
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
721
"'''": (_single_line_single, _multi_line_single),
722
'"""': (_single_line_double, _multi_line_double),
725
def __init__(self, infile=None, options=None, **kwargs):
727
Parse or create a config file object.
729
``ConfigObj(infile=None, options=None, **kwargs)``
735
# keyword arguments take precedence over an options dictionary
736
options.update(kwargs)
737
# init the superclass
738
Section.__init__(self, self, 0, self)
740
defaults = OPTION_DEFAULTS.copy()
741
for entry in options.keys():
742
if entry not in defaults.keys():
743
raise TypeError, 'Unrecognised option "%s".' % entry
744
# TODO: check the values too
745
# add the explicit options to the defaults
746
defaults.update(options)
748
# initialise a few variables
750
self.raise_errors = defaults['raise_errors']
751
self.interpolation = defaults['interpolation']
752
self.list_values = defaults['list_values']
753
self.create_empty = defaults['create_empty']
754
self.file_error = defaults['file_error']
755
self.stringify = defaults['stringify']
756
self.indent_type = defaults['indent_type']
757
# used by the write method
760
self.initial_comment = []
761
self.final_comment = []
763
if isinstance(infile, StringTypes):
764
self.filename = os.path.abspath(infile)
765
if os.path.isfile(self.filename):
766
infile = open(self.filename).readlines()
767
elif self.file_error:
768
# raise an error if the file doesn't exist
769
raise IOError, 'Config file not found: "%s".' % self.filename
771
# file doesn't already exist
772
if self.create_empty:
773
# this is a good test that the filename specified
774
# isn't impossible - like on a non existent device
775
h = open(self.filename)
779
elif isinstance(infile, (list, tuple)):
781
elif isinstance(infile, dict):
783
# the Section class handles creating subsections
784
if isinstance(infile, ConfigObj):
785
# get a copy of our ConfigObj
786
infile = infile.dict()
788
self[entry] = infile[entry]
791
if defaults['configspec'] is not None:
792
self._handle_configspec(defaults['configspec'])
794
self.configspec = None
796
elif hasattr(infile, 'seek'):
797
# this supports StringIO instances and even file objects
798
self.filename = infile
800
infile = infile.readlines()
801
self.filename.seek(0)
803
raise TypeError, ('infile must be a filename,'
804
' StringIO instance, or a file as a list.')
806
# strip trailing '\n' from lines
807
infile = [line.rstrip('\n') for line in infile]
809
# remove the UTF8 BOM if it is there
810
# FIXME: support other BOM
811
if infile and infile[0].startswith(BOM_UTF8):
812
infile[0] = infile[0][3:]
818
# if we had any errors, now is the time to raise them
820
error = ConfigObjError("Parsing failed.")
821
# set the errors attribute; it's a list of tuples:
822
# (error_type, message, line_number)
823
error.errors = self._errors
824
# set the config attribute
827
# delete private attributes
830
if defaults['configspec'] is None:
831
self.configspec = None
833
self._handle_configspec(defaults['configspec'])
835
def _parse(self, infile):
837
Actually parse the config file
839
Testing Interpolation
844
... 'userdir': 'c:\\\\home',
849
... 'a': '%(datadir)s\\\\some path\\\\file.py',
850
... 'b': '%(userdir)s\\\\some path\\\\file.py',
852
... 'd': '%(not_here)s',
855
>>> c['section']['DEFAULT'] = {
856
... 'datadir': 'c:\\\\silly_test',
857
... 'a': 'hello - %(b)s',
859
>>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
861
>>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
863
>>> c['section']['c'] == 'Yo hello - goodbye'
866
Switching Interpolation Off
868
>>> c.interpolation = False
869
>>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
871
>>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
873
>>> c['section']['c'] == 'Yo %(a)s'
876
Testing the interpolation errors.
878
>>> c.interpolation = True
879
>>> c['section']['d']
880
Traceback (most recent call last):
881
MissingInterpolationOption: missing option "not_here" in interpolation.
882
>>> c['section']['e']
883
Traceback (most recent call last):
884
InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
888
>>> i._quote('\"""\'\'\'')
889
Traceback (most recent call last):
890
SyntaxError: EOF while scanning triple-quoted string
892
... i._quote('\\n', multiline=False)
893
... except ConfigObjError, e:
895
'Value "\\n" cannot be safely quoted.'
896
>>> k._quote(' "\' ', multiline=False)
897
Traceback (most recent call last):
898
SyntaxError: EOL while scanning single-quoted string
900
Testing with "stringify" off.
901
>>> c.stringify = False
903
Traceback (most recent call last):
904
TypeError: Value is not a string "1".
909
maxline = len(infile) - 1
911
reset_comment = False
912
while cur_index < maxline:
916
line = infile[cur_index]
918
# do we have anything on the line ?
919
if not sline or sline.startswith('#'):
920
reset_comment = False
921
comment_list.append(line)
924
# preserve initial comment
925
self.initial_comment = comment_list
929
# first we check if it's a section marker
930
mat = self._sectionmarker.match(line)
931
## print >> sys.stderr, sline, mat
934
(indent, sect_open, sect_name, sect_close, comment) = (
936
if indent and (self.indent_type is None):
937
self.indent_type = indent[0]
938
cur_depth = sect_open.count('[')
939
if cur_depth != sect_close.count(']'):
941
"Cannot compute the section depth at line %s.",
942
NestingError, infile, cur_index)
944
if cur_depth < this_section.depth:
945
# the new section is dropping back to a previous level
947
parent = self._match_depth(
952
"Cannot compute nesting level at line %s.",
953
NestingError, infile, cur_index)
955
elif cur_depth == this_section.depth:
956
# the new section is a sibling of the current section
957
parent = this_section.parent
958
elif cur_depth == this_section.depth + 1:
959
# the new section is a child the current section
960
parent = this_section
963
"Section too nested at line %s.",
964
NestingError, infile, cur_index)
966
sect_name = self._unquote(sect_name)
967
if parent.has_key(sect_name):
968
## print >> sys.stderr, sect_name
970
'Duplicate section name at line %s.',
971
DuplicateError, infile, cur_index)
973
# create the new section
974
this_section = Section(
979
parent[sect_name] = this_section
980
parent.inline_comments[sect_name] = comment
981
parent.comments[sect_name] = comment_list
982
## print >> sys.stderr, parent[sect_name] is this_section
985
# it's not a section marker,
986
# so it should be a valid ``key = value`` line
987
mat = self._keyword.match(line)
988
## print >> sys.stderr, sline, mat
991
# value will include any inline comment
992
(indent, key, value) = mat.groups()
993
if indent and (self.indent_type is None):
994
self.indent_type = indent[0]
995
# check for a multiline value
996
if value[:3] in ['"""', "'''"]:
998
(value, comment, cur_index) = self._multiline(
999
value, infile, cur_index, maxline)
1002
'Parse error in value at line %s.',
1003
ParseError, infile, cur_index)
1006
# extract comment and lists
1008
(value, comment) = self._handle_value(value)
1011
'Parse error in value at line %s.',
1012
ParseError, infile, cur_index)
1015
## print >> sys.stderr, sline
1016
key = self._unquote(key)
1017
if this_section.has_key(key):
1019
'Duplicate keyword name at line %s.',
1020
DuplicateError, infile, cur_index)
1023
## print >> sys.stderr, this_section.name
1024
this_section[key] = value
1025
this_section.inline_comments[key] = comment
1026
this_section.comments[key] = comment_list
1027
## print >> sys.stderr, key, this_section[key]
1028
## if this_section.name is not None:
1029
## print >> sys.stderr, this_section
1030
## print >> sys.stderr, this_section.parent
1031
## print >> sys.stderr, this_section.parent[this_section.name]
1034
# it neither matched as a keyword
1035
# or a section marker
1037
'Invalid line at line "%s".',
1038
ParseError, infile, cur_index)
1039
if self.indent_type is None:
1040
# no indentation used, set the type accordingly
1041
self.indent_type = ''
1042
# preserve the final comment
1043
self.final_comment = comment_list
1045
def _match_depth(self, sect, depth):
1047
Given a section and a depth level, walk back through the sections
1048
parents to see if the depth level matches a previous section.
1050
Return a reference to the right section,
1051
or raise a SyntaxError.
1053
while depth < sect.depth:
1054
if sect is sect.parent:
1055
# we've reached the top level already
1058
if sect.depth == depth:
1060
# shouldn't get here
1063
def _handle_error(self, text, ErrorClass, infile, cur_index):
1065
Handle an error according to the error settings.
1067
Either raise the error or store it.
1068
The error will have occured at ``cur_index``
1070
line = infile[cur_index]
1071
message = text % cur_index
1072
error = ErrorClass(message, cur_index, line)
1073
if self.raise_errors:
1074
# raise the error - parsing stops here
1077
# reraise when parsing has finished
1078
self._errors.append(error)
1080
def _unquote(self, value):
1081
"""Return an unquoted version of a value"""
1082
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1086
def _quote(self, value, multiline=True):
1088
Return a safely quoted version of a value.
1090
Raise a ConfigObjError if the value cannot be safely quoted.
1091
If multiline is ``True`` (default) then use triple quotes
1094
Don't quote values that don't need it.
1095
Recursively quote members of a list and return a comma joined list.
1096
Multiline is ``False`` for lists.
1097
Obey list syntax for empty and single member lists.
1099
if isinstance(value, (list, tuple)):
1102
elif len(value) == 1:
1103
return self._quote(value[0], multiline=False) + ','
1104
return ','.join([self._quote(val, multiline=False)
1106
if not isinstance(value, StringTypes):
1110
raise TypeError, 'Value "%s" is not a string.' % value
1114
wspace_plus = ' \r\t\n\v\t\'"'
1119
if not (multiline and
1120
((("'" in value) and ('"' in value)) or ('\n' in value))):
1121
# for normal values either single or double quotes will do
1123
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1125
if ((value[0] not in wspace_plus) and
1126
(value[-1] not in wspace_plus) and
1127
(',' not in value)):
1130
if ("'" in value) and ('"' in value):
1131
raise ConfigObjError, (
1132
'Value "%s" cannot be safely quoted.' % value)
1138
# if value has '\n' or "'" *and* '"', it will need triple quotes
1139
if (value.find('"""') != -1) and (value.find("'''") != -1):
1140
raise ConfigObjError, (
1141
'Value "%s" cannot be safely quoted.' % value)
1142
if value.find('"""') == -1:
1148
def _handle_value(self, value):
1150
Given a value string, unquote, remove comment,
1151
handle lists. (including empty and single member lists)
1153
Testing list values.
1155
>>> testconfig3 = '''
1158
... c = test1, test2 , test3
1159
... d = test1, test2, test3,
1161
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1164
>>> d['b'] == ['test']
1166
>>> d['c'] == ['test1', 'test2', 'test3']
1168
>>> d['d'] == ['test1', 'test2', 'test3']
1171
Testing with list values off.
1174
... testconfig3.split('\\n'),
1175
... raise_errors=True,
1176
... list_values=False)
1179
>>> e['b'] == 'test,'
1181
>>> e['c'] == 'test1, test2 , test3'
1183
>>> e['d'] == 'test1, test2, test3,'
1186
Testing creating from a dictionary.
1209
>>> g = ConfigObj(f)
1213
Testing we correctly detect badly built list values (4 of them).
1215
>>> testconfig4 = '''
1219
... dummy = ,,hello, goodbye
1222
... ConfigObj(testconfig4.split('\\n'))
1223
... except ConfigObjError, e:
1227
Testing we correctly detect badly quoted values (4 of them).
1229
>>> testconfig5 = '''
1230
... config = "hello # comment
1232
... fish = 'goodbye # comment
1233
... dummy = "hello again
1236
... ConfigObj(testconfig5.split('\\n'))
1237
... except ConfigObjError, e:
1241
# do we look for lists in values ?
1242
if not self.list_values:
1243
mat = self._nolistvalue.match(value)
1246
(value, comment) = mat.groups()
1247
# FIXME: unquoting here can be a source of error
1248
return (self._unquote(value), comment)
1249
mat = self._valueexp.match(value)
1251
# the value is badly constructed, probably badly quoted,
1252
# or an invalid list
1254
(list_values, single, empty_list, comment) = mat.groups()
1255
if (list_values == '') and (single is None):
1256
# change this if you want to accept empty values
1258
# NOTE: note there is no error handling from here if the regex
1259
# is wrong: then incorrect values will slip through
1260
if empty_list is not None:
1261
# the single comma - meaning an empty list
1262
return ([], comment)
1263
if single is not None:
1264
single = self._unquote(single)
1265
if list_values == '':
1267
return (single, comment)
1268
the_list = self._listvalueexp.findall(list_values)
1269
the_list = [self._unquote(val) for val in the_list]
1270
if single is not None:
1271
the_list += [single]
1272
return (the_list, comment)
1274
def _multiline(self, value, infile, cur_index, maxline):
1276
Extract the value, where we are in a multiline situation
1278
Testing multiline values.
1281
... 'name4': ' another single line value ',
1282
... 'multi section': {
1283
... 'name4': '\\n Well, this is a\\n multiline '
1285
... 'name2': '\\n Well, this is a\\n multiline '
1287
... 'name3': '\\n Well, this is a\\n multiline '
1289
... 'name1': '\\n Well, this is a\\n multiline '
1292
... 'name2': ' another single line value ',
1293
... 'name3': ' a single line value ',
1294
... 'name1': ' a single line value ',
1299
newvalue = value[3:]
1300
single_line = self._triple_quote[quot][0]
1301
multi_line = self._triple_quote[quot][1]
1302
mat = single_line.match(value)
1304
retval = list(mat.groups())
1305
retval.append(cur_index)
1307
elif newvalue.find(quot) != -1:
1308
# somehow the triple quote is missing
1311
while cur_index < maxline:
1314
line = infile[cur_index]
1315
if line.find(quot) == -1:
1318
# end of multiline, process it
1321
# we've got to the end of the config, oops...
1323
mat = multi_line.match(line)
1325
# a badly formed line
1327
(value, comment) = mat.groups()
1328
return (newvalue + value, comment, cur_index)
1330
def _handle_configspec(self, configspec):
1331
"""Parse the configspec."""
1333
configspec = ConfigObj(
1338
except ConfigObjError, e:
1339
# FIXME: Should these errors have a reference
1340
# to the already parsed ConfigObj ?
1341
raise ConfigspecError('Parsing configspec failed: %s' % e)
1343
raise IOError('Reading configspec failed: %s' % e)
1344
self._set_configspec_value(configspec, self)
1346
def _set_configspec_value(self, configspec, section):
1347
"""Used to recursively set configspec values."""
1348
if '__many__' in configspec.sections:
1349
section.configspec['__many__'] = configspec['__many__']
1350
if len(configspec.sections) > 1:
1351
# FIXME: can we supply any useful information here ?
1352
raise RepeatSectionError
1353
for entry in configspec.scalars:
1354
section.configspec[entry] = configspec[entry]
1355
for entry in configspec.sections:
1356
if entry == '__many__':
1358
if not section.has_key(entry):
1360
self._set_configspec_value(configspec[entry], section[entry])
1362
def _handle_repeat(self, section, configspec):
1363
"""Dynamically assign configspec for repeated section."""
1365
section_keys = configspec.sections
1366
scalar_keys = configspec.scalars
1367
except AttributeError:
1368
section_keys = [entry for entry in configspec
1369
if isinstance(configspec[entry], dict)]
1370
scalar_keys = [entry for entry in configspec
1371
if not isinstance(configspec[entry], dict)]
1372
if '__many__' in section_keys and len(section_keys) > 1:
1373
# FIXME: can we supply any useful information here ?
1374
raise RepeatSectionError
1377
for entry in scalar_keys:
1378
val = configspec[entry]
1379
scalars[entry] = val
1380
for entry in section_keys:
1381
val = configspec[entry]
1382
if entry == '__many__':
1383
scalars[entry] = val
1385
sections[entry] = val
1387
section.configspec = scalars
1388
for entry in sections:
1389
if not section.has_key(entry):
1391
self._handle_repeat(section[entry], sections[entry])
1393
def _write_line(self, indent_string, entry, this_entry, comment):
1394
"""Write an individual line, for the write method"""
1395
return '%s%s = %s%s' % (
1397
self._quote(entry, multiline=False),
1398
self._quote(this_entry),
1401
def _write_marker(self, indent_string, depth, entry, comment):
1402
"""Write a section marker line"""
1403
return '%s%s%s%s%s' % (
1406
self._quote(entry, multiline=False),
1410
def _handle_comment(self, comment):
1412
Deal with a comment.
1414
>>> filename = a.filename
1415
>>> a.filename = None
1416
>>> values = a.write()
1418
>>> while index < 23:
1420
... line = values[index-1]
1421
... assert line.endswith('# comment ' + str(index))
1422
>>> a.filename = filename
1424
>>> start_comment = ['# Initial Comment', '', '#']
1425
>>> end_comment = ['', '#', '# Final Comment']
1426
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1427
>>> nc = ConfigObj(newconfig)
1428
>>> nc.initial_comment
1429
['# Initial Comment', '', '#']
1430
>>> nc.final_comment
1431
['', '#', '# Final Comment']
1432
>>> nc.initial_comment == start_comment
1434
>>> nc.final_comment == end_comment
1439
if self.indent_type == '\t':
1442
start = ' ' * NUM_INDENT_SPACES
1443
if not comment.startswith('#'):
1445
return (start + comment)
1447
def _compute_indent_string(self, depth):
1449
Compute the indent string, according to current indent_type and depth
1451
if self.indent_type == '':
1452
# no indentation at all
1454
if self.indent_type == '\t':
1456
if self.indent_type == ' ':
1457
return ' ' * NUM_INDENT_SPACES * depth
1462
def write(self, section=None):
1464
Write the current ConfigObj as a file
1466
tekNico: FIXME: use StringIO instead of real files
1468
>>> filename = a.filename
1469
>>> a.filename = 'test.ini'
1471
>>> a.filename = filename
1472
>>> a == ConfigObj('test.ini', raise_errors=True)
1474
>>> os.remove('test.ini')
1475
>>> b.filename = 'test.ini'
1477
>>> b == ConfigObj('test.ini', raise_errors=True)
1479
>>> os.remove('test.ini')
1480
>>> i.filename = 'test.ini'
1482
>>> i == ConfigObj('test.ini', raise_errors=True)
1484
>>> os.remove('test.ini')
1486
>>> a['DEFAULT'] = {'a' : 'fish'}
1487
>>> a['a'] = '%(a)s'
1489
['a = %(a)s', '[DEFAULT]', 'a = fish']
1492
if self.indent_type is None:
1493
# this can be true if initialised from a dictionary
1494
self.indent_type = DEFAULT_INDENT_TYPE
1499
int_val = self.interpolation
1500
self.interpolation = False
1503
for line in self.initial_comment:
1504
stripped_line = line.strip()
1505
if stripped_line and not stripped_line.startswith('#'):
1509
indent_string = self._compute_indent_string(section.depth)
1510
for entry in (section.scalars + section.sections):
1511
if entry in section.defaults:
1512
# don't write out default values
1514
for comment_line in section.comments[entry]:
1515
comment_line = comment_line.lstrip()
1516
if comment_line and not comment_line.startswith('#'):
1517
comment_line = '#' + comment_line
1518
out.append(indent_string + comment_line)
1519
this_entry = section[entry]
1520
comment = self._handle_comment(section.inline_comments[entry])
1522
if isinstance(this_entry, dict):
1524
out.append(self._write_marker(
1529
out.extend(self.write(this_entry))
1531
out.append(self._write_line(
1538
for line in self.final_comment:
1539
stripped_line = line.strip()
1540
if stripped_line and not stripped_line.startswith('#'):
1544
if int_val != 'test':
1545
self.interpolation = int_val
1547
if (return_list) or (self.filename is None):
1550
if isinstance(self.filename, StringTypes):
1551
h = open(self.filename, 'w')
1552
h.write(self.BOM or '')
1553
h.write('\n'.join(out))
1556
self.filename.seek(0)
1557
self.filename.write(self.BOM or '')
1558
self.filename.write('\n'.join(out))
1559
# if we have a stored file object (or StringIO)
1560
# we *don't* close it
1562
def validate(self, validator, section=None):
1564
Test the ConfigObj against a configspec.
1566
It uses the ``validator`` object from *validate.py*.
1568
To run ``validate`` on the current ConfigObj, call: ::
1570
test = config.validate(validator)
1572
(Normally having previously passed in the configspec when the ConfigObj
1573
was created - you can dynamically assign a dictionary of checks to the
1574
``configspec`` attribute of a section though).
1576
It returns ``True`` if everything passes, or a dictionary of
1577
pass/fails (True/False). If every member of a subsection passes, it
1578
will just have the value ``True``. (It also returns ``False`` if all
1581
In addition, it converts the values from strings to their native
1582
types if their checks pass (and ``stringify`` is set).
1585
... from validate import Validator
1586
... except ImportError:
1587
... print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
1604
... '''.split('\\n')
1605
... configspec = '''
1606
... test1='integer(30,50)'
1609
... test4='float(6.0)'
1611
... test1='integer(30,50)'
1614
... test4='float(6.0)'
1616
... test1='integer(30,50)'
1619
... test4='float(6.0)'
1620
... '''.split('\\n')
1621
... val = Validator()
1622
... c1 = ConfigObj(config, configspec=configspec)
1623
... test = c1.validate(val)
1634
... 'sub section': {
1643
>>> val.check(c1.configspec['test4'], c1['test4'])
1644
Traceback (most recent call last):
1645
VdtValueTooSmallError: the value "5.0" is too small.
1647
>>> val_test_config = '''
1652
... key2 = 1.1, 3.0, 17, 6.8
1655
... key2 = True'''.split('\\n')
1656
>>> val_test_configspec = '''
1661
... key2 = float_list(4)
1663
... key = option(option1, option2)
1664
... key2 = boolean'''.split('\\n')
1665
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
1666
>>> val_test.validate(val)
1668
>>> val_test['key'] = 'text not a digit'
1669
>>> val_res = val_test.validate(val)
1670
>>> val_res == {'key2': True, 'section': True, 'key': False}
1672
>>> configspec = '''
1673
... test1='integer(30,50, default=40)'
1674
... test2='string(default="hello")'
1675
... test3='integer(default=3)'
1676
... test4='float(6.0, default=6.0)'
1678
... test1='integer(30,50, default=40)'
1679
... test2='string(default="hello")'
1680
... test3='integer(default=3)'
1681
... test4='float(6.0, default=6.0)'
1683
... test1='integer(30,50, default=40)'
1684
... test2='string(default="hello")'
1685
... test3='integer(default=3)'
1686
... test4='float(6.0, default=6.0)'
1687
... '''.split('\\n')
1688
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1690
{'test1': '30', 'section': {'sub section': {}}}
1691
>>> default_test.validate(val)
1693
>>> default_test == {
1695
... 'test2': 'hello',
1700
... 'test2': 'hello',
1703
... 'sub section': {
1706
... 'test2': 'hello',
1713
Now testing with repeated sections : BIG TEST
1715
>>> repeated_1 = '''
1717
... [[__many__]] # spec for a dog
1718
... fleas = boolean(default=True)
1719
... tail = option(long, short, default=long)
1720
... name = string(default=rover)
1721
... [[[__many__]]] # spec for a puppy
1722
... name = string(default="son of rover")
1723
... age = float(default=0.0)
1725
... [[__many__]] # spec for a cat
1726
... fleas = boolean(default=True)
1727
... tail = option(long, short, default=short)
1728
... name = string(default=pussy)
1729
... [[[__many__]]] # spec for a kitten
1730
... name = string(default="son of pussy")
1731
... age = float(default=0.0)
1732
... '''.split('\\n')
1733
>>> repeated_2 = '''
1736
... # blank dogs with puppies
1737
... # should be filled in by the configspec
1752
... # blank cats with kittens
1753
... # should be filled in by the configspec
1766
... '''.split('\\n')
1767
>>> repeated_3 = '''
1778
... '''.split('\\n')
1779
>>> repeated_4 = '''
1782
... name = string(default=Michael)
1783
... age = float(default=0.0)
1784
... sex = option(m, f, default=m)
1785
... '''.split('\\n')
1786
>>> repeated_5 = '''
1789
... fleas = boolean(default=True)
1790
... tail = option(long, short, default=short)
1791
... name = string(default=pussy)
1792
... [[[description]]]
1793
... height = float(default=3.3)
1794
... weight = float(default=6)
1796
... fur = option(black, grey, brown, "tortoise shell", default=black)
1797
... condition = integer(0,10, default=5)
1798
... '''.split('\\n')
1799
>>> from validate import Validator
1800
>>> val= Validator()
1801
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
1802
>>> repeater.validate(val)
1809
... 'name': 'rover',
1810
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1811
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1812
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1817
... 'name': 'rover',
1818
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1819
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1820
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1825
... 'name': 'rover',
1826
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1827
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1828
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1834
... 'tail': 'short',
1835
... 'name': 'pussy',
1836
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1837
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1838
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1842
... 'tail': 'short',
1843
... 'name': 'pussy',
1844
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1845
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1846
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1850
... 'tail': 'short',
1851
... 'name': 'pussy',
1852
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1853
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1854
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1859
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
1860
>>> repeater.validate(val)
1864
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1865
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1866
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1869
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1870
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1871
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1875
>>> repeater = ConfigObj(configspec=repeated_4)
1876
>>> repeater['Michael'] = {}
1877
>>> repeater.validate(val)
1880
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
1883
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
1885
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1886
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
1889
>>> repeater.validate(val)
1892
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1896
... 'tail': 'short',
1897
... 'name': 'pussy',
1898
... 'description': {
1900
... 'height': 3.2999999999999998,
1901
... 'coat': {'fur': 'black', 'condition': 5},
1906
... 'tail': 'short',
1907
... 'name': 'pussy',
1908
... 'description': {
1910
... 'height': 3.2999999999999998,
1911
... 'coat': {'fur': 'black', 'condition': 5},
1916
... 'tail': 'short',
1917
... 'name': 'pussy',
1918
... 'description': {
1920
... 'height': 3.2999999999999998,
1921
... 'coat': {'fur': 'black', 'condition': 5},
1928
Test that interpolation is preserved for validated string values.
1930
>>> t['DEFAULT'] = {}
1931
>>> t['DEFAULT']['test'] = 'a'
1932
>>> t['test'] = '%(test)s'
1936
>>> t.configspec = {'test': 'string'}
1939
>>> t.interpolation = False
1941
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
1943
FIXME: Above tests will fail if we couldn't import Validator (the ones
1944
that don't raise errors will produce different output and still fail as
1948
if self.configspec is None:
1949
raise ValueError, 'No configspec supplied.'
1952
spec_section = section.configspec
1953
if '__many__' in section.configspec:
1954
many = spec_section['__many__']
1955
# dynamically assign the configspecs
1956
# for the sections below
1957
for entry in section.sections:
1958
self._handle_repeat(section[entry], many)
1963
for entry in spec_section:
1964
if entry == '__many__':
1966
if (not entry in section.scalars) or (entry in section.defaults):
1968
# or entries from defaults
1973
val = section[entry]
1975
check = validator.check(spec_section[entry],
1978
except validator.baseErrorClass:
1981
# MIKE: we want to raise all other exceptions, not just print ?
1982
## except Exception, err:
1987
if self.stringify or missing:
1988
# if we are doing type conversion
1989
# or the value is a supplied default
1990
if not self.stringify:
1991
if isinstance(check, (list, tuple)):
1993
check = [str(item) for item in check]
1994
elif missing and check is None:
1995
# convert the None from a default to a ''
1999
if (check != val) or missing:
2000
section[entry] = check
2001
if missing and entry not in section.defaults:
2002
section.defaults.append(entry)
2004
for entry in section.sections:
2005
check = self.validate(validator, section[entry])
2022
class SimpleVal(object):
2025
Can be used to check that all members expected are present.
2027
To use it, provide a configspec with all your members in (the value given
2028
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2029
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2030
members are present, or a dictionary with True/False meaning
2031
present/missing. (Whole missing sections will be replaced with ``False``)
2033
>>> val = SimpleVal()
2049
... '''.split('\\n')
2050
>>> configspec = '''
2065
... '''.split('\\n')
2066
>>> o = ConfigObj(config, configspec=configspec)
2069
>>> o = ConfigObj(configspec=configspec)
2075
self.baseErrorClass = ConfigObjError
2077
def check(self, check, member, missing=False):
2078
"""A dummy check method, always returns the value unchanged."""
2080
raise self.baseErrorClass
2083
# FIXME: test error code for badly built multiline values
2084
# FIXME: test handling of StringIO
2085
# FIXME: test interpolation with writing
2089
Dummy function to hold some of the doctests.
2126
... 'keys11': 'val1',
2127
... 'keys13': 'val3',
2128
... 'keys12': 'val2',
2131
... 'section 2 sub 1': {
2134
... 'keys21': 'val1',
2135
... 'keys22': 'val2',
2136
... 'keys23': 'val3',
2141
... 'a' = b # !"$%^&*(),::;'@~#= 33
2142
... "b" = b #= 6, 33
2143
... ''' .split('\\n')
2144
>>> t2 = ConfigObj(t)
2145
>>> assert t2 == {'a': 'b', 'b': 'b'}
2146
>>> t2.inline_comments['b'] = ''
2148
>>> assert t2.write() == ['','b = b', '']
2151
if __name__ == '__main__':
2152
# run the code tests in doctest format
2155
key1= val # comment 1
2156
key2= val # comment 2
2159
key1= val # comment 5
2160
key2= val # comment 6
2163
key1= val # comment 9
2164
key2= val # comment 10
2166
[[lev2ba]] # comment 12
2167
key1= val # comment 13
2169
[[lev2bb]] # comment 15
2170
key1= val # comment 16
2172
[lev1c] # comment 18
2174
[[lev2c]] # comment 20
2176
[[[lev3c]]] # comment 22
2177
key1 = val # comment 23"""
2183
["section 1"] # comment
2192
[['section 2 sub 1']]
2197
name1 = """ a single line value """ # comment
2198
name2 = \''' another single line value \''' # comment
2199
name3 = """ a single line value """
2200
name4 = \''' another single line value \'''
2217
\''' # I guess this is a comment too
2221
m = sys.modules.get('__main__')
2222
globs = m.__dict__.copy()
2223
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2224
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2225
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2227
'INTP_VER': INTP_VER,
2232
doctest.testmod(m, globs=globs)
2238
With list values off, ConfigObj can incorrectly unquote values. (This makes
2239
it impossible to use listquote to handle your list values for you - for
2240
nested lists. Not handling quotes at all would be better for this)
2245
A method to optionally remove uniform indentation from multiline values.
2246
(do as an example of using ``walk`` - along with string-escape)
2248
INCOMPATIBLE CHANGES
2249
====================
2251
(I have removed a lot of needless complications - this list is probably not
2252
conclusive, many option/attribute/method names have changed)
2256
The only valid divider is '='
2258
We've removed line continuations with '\'
2260
No recursive lists in values
2264
No distinction between flatfiles and non flatfiles
2266
Change in list syntax - use commas to indicate list, not parentheses
2267
(square brackets and parentheses are no longer recognised as lists)
2269
';' is no longer valid for comments and no multiline comments
2273
We don't allow empty values - have to use '' or ""
2275
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
2276
initialise it as an empty section.
2278
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
2279
replaced by triple quote, multiple line values.
2281
The ``newline``, ``force_return``, and ``default`` options have gone
2283
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
2284
with the ``encode`` and ``decode`` methods.
2286
``fileerror`` and ``createempty`` options have become ``file_error`` and
2289
Partial configspecs (for specifying the order members should be written
2290
out and which should be present) have gone. The configspec is no longer
2291
used to specify order for the ``write`` method.
2293
Exceeding the maximum depth of recursion in string interpolation now
2294
raises an error ``InterpolationDepthError``.
2296
Specifying a value for interpolation which doesn't exist now raises an
2297
error ``MissingInterpolationOption`` (instead of merely being ignored).
2299
The ``writein`` method has been removed.
2301
The comments attribute is now a list (``inline_comments`` equates to the
2302
old comments attribute)
2307
You can't have a keyword with the same name as a section (in the same
2308
section). They are both dictionary keys - so they would overlap.
2310
Interpolation checks first the 'DEFAULT' subsection of the current
2311
section, next it checks the 'DEFAULT' section of the parent section,
2312
last it checks the 'DEFAULT' section of the main section.
2314
Logically a 'DEFAULT' section should apply to all subsections of the *same
2315
parent* - this means that checking the 'DEFAULT' subsection in the
2316
*current section* is not necessarily logical ?
2318
In order to simplify unicode support (which is possibly of limited value
2319
in a config file) I have removed automatic support and added the
2320
``encode`` and ``decode methods, which can be used to transform keys and
2321
entries. Because the regex looks for specific values on inital parsing
2322
(i.e. the quotes and the equals signs) it can only read ascii compatible
2323
encodings. For unicode use ``UTF8``, which is ASCII compatible.
2325
Does it matter that we don't support the ':' divider, which is supported
2326
by ``ConfigParser`` ?
2328
Following error with "list_values=False" : ::
2330
>>> a = ["a='hello', 'goodbye'"]
2332
>>> c(a, list_values=False)
2333
{'a': "hello', 'goodbye"}
2335
The regular expression correctly removes the value -
2336
``"'hello', 'goodbye'"`` and then unquote just removes the front and
2337
back quotes (called from ``_handle_value``). What should we do ??
2338
(*ought* to raise exception because it's an invalid value if lists are
2339
off *sigh*. This is not what you want if you want to do your own list
2340
processing - would be *better* in this case not to unquote.)
2342
String interpolation and validation don't play well together. When
2343
validation changes type it sets the value. This will correctly fetch the
2344
value using interpolation - but then overwrite the interpolation reference.
2345
If the value is unchanged by validation (it's a string) - but other types
2351
List values allow you to specify multiple values for a keyword. This
2352
maps to a list as the resulting Python object when parsed.
2354
The syntax for lists is easy. A list is a comma separated set of values.
2355
If these values contain quotes, the hash mark, or commas, then the values
2356
can be surrounded by quotes. e.g. : ::
2358
keyword = value1, 'value 2', "value 3"
2360
If a value needs to be a list, but only has one member, then you indicate
2361
this with a trailing comma. e.g. : ::
2363
keyword = "single value",
2365
If a value needs to be a list, but it has no members, then you indicate
2366
this with a single comma. e.g. : ::
2368
keyword = , # an empty list
2370
Using triple quotes it will be possible for single values to contain
2371
newlines and *both* single quotes and double quotes. Triple quotes aren't
2372
allowed in list values. This means that the members of list values can't
2373
contain carriage returns (or line feeds :-) or both quote values.
2381
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
2387
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
2388
a reference to the new section.
2393
Removed ``PositionError``.
2395
Allowed quotes around keys as documented.
2397
Fixed bug with commas in comments. (matched as a list value)
2404
Fixed bug in initialising ConfigObj from a ConfigObj.
2406
Changed the mailing list address.
2413
Fixed bug in ``Section__delitem__`` oops.
2418
Interpolation is switched off before writing out files.
2420
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
2421
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
2423
Moved the doctests from the ``__init__`` method to a separate function.
2424
(For the sake of IDE calltips).
2431
String values unchanged by validation *aren't* reset. This preserves
2432
interpolation in string values.
2437
None from a default is turned to '' if stringify is off - because setting
2438
a value to None raises an error.
2447
Actually added the RepeatSectionError class ;-)
2452
If ``stringify`` is off - list values are preserved by the ``validate``
2460
Fixed ``simpleVal``.
2462
Added ``RepeatSectionError`` error if you have additional sections in a
2463
section with a ``__many__`` (repeated) section.
2467
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
2468
mutated the self._infile, self._index and self._maxline attributes into
2469
local variables and method parameters
2471
Reshaped the ConfigObj._multiline method to better reflect its semantics
2473
Changed the "default_test" test in ConfigObj.validate to check the fix for
2474
the bug in validate.Validator.check
2481
Updated comments at top
2488
Implemented repeated sections.
2492
Added test for interpreter version: raises RuntimeError if earlier than
2500
Implemented default values in configspecs.
2504
Fixed naked except: clause in validate that was silencing the fact
2505
that Python2.2 does not have dict.pop
2512
Bug fix causing error if file didn't exist.
2519
Adjusted doctests for Python 2.2.3 compatibility
2526
Added the inline_comments attribute
2528
We now preserve and rewrite all comments in the config file
2530
configspec is now a section attribute
2532
The validate method changes values in place
2534
Added InterpolationError
2536
The errors now have line number, line, and message attributes. This
2537
simplifies error handling
2546
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
2549
Replaced ``basestring`` with ``types.StringTypes``
2551
Removed the ``writein`` method
2560
Indentation in config file is not significant anymore, subsections are
2561
designated by repeating square brackets
2563
Adapted all tests and docs to the new format
2577
Reformatted final docstring in ReST format, indented it for easier folding
2579
Code tests converted to doctest format, and scattered them around
2580
in various docstrings
2582
Walk method rewritten using scalars and sections attributes
2589
Changed Validator and SimpleVal "test" methods to "check"
2596
Changed Section.sequence to Section.scalars and Section.sections
2598
Added Section.configspec
2600
Sections in the root section now have no extra indentation
2602
Comments now better supported in Section and preserved by ConfigObj
2604
Comments also written out
2606
Implemented initial_comment and final_comment
2608
A scalar value after a section will now raise an error
2613
Fixed a couple of bugs
2615
Can now pass a tuple instead of a list
2617
Simplified dict and walk methods
2619
Added __str__ to Section
2631
The stringify option implemented. On by default.
2636
Renamed private attributes with a single underscore prefix.
2638
Changes to interpolation - exceeding recursion depth, or specifying a
2639
missing value, now raise errors.
2641
Changes for Python 2.2 compatibility. (changed boolean tests - removed
2642
``is True`` and ``is False``)
2644
Added test for duplicate section and member (and fixed bug)
2658
Now properly handles values including comments and lists.
2660
Better error handling.
2662
String interpolation.
2664
Some options implemented.
2666
You can pass a Section a dictionary to initialise it.
2668
Setting a Section member to a dictionary will create a Section instance.
2675
Experimental reader.
2677
A reasonably elegant implementation - a basic reader in 160 lines of code.
2679
*A programming language is a medium of expression.* - Paul Graham