bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
1185.12.49
by Aaron Bentley
 Switched to ConfigObj  | 
1  | 
# configobj.py
 | 
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
 | 
|
6  | 
||
7  | 
# ConfigObj 4
 | 
|
8  | 
||
9  | 
# Released subject to the BSD License
 | 
|
10  | 
# Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
 | 
|
11  | 
||
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.
 | 
|
17  | 
||
18  | 
"""
 | 
|
19  | 
    >>> z = ConfigObj()
 | 
|
20  | 
    >>> z['a'] = 'a'
 | 
|
21  | 
    >>> z['sect'] = {
 | 
|
22  | 
    ...    'subsect': {
 | 
|
23  | 
    ...         'a': 'fish',
 | 
|
24  | 
    ...         'b': 'wobble',
 | 
|
25  | 
    ...     },
 | 
|
26  | 
    ...     'member': 'value',
 | 
|
27  | 
    ... }
 | 
|
28  | 
    >>> x = ConfigObj(z.write())
 | 
|
29  | 
    >>> z == x
 | 
|
30  | 
    1
 | 
|
31  | 
"""
 | 
|
32  | 
||
33  | 
import sys  | 
|
34  | 
INTP_VER = sys.version_info[:2]  | 
|
35  | 
if INTP_VER < (2, 2):  | 
|
36  | 
raise RuntimeError("Python v.2.2 or later needed")  | 
|
37  | 
||
38  | 
import os, re  | 
|
39  | 
from types import StringTypes  | 
|
40  | 
||
41  | 
# the UTF8 BOM - from codecs module
 | 
|
42  | 
BOM_UTF8 = '\xef\xbb\xbf'  | 
|
43  | 
||
44  | 
__version__ = '4.0.0'  | 
|
45  | 
||
46  | 
__revision__ = '$Id: configobj.py 135 2005-10-12 14:52:28Z fuzzyman $'  | 
|
47  | 
||
48  | 
__docformat__ = "restructuredtext en"  | 
|
49  | 
||
50  | 
__all__ = (  | 
|
51  | 
'__version__',  | 
|
52  | 
'BOM_UTF8',  | 
|
53  | 
'DEFAULT_INDENT_TYPE',  | 
|
54  | 
'NUM_INDENT_SPACES',  | 
|
55  | 
'MAX_INTERPOL_DEPTH',  | 
|
56  | 
'ConfigObjError',  | 
|
57  | 
'NestingError',  | 
|
58  | 
'ParseError',  | 
|
59  | 
'DuplicateError',  | 
|
60  | 
'ConfigspecError',  | 
|
61  | 
'ConfigObj',  | 
|
62  | 
'SimpleVal',  | 
|
63  | 
'InterpolationError',  | 
|
64  | 
'InterpolationDepthError',  | 
|
65  | 
'MissingInterpolationOption',  | 
|
66  | 
'RepeatSectionError',  | 
|
67  | 
'__docformat__',  | 
|
68  | 
)
 | 
|
69  | 
||
70  | 
DEFAULT_INDENT_TYPE = ' '  | 
|
71  | 
NUM_INDENT_SPACES = 4  | 
|
72  | 
MAX_INTERPOL_DEPTH = 10  | 
|
73  | 
||
74  | 
OPTION_DEFAULTS = {  | 
|
75  | 
'interpolation': True,  | 
|
76  | 
'raise_errors': False,  | 
|
77  | 
'list_values': True,  | 
|
78  | 
'create_empty': False,  | 
|
79  | 
'file_error': False,  | 
|
80  | 
'configspec': None,  | 
|
81  | 
'stringify': True,  | 
|
82  | 
    # option may be set to one of ('', ' ', '\t')
 | 
|
83  | 
'indent_type': None,  | 
|
84  | 
}
 | 
|
85  | 
||
86  | 
class ConfigObjError(SyntaxError):  | 
|
87  | 
"""  | 
|
88  | 
    This is the base class for all errors that ConfigObj raises.
 | 
|
89  | 
    It is a subclass of SyntaxError.
 | 
|
90  | 
    
 | 
|
91  | 
    >>> raise ConfigObjError
 | 
|
92  | 
    Traceback (most recent call last):
 | 
|
93  | 
    ConfigObjError
 | 
|
94  | 
    """
 | 
|
95  | 
def __init__(self, message='', line_number=None, line=''):  | 
|
96  | 
self.line = line  | 
|
97  | 
self.line_number = line_number  | 
|
98  | 
self.message = message  | 
|
99  | 
SyntaxError.__init__(self, message)  | 
|
100  | 
||
101  | 
class NestingError(ConfigObjError):  | 
|
102  | 
"""  | 
|
103  | 
    This error indicates a level of nesting that doesn't match.
 | 
|
104  | 
    
 | 
|
105  | 
    >>> raise NestingError
 | 
|
106  | 
    Traceback (most recent call last):
 | 
|
107  | 
    NestingError
 | 
|
108  | 
    """
 | 
|
109  | 
||
110  | 
class ParseError(ConfigObjError):  | 
|
111  | 
"""  | 
|
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.
 | 
|
115  | 
    
 | 
|
116  | 
    >>> raise ParseError
 | 
|
117  | 
    Traceback (most recent call last):
 | 
|
118  | 
    ParseError
 | 
|
119  | 
    """
 | 
|
120  | 
||
121  | 
class DuplicateError(ConfigObjError):  | 
|
122  | 
"""  | 
|
123  | 
    The keyword or section specified already exists.
 | 
|
124  | 
    
 | 
|
125  | 
    >>> raise DuplicateError
 | 
|
126  | 
    Traceback (most recent call last):
 | 
|
127  | 
    DuplicateError
 | 
|
128  | 
    """
 | 
|
129  | 
||
130  | 
class ConfigspecError(ConfigObjError):  | 
|
131  | 
"""  | 
|
132  | 
    An error occured whilst parsing a configspec.
 | 
|
133  | 
    
 | 
|
134  | 
    >>> raise ConfigspecError
 | 
|
135  | 
    Traceback (most recent call last):
 | 
|
136  | 
    ConfigspecError
 | 
|
137  | 
    """
 | 
|
138  | 
||
139  | 
class InterpolationError(ConfigObjError):  | 
|
140  | 
"""Base class for the two interpolation errors."""  | 
|
141  | 
||
142  | 
class InterpolationDepthError(InterpolationError):  | 
|
143  | 
"""Maximum interpolation depth exceeded in string interpolation."""  | 
|
144  | 
||
145  | 
def __init__(self, option):  | 
|
146  | 
"""  | 
|
147  | 
        >>> raise InterpolationDepthError('yoda')
 | 
|
148  | 
        Traceback (most recent call last):
 | 
|
149  | 
        InterpolationDepthError: max interpolation depth exceeded in value "yoda".
 | 
|
150  | 
        """
 | 
|
151  | 
InterpolationError.__init__(  | 
|
152  | 
self,  | 
|
153  | 
'max interpolation depth exceeded in value "%s".' % option)  | 
|
154  | 
||
155  | 
class RepeatSectionError(ConfigObjError):  | 
|
156  | 
"""  | 
|
157  | 
    This error indicates additional sections in a section with a
 | 
|
158  | 
    ``__many__`` (repeated) section.
 | 
|
159  | 
    
 | 
|
160  | 
    >>> raise RepeatSectionError
 | 
|
161  | 
    Traceback (most recent call last):
 | 
|
162  | 
    RepeatSectionError
 | 
|
163  | 
    """
 | 
|
164  | 
||
165  | 
class MissingInterpolationOption(InterpolationError):  | 
|
166  | 
"""A value specified for interpolation was missing."""  | 
|
167  | 
||
168  | 
def __init__(self, option):  | 
|
169  | 
"""  | 
|
170  | 
        >>> raise MissingInterpolationOption('yoda')
 | 
|
171  | 
        Traceback (most recent call last):
 | 
|
172  | 
        MissingInterpolationOption: missing option "yoda" in interpolation.
 | 
|
173  | 
        """
 | 
|
174  | 
InterpolationError.__init__(  | 
|
175  | 
self,  | 
|
176  | 
'missing option "%s" in interpolation.' % option)  | 
|
177  | 
||
178  | 
class Section(dict):  | 
|
179  | 
"""  | 
|
180  | 
    A dictionary-like object that represents a section in a config file.
 | 
|
181  | 
    
 | 
|
182  | 
    It does string interpolation if the 'interpolate' attribute
 | 
|
183  | 
    of the 'main' object is set to True.
 | 
|
184  | 
    
 | 
|
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.
 | 
|
187  | 
    
 | 
|
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.
 | 
|
191  | 
    
 | 
|
192  | 
    Iteration follows the order: scalars, then sections.
 | 
|
193  | 
    """
 | 
|
194  | 
||
195  | 
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")  | 
|
196  | 
||
197  | 
def __init__(self, parent, depth, main, indict=None, name=None):  | 
|
198  | 
"""  | 
|
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
 | 
|
203  | 
        """
 | 
|
204  | 
if indict is None:  | 
|
205  | 
indict = {}  | 
|
206  | 
dict.__init__(self)  | 
|
207  | 
        # used for nesting level *and* interpolation
 | 
|
208  | 
self.parent = parent  | 
|
209  | 
        # used for the interpolation attribute
 | 
|
210  | 
self.main = main  | 
|
211  | 
        # level of nesting depth of this Section
 | 
|
212  | 
self.depth = depth  | 
|
213  | 
        # the sequence of scalar values in this Section
 | 
|
214  | 
self.scalars = []  | 
|
215  | 
        # the sequence of sections in this Section
 | 
|
216  | 
self.sections = []  | 
|
217  | 
        # purely for information
 | 
|
218  | 
self.name = name  | 
|
219  | 
        # for comments :-)
 | 
|
220  | 
self.comments = {}  | 
|
221  | 
self.inline_comments = {}  | 
|
222  | 
        # for the configspec
 | 
|
223  | 
self.configspec = {}  | 
|
224  | 
        # for defaults
 | 
|
225  | 
self.defaults = []  | 
|
226  | 
        #
 | 
|
227  | 
        # we do this explicitly so that __setitem__ is used properly
 | 
|
228  | 
        # (rather than just passing to ``dict.__init__``)
 | 
|
229  | 
for entry in indict:  | 
|
230  | 
self[entry] = indict[entry]  | 
|
231  | 
||
232  | 
def _interpolate(self, value):  | 
|
233  | 
"""Nicked from ConfigParser."""  | 
|
234  | 
depth = MAX_INTERPOL_DEPTH  | 
|
235  | 
        # loop through this until it's done
 | 
|
236  | 
while depth:  | 
|
237  | 
depth -= 1  | 
|
238  | 
if value.find("%(") != -1:  | 
|
239  | 
value = self._KEYCRE.sub(self._interpolation_replace, value)  | 
|
240  | 
else:  | 
|
241  | 
                break
 | 
|
242  | 
else:  | 
|
243  | 
raise InterpolationDepthError(value)  | 
|
244  | 
return value  | 
|
245  | 
||
246  | 
def _interpolation_replace(self, match):  | 
|
247  | 
""" """  | 
|
248  | 
s = match.group(1)  | 
|
249  | 
if s is None:  | 
|
250  | 
return match.group()  | 
|
251  | 
else:  | 
|
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
 | 
|
257  | 
if val is None:  | 
|
258  | 
val = self.parent.get('DEFAULT', {}).get(s)  | 
|
259  | 
            # last, try the 'DEFAULT' member of the *main section*
 | 
|
260  | 
if val is None:  | 
|
261  | 
val = self.main.get('DEFAULT', {}).get(s)  | 
|
262  | 
self.main.interpolation = True  | 
|
263  | 
if val is None:  | 
|
264  | 
raise MissingInterpolationOption(s)  | 
|
265  | 
return val  | 
|
266  | 
||
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)  | 
|
272  | 
return val  | 
|
273  | 
||
274  | 
def __setitem__(self, key, value):  | 
|
275  | 
"""  | 
|
276  | 
        Correctly set a value.
 | 
|
277  | 
        
 | 
|
278  | 
        Making dictionary values Section instances.
 | 
|
279  | 
        (We have to special case 'Section' instances - which are also dicts)
 | 
|
280  | 
        
 | 
|
281  | 
        Keys must be strings.
 | 
|
282  | 
        Values need only be strings (or lists of strings) if
 | 
|
283  | 
        ``main.stringify`` is set.
 | 
|
284  | 
        """
 | 
|
285  | 
if not isinstance(key, StringTypes):  | 
|
286  | 
raise ValueError, 'The key "%s" is not a string.' % key  | 
|
287  | 
##        if self.depth is None:
 | 
|
288  | 
##            self.depth = 0
 | 
|
289  | 
        # add the comment
 | 
|
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)  | 
|
296  | 
        #
 | 
|
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  | 
|
307  | 
dict.__setitem__(  | 
|
308  | 
self,  | 
|
309  | 
key,  | 
|
310  | 
Section(  | 
|
311  | 
self,  | 
|
312  | 
new_depth,  | 
|
313  | 
self.main,  | 
|
314  | 
indict=value,  | 
|
315  | 
name=key))  | 
|
316  | 
else:  | 
|
317  | 
if not self.has_key(key):  | 
|
318  | 
self.scalars.append(key)  | 
|
319  | 
if not self.main.stringify:  | 
|
320  | 
if isinstance(value, StringTypes):  | 
|
321  | 
                    pass
 | 
|
322  | 
elif isinstance(value, (list, tuple)):  | 
|
323  | 
for entry in value:  | 
|
324  | 
if not isinstance(entry, StringTypes):  | 
|
325  | 
raise TypeError, (  | 
|
326  | 
'Value is not a string "%s".' % entry)  | 
|
327  | 
else:  | 
|
328  | 
raise TypeError, 'Value is not a string "%s".' % value  | 
|
329  | 
dict.__setitem__(self, key, value)  | 
|
330  | 
||
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)  | 
|
336  | 
else:  | 
|
337  | 
self.sections.remove(key)  | 
|
338  | 
del self.comments[key]  | 
|
339  | 
del self.inline_comments[key]  | 
|
340  | 
||
341  | 
def get(self, key, default=None):  | 
|
342  | 
"""A version of ``get`` that doesn't bypass string interpolation."""  | 
|
343  | 
try:  | 
|
344  | 
return self[key]  | 
|
345  | 
except KeyError:  | 
|
346  | 
return default  | 
|
347  | 
||
348  | 
def update(self, indict):  | 
|
349  | 
"""A version of update that uses our ``__setitem__``."""  | 
|
350  | 
for entry in indict:  | 
|
351  | 
self[entry] = indict[entry]  | 
|
352  | 
||
353  | 
def pop(self, key, *args):  | 
|
354  | 
""" """  | 
|
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)  | 
|
366  | 
return val  | 
|
367  | 
||
368  | 
def popitem(self):  | 
|
369  | 
"""Pops the first (key,val)"""  | 
|
370  | 
sequence = (self.scalars + self.sections)  | 
|
371  | 
if not sequence:  | 
|
372  | 
raise KeyError, ": 'popitem(): dictionary is empty'"  | 
|
373  | 
key = sequence[0]  | 
|
374  | 
val = self[key]  | 
|
375  | 
del self[key]  | 
|
376  | 
return key, val  | 
|
377  | 
||
378  | 
def clear(self):  | 
|
379  | 
"""  | 
|
380  | 
        A version of clear that also affects scalars/sections
 | 
|
381  | 
        Also clears comments and configspec.
 | 
|
382  | 
        
 | 
|
383  | 
        Leaves other attributes alone :
 | 
|
384  | 
            depth/main/parent are not affected
 | 
|
385  | 
        """
 | 
|
386  | 
dict.clear(self)  | 
|
387  | 
self.scalars = []  | 
|
388  | 
self.sections = []  | 
|
389  | 
self.comments = {}  | 
|
390  | 
self.inline_comments = {}  | 
|
391  | 
self.configspec = {}  | 
|
392  | 
||
393  | 
def setdefault(self, key, default=None):  | 
|
394  | 
"""A version of setdefault that sets sequence if appropriate."""  | 
|
395  | 
try:  | 
|
396  | 
return self[key]  | 
|
397  | 
except KeyError:  | 
|
398  | 
self[key] = default  | 
|
399  | 
return self[key]  | 
|
400  | 
||
401  | 
def items(self):  | 
|
402  | 
""" """  | 
|
403  | 
return zip((self.scalars + self.sections), self.values())  | 
|
404  | 
||
405  | 
def keys(self):  | 
|
406  | 
""" """  | 
|
407  | 
return (self.scalars + self.sections)  | 
|
408  | 
||
409  | 
def values(self):  | 
|
410  | 
""" """  | 
|
411  | 
return [self[key] for key in (self.scalars + self.sections)]  | 
|
412  | 
||
413  | 
def iteritems(self):  | 
|
414  | 
""" """  | 
|
415  | 
return iter(self.items())  | 
|
416  | 
||
417  | 
def iterkeys(self):  | 
|
418  | 
""" """  | 
|
419  | 
return iter((self.scalars + self.sections))  | 
|
420  | 
||
421  | 
__iter__ = iterkeys  | 
|
422  | 
||
423  | 
def itervalues(self):  | 
|
424  | 
""" """  | 
|
425  | 
return iter(self.values())  | 
|
426  | 
||
427  | 
def __repr__(self):  | 
|
428  | 
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))  | 
|
429  | 
for key in (self.scalars + self.sections)])  | 
|
430  | 
||
431  | 
__str__ = __repr__  | 
|
432  | 
||
433  | 
    # Extra methods - not in a normal dictionary
 | 
|
434  | 
||
435  | 
def dict(self):  | 
|
436  | 
"""  | 
|
437  | 
        Return a deepcopy of self as a dictionary.
 | 
|
438  | 
        
 | 
|
439  | 
        All members that are ``Section`` instances are recursively turned to
 | 
|
440  | 
        ordinary dictionaries - by calling their ``dict`` method.
 | 
|
441  | 
        
 | 
|
442  | 
        >>> n = a.dict()
 | 
|
443  | 
        >>> n == a
 | 
|
444  | 
        1
 | 
|
445  | 
        >>> n is a
 | 
|
446  | 
        0
 | 
|
447  | 
        """
 | 
|
448  | 
newdict = {}  | 
|
449  | 
for entry in self:  | 
|
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  | 
|
457  | 
return newdict  | 
|
458  | 
||
459  | 
def rename(self, oldkey, newkey):  | 
|
460  | 
"""  | 
|
461  | 
        Change a keyname to another, without changing position in sequence.
 | 
|
462  | 
        
 | 
|
463  | 
        Implemented so that transformations can be made on keys,
 | 
|
464  | 
        as well as on values. (used by encode and decode)
 | 
|
465  | 
        
 | 
|
466  | 
        Also renames comments.
 | 
|
467  | 
        """
 | 
|
468  | 
if oldkey in self.scalars:  | 
|
469  | 
the_list = self.scalars  | 
|
470  | 
elif oldkey in self.sections:  | 
|
471  | 
the_list = self.sections  | 
|
472  | 
else:  | 
|
473  | 
raise KeyError, 'Key "%s" not found.' % oldkey  | 
|
474  | 
pos = the_list.index(oldkey)  | 
|
475  | 
        #
 | 
|
476  | 
val = self[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  | 
|
487  | 
||
488  | 
def walk(self, function, raise_errors=True,  | 
|
489  | 
call_on_sections=False, **keywargs):  | 
|
490  | 
"""  | 
|
491  | 
        Walk every member and call a function on the keyword and value.
 | 
|
492  | 
        
 | 
|
493  | 
        Return a dictionary of the return values
 | 
|
494  | 
        
 | 
|
495  | 
        If the function raises an exception, raise the errror
 | 
|
496  | 
        unless ``raise_errors=False``, in which case set the return value to
 | 
|
497  | 
        ``False``.
 | 
|
498  | 
        
 | 
|
499  | 
        Any unrecognised keyword arguments you pass to walk, will be pased on
 | 
|
500  | 
        to the function you pass in.
 | 
|
501  | 
        
 | 
|
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.
 | 
|
508  | 
        
 | 
|
509  | 
        See  the encode and decode methods for examples, including functions.
 | 
|
510  | 
        """
 | 
|
511  | 
out = {}  | 
|
512  | 
        # scalars first
 | 
|
513  | 
for entry in self.scalars[:]:  | 
|
514  | 
try:  | 
|
515  | 
out[entry] = function(self, entry, **keywargs)  | 
|
516  | 
except Exception:  | 
|
517  | 
if raise_errors:  | 
|
518  | 
                    raise
 | 
|
519  | 
else:  | 
|
520  | 
out[entry] = False  | 
|
521  | 
        # then sections
 | 
|
522  | 
for entry in self.sections[:]:  | 
|
523  | 
if call_on_sections:  | 
|
524  | 
try:  | 
|
525  | 
function(self, entry, **keywargs)  | 
|
526  | 
except Exception:  | 
|
527  | 
if raise_errors:  | 
|
528  | 
                        raise
 | 
|
529  | 
else:  | 
|
530  | 
out[entry] = False  | 
|
531  | 
            # previous result is discarded
 | 
|
532  | 
out[entry] = self[entry].walk(  | 
|
533  | 
function,  | 
|
534  | 
raise_errors=raise_errors,  | 
|
535  | 
call_on_sections=call_on_sections,  | 
|
536  | 
**keywargs)  | 
|
537  | 
return out  | 
|
538  | 
||
539  | 
def decode(self, encoding):  | 
|
540  | 
"""  | 
|
541  | 
        Decode all strings and values to unicode, using the specified encoding.
 | 
|
542  | 
        
 | 
|
543  | 
        Works with subsections and list values.
 | 
|
544  | 
        
 | 
|
545  | 
        Uses the ``walk`` method.
 | 
|
546  | 
        
 | 
|
547  | 
        Testing ``encode`` and ``decode``.
 | 
|
548  | 
        >>> m = ConfigObj(a)
 | 
|
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.'
 | 
|
559  | 
        >>> testuni(m)
 | 
|
560  | 
        >>> m.encode('ascii')
 | 
|
561  | 
        >>> a == m
 | 
|
562  | 
        1
 | 
|
563  | 
        """
 | 
|
564  | 
def decode(section, key, encoding=encoding):  | 
|
565  | 
""" """  | 
|
566  | 
val = section[key]  | 
|
567  | 
if isinstance(val, (list, tuple)):  | 
|
568  | 
newval = []  | 
|
569  | 
for entry in val:  | 
|
570  | 
newval.append(entry.decode(encoding))  | 
|
571  | 
elif isinstance(val, dict):  | 
|
572  | 
newval = val  | 
|
573  | 
else:  | 
|
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)  | 
|
580  | 
||
581  | 
def encode(self, encoding):  | 
|
582  | 
"""  | 
|
583  | 
        Encode all strings and values from unicode,
 | 
|
584  | 
        using the specified encoding.
 | 
|
585  | 
        
 | 
|
586  | 
        Works with subsections and list values.
 | 
|
587  | 
        Uses the ``walk`` method.
 | 
|
588  | 
        """
 | 
|
589  | 
def encode(section, key, encoding=encoding):  | 
|
590  | 
""" """  | 
|
591  | 
val = section[key]  | 
|
592  | 
if isinstance(val, (list, tuple)):  | 
|
593  | 
newval = []  | 
|
594  | 
for entry in val:  | 
|
595  | 
newval.append(entry.encode(encoding))  | 
|
596  | 
elif isinstance(val, dict):  | 
|
597  | 
newval = val  | 
|
598  | 
else:  | 
|
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)  | 
|
604  | 
||
605  | 
class ConfigObj(Section):  | 
|
606  | 
"""  | 
|
607  | 
    An object to read, create, and write config files.
 | 
|
608  | 
    
 | 
|
609  | 
    Testing with duplicate keys and sections.
 | 
|
610  | 
    
 | 
|
611  | 
    >>> c = '''
 | 
|
612  | 
    ... [hello]
 | 
|
613  | 
    ... member = value
 | 
|
614  | 
    ... [hello again]
 | 
|
615  | 
    ... member = value
 | 
|
616  | 
    ... [ "hello" ]
 | 
|
617  | 
    ... member = value
 | 
|
618  | 
    ... '''
 | 
|
619  | 
    >>> ConfigObj(c.split('\\n'), raise_errors = True)
 | 
|
620  | 
    Traceback (most recent call last):
 | 
|
621  | 
    DuplicateError: Duplicate section name at line 5.
 | 
|
622  | 
    
 | 
|
623  | 
    >>> d = '''
 | 
|
624  | 
    ... [hello]
 | 
|
625  | 
    ... member = value
 | 
|
626  | 
    ... [hello again]
 | 
|
627  | 
    ... member1 = value
 | 
|
628  | 
    ... member2 = value
 | 
|
629  | 
    ... 'member1' = value
 | 
|
630  | 
    ... [ "and again" ]
 | 
|
631  | 
    ... member = value
 | 
|
632  | 
    ... '''
 | 
|
633  | 
    >>> ConfigObj(d.split('\\n'), raise_errors = True)
 | 
|
634  | 
    Traceback (most recent call last):
 | 
|
635  | 
    DuplicateError: Duplicate keyword name at line 6.
 | 
|
636  | 
    """
 | 
|
637  | 
||
638  | 
_keyword = re.compile(r'''^ # line start  | 
|
639  | 
        (\s*)                   # indentation
 | 
|
640  | 
        (                       # keyword
 | 
|
641  | 
            (?:".*?")|          # double quotes
 | 
|
642  | 
            (?:'.*?')|          # single quotes
 | 
|
643  | 
            (?:[^'"=].*?)       # no quotes
 | 
|
644  | 
        )
 | 
|
645  | 
        \s*=\s*                 # divider
 | 
|
646  | 
        (.*)                    # value (including list values and comments)
 | 
|
647  | 
        $   # line end
 | 
|
648  | 
''',  | 
|
649  | 
re.VERBOSE)  | 
|
650  | 
||
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
 | 
|
661  | 
$''',  | 
|
662  | 
re.VERBOSE)  | 
|
663  | 
||
664  | 
    # this regexp pulls list values out as a single string
 | 
|
665  | 
    # or single values and comments
 | 
|
666  | 
_valueexp = re.compile(r'''^  | 
|
667  | 
        (?:
 | 
|
668  | 
            (?:
 | 
|
669  | 
                (
 | 
|
670  | 
                    (?:
 | 
|
671  | 
                        (?:
 | 
|
672  | 
                            (?:".*?")|              # double quotes
 | 
|
673  | 
                            (?:'.*?')|              # single quotes
 | 
|
674  | 
                            (?:[^'",\#][^,\#]*?)       # unquoted
 | 
|
675  | 
                        )
 | 
|
676  | 
                        \s*,\s*                     # comma
 | 
|
677  | 
                    )*      # match all list items ending in a comma (if any)
 | 
|
678  | 
                )
 | 
|
679  | 
                (
 | 
|
680  | 
                    (?:".*?")|                      # double quotes
 | 
|
681  | 
                    (?:'.*?')|                      # single quotes
 | 
|
682  | 
                    (?:[^'",\#\s][^,]*?)             # unquoted
 | 
|
683  | 
                )?          # last item in a list - or string value
 | 
|
684  | 
            )|
 | 
|
685  | 
            (,)             # alternatively a single comma - empty list
 | 
|
686  | 
        )
 | 
|
687  | 
        \s*(\#.*)?          # optional comment
 | 
|
688  | 
$''',  | 
|
689  | 
re.VERBOSE)  | 
|
690  | 
||
691  | 
    # use findall to get the members of a list value
 | 
|
692  | 
_listvalueexp = re.compile(r'''  | 
|
693  | 
        (
 | 
|
694  | 
            (?:".*?")|          # double quotes
 | 
|
695  | 
            (?:'.*?')|          # single quotes
 | 
|
696  | 
            (?:[^'",\#].*?)       # unquoted
 | 
|
697  | 
        )
 | 
|
698  | 
        \s*,\s*                 # comma
 | 
|
699  | 
''',  | 
|
700  | 
re.VERBOSE)  | 
|
701  | 
||
702  | 
    # this regexp is used for the value
 | 
|
703  | 
    # when lists are switched off
 | 
|
704  | 
_nolistvalue = re.compile(r'''^  | 
|
705  | 
        (
 | 
|
706  | 
            (?:".*?")|          # double quotes
 | 
|
707  | 
            (?:'.*?')|          # single quotes
 | 
|
708  | 
            (?:[^'"\#].*?)      # unquoted
 | 
|
709  | 
        )
 | 
|
710  | 
        \s*(\#.*)?              # optional comment
 | 
|
711  | 
$''',  | 
|
712  | 
re.VERBOSE)  | 
|
713  | 
||
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*(#.*)?$')  | 
|
719  | 
||
720  | 
_triple_quote = {  | 
|
721  | 
"'''": (_single_line_single, _multi_line_single),  | 
|
722  | 
'"""': (_single_line_double, _multi_line_double),  | 
|
723  | 
    }
 | 
|
724  | 
||
725  | 
def __init__(self, infile=None, options=None, **kwargs):  | 
|
726  | 
"""  | 
|
727  | 
        Parse or create a config file object.
 | 
|
728  | 
        
 | 
|
729  | 
        ``ConfigObj(infile=None, options=None, **kwargs)``
 | 
|
730  | 
        """
 | 
|
731  | 
if infile is None:  | 
|
732  | 
infile = []  | 
|
733  | 
if options is None:  | 
|
734  | 
options = {}  | 
|
735  | 
        # keyword arguments take precedence over an options dictionary
 | 
|
736  | 
options.update(kwargs)  | 
|
737  | 
        # init the superclass
 | 
|
738  | 
Section.__init__(self, self, 0, self)  | 
|
739  | 
        #
 | 
|
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)  | 
|
747  | 
        #
 | 
|
748  | 
        # initialise a few variables
 | 
|
749  | 
self._errors = []  | 
|
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
 | 
|
758  | 
self.BOM = None  | 
|
759  | 
        #
 | 
|
760  | 
self.initial_comment = []  | 
|
761  | 
self.final_comment = []  | 
|
762  | 
        #
 | 
|
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  | 
|
770  | 
else:  | 
|
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)  | 
|
776  | 
h.write('')  | 
|
777  | 
h.close()  | 
|
778  | 
infile = []  | 
|
779  | 
elif isinstance(infile, (list, tuple)):  | 
|
780  | 
self.filename = None  | 
|
781  | 
elif isinstance(infile, dict):  | 
|
782  | 
            # initialise self
 | 
|
783  | 
            # the Section class handles creating subsections
 | 
|
784  | 
if isinstance(infile, ConfigObj):  | 
|
785  | 
                # get a copy of our ConfigObj
 | 
|
786  | 
infile = infile.dict()  | 
|
787  | 
for entry in infile:  | 
|
788  | 
self[entry] = infile[entry]  | 
|
789  | 
self.filename = None  | 
|
790  | 
del self._errors  | 
|
791  | 
if defaults['configspec'] is not None:  | 
|
792  | 
self._handle_configspec(defaults['configspec'])  | 
|
793  | 
else:  | 
|
794  | 
self.configspec = None  | 
|
795  | 
            return
 | 
|
796  | 
elif hasattr(infile, 'seek'):  | 
|
797  | 
            # this supports StringIO instances and even file objects
 | 
|
798  | 
self.filename = infile  | 
|
799  | 
infile.seek(0)  | 
|
800  | 
infile = infile.readlines()  | 
|
801  | 
self.filename.seek(0)  | 
|
802  | 
else:  | 
|
803  | 
raise TypeError, ('infile must be a filename,'  | 
|
804  | 
' StringIO instance, or a file as a list.')  | 
|
805  | 
        #
 | 
|
806  | 
        # strip trailing '\n' from lines
 | 
|
807  | 
infile = [line.rstrip('\n') for line in infile]  | 
|
808  | 
        #
 | 
|
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:]  | 
|
813  | 
self.BOM = BOM_UTF8  | 
|
814  | 
else:  | 
|
815  | 
self.BOM = None  | 
|
816  | 
        #
 | 
|
817  | 
self._parse(infile)  | 
|
818  | 
        # if we had any errors, now is the time to raise them
 | 
|
819  | 
if self._errors:  | 
|
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
 | 
|
825  | 
error.config = self  | 
|
826  | 
raise error  | 
|
827  | 
        # delete private attributes
 | 
|
828  | 
del self._errors  | 
|
829  | 
        #
 | 
|
830  | 
if defaults['configspec'] is None:  | 
|
831  | 
self.configspec = None  | 
|
832  | 
else:  | 
|
833  | 
self._handle_configspec(defaults['configspec'])  | 
|
834  | 
||
835  | 
def _parse(self, infile):  | 
|
836  | 
"""  | 
|
837  | 
        Actually parse the config file
 | 
|
838  | 
        
 | 
|
839  | 
        Testing Interpolation
 | 
|
840  | 
        
 | 
|
841  | 
        >>> c = ConfigObj()
 | 
|
842  | 
        >>> c['DEFAULT'] = {
 | 
|
843  | 
        ...     'b': 'goodbye',
 | 
|
844  | 
        ...     'userdir': 'c:\\\\home',
 | 
|
845  | 
        ...     'c': '%(d)s',
 | 
|
846  | 
        ...     'd': '%(c)s'
 | 
|
847  | 
        ... }
 | 
|
848  | 
        >>> c['section'] = {
 | 
|
849  | 
        ...     'a': '%(datadir)s\\\\some path\\\\file.py',
 | 
|
850  | 
        ...     'b': '%(userdir)s\\\\some path\\\\file.py',
 | 
|
851  | 
        ...     'c': 'Yo %(a)s',
 | 
|
852  | 
        ...     'd': '%(not_here)s',
 | 
|
853  | 
        ...     'e': '%(c)s',
 | 
|
854  | 
        ... }
 | 
|
855  | 
        >>> c['section']['DEFAULT'] = {
 | 
|
856  | 
        ...     'datadir': 'c:\\\\silly_test',
 | 
|
857  | 
        ...     'a': 'hello - %(b)s',
 | 
|
858  | 
        ... }
 | 
|
859  | 
        >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
 | 
|
860  | 
        1
 | 
|
861  | 
        >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
 | 
|
862  | 
        1
 | 
|
863  | 
        >>> c['section']['c'] == 'Yo hello - goodbye'
 | 
|
864  | 
        1
 | 
|
865  | 
        
 | 
|
866  | 
        Switching Interpolation Off
 | 
|
867  | 
        
 | 
|
868  | 
        >>> c.interpolation = False
 | 
|
869  | 
        >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
 | 
|
870  | 
        1
 | 
|
871  | 
        >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
 | 
|
872  | 
        1
 | 
|
873  | 
        >>> c['section']['c'] == 'Yo %(a)s'
 | 
|
874  | 
        1
 | 
|
875  | 
        
 | 
|
876  | 
        Testing the interpolation errors.
 | 
|
877  | 
        
 | 
|
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".
 | 
|
885  | 
        
 | 
|
886  | 
        Testing our quoting.
 | 
|
887  | 
        
 | 
|
888  | 
>>> i._quote('\"""\'\'\'')  | 
|
889  | 
Traceback (most recent call last):  | 
|
890  | 
SyntaxError: EOF while scanning triple-quoted string  | 
|
891  | 
>>> try:  | 
|
892  | 
... i._quote('\\n', multiline=False)  | 
|
893  | 
... except ConfigObjError, e:  | 
|
894  | 
... e.msg  | 
|
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  | 
|
899  | 
||
900  | 
Testing with "stringify" off.  | 
|
901  | 
>>> c.stringify = False  | 
|
902  | 
>>> c['test'] = 1  | 
|
903  | 
Traceback (most recent call last):  | 
|
904  | 
TypeError: Value is not a string "1".  | 
|
905  | 
"""  | 
|
906  | 
        comment_list = []
 | 
|
907  | 
        done_start = False
 | 
|
908  | 
        this_section = self
 | 
|
909  | 
        maxline = len(infile) - 1
 | 
|
910  | 
        cur_index = -1
 | 
|
911  | 
        reset_comment = False
 | 
|
912  | 
        while cur_index < maxline:
 | 
|
913  | 
            if reset_comment:
 | 
|
914  | 
                comment_list = []
 | 
|
915  | 
            cur_index += 1
 | 
|
916  | 
            line = infile[cur_index]
 | 
|
917  | 
            sline = line.strip()
 | 
|
918  | 
            # do we have anything on the line ?
 | 
|
919  | 
            if not sline or sline.startswith('#'):
 | 
|
920  | 
                reset_comment = False
 | 
|
921  | 
                comment_list.append(line)
 | 
|
922  | 
                continue
 | 
|
923  | 
            if not done_start:
 | 
|
924  | 
                # preserve initial comment
 | 
|
925  | 
                self.initial_comment = comment_list
 | 
|
926  | 
                comment_list = []
 | 
|
927  | 
                done_start = True
 | 
|
928  | 
            reset_comment = True
 | 
|
929  | 
            # first we check if it's a section marker
 | 
|
930  | 
            mat = self._sectionmarker.match(line)
 | 
|
931  | 
##            print >> sys.stderr, sline, mat
 | 
|
932  | 
            if mat is not None:
 | 
|
933  | 
                # is a section line
 | 
|
934  | 
                (indent, sect_open, sect_name, sect_close, comment) = (
 | 
|
935  | 
                    mat.groups())
 | 
|
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(']'):
 | 
|
940  | 
                    self._handle_error(
 | 
|
941  | 
                        "Cannot compute the section depth at line %s.",
 | 
|
942  | 
                        NestingError, infile, cur_index)
 | 
|
943  | 
                    continue
 | 
|
944  | 
                if cur_depth < this_section.depth:
 | 
|
945  | 
                    # the new section is dropping back to a previous level
 | 
|
946  | 
                    try:
 | 
|
947  | 
                        parent = self._match_depth(
 | 
|
948  | 
                            this_section,
 | 
|
949  | 
                            cur_depth).parent
 | 
|
950  | 
                    except SyntaxError:
 | 
|
951  | 
                        self._handle_error(
 | 
|
952  | 
                            "Cannot compute nesting level at line %s.",
 | 
|
953  | 
                            NestingError, infile, cur_index)
 | 
|
954  | 
                        continue
 | 
|
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
 | 
|
961  | 
                else:
 | 
|
962  | 
                    self._handle_error(
 | 
|
963  | 
                        "Section too nested at line %s.",
 | 
|
964  | 
                        NestingError, infile, cur_index)
 | 
|
965  | 
                #
 | 
|
966  | 
                sect_name = self._unquote(sect_name)
 | 
|
967  | 
                if parent.has_key(sect_name):
 | 
|
968  | 
##                    print >> sys.stderr, sect_name
 | 
|
969  | 
                    self._handle_error(
 | 
|
970  | 
                        'Duplicate section name at line %s.',
 | 
|
971  | 
                        DuplicateError, infile, cur_index)
 | 
|
972  | 
                    continue
 | 
|
973  | 
                # create the new section
 | 
|
974  | 
                this_section = Section(
 | 
|
975  | 
                    parent,
 | 
|
976  | 
                    cur_depth,
 | 
|
977  | 
                    self,
 | 
|
978  | 
                    name=sect_name)
 | 
|
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
 | 
|
983  | 
                continue
 | 
|
984  | 
            #
 | 
|
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
 | 
|
989  | 
            if mat is not None:
 | 
|
990  | 
                # is a keyword value
 | 
|
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 ['"""', "'''"]:  | 
|
997  | 
try:  | 
|
998  | 
(value, comment, cur_index) = self._multiline(  | 
|
999  | 
value, infile, cur_index, maxline)  | 
|
1000  | 
except SyntaxError:  | 
|
1001  | 
self._handle_error(  | 
|
1002  | 
'Parse error in value at line %s.',  | 
|
1003  | 
ParseError, infile, cur_index)  | 
|
1004  | 
                        continue
 | 
|
1005  | 
else:  | 
|
1006  | 
                    # extract comment and lists
 | 
|
1007  | 
try:  | 
|
1008  | 
(value, comment) = self._handle_value(value)  | 
|
1009  | 
except SyntaxError:  | 
|
1010  | 
self._handle_error(  | 
|
1011  | 
'Parse error in value at line %s.',  | 
|
1012  | 
ParseError, infile, cur_index)  | 
|
1013  | 
                        continue
 | 
|
1014  | 
                #
 | 
|
1015  | 
##                print >> sys.stderr, sline
 | 
|
1016  | 
key = self._unquote(key)  | 
|
1017  | 
if this_section.has_key(key):  | 
|
1018  | 
self._handle_error(  | 
|
1019  | 
'Duplicate keyword name at line %s.',  | 
|
1020  | 
DuplicateError, infile, cur_index)  | 
|
1021  | 
                    continue
 | 
|
1022  | 
                # add the key
 | 
|
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]
 | 
|
1032  | 
                continue
 | 
|
1033  | 
            #
 | 
|
1034  | 
            # it neither matched as a keyword
 | 
|
1035  | 
            # or a section marker
 | 
|
1036  | 
self._handle_error(  | 
|
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  | 
|
1044  | 
||
1045  | 
def _match_depth(self, sect, depth):  | 
|
1046  | 
"""  | 
|
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.
 | 
|
1049  | 
        
 | 
|
1050  | 
        Return a reference to the right section,
 | 
|
1051  | 
        or raise a SyntaxError.
 | 
|
1052  | 
        """
 | 
|
1053  | 
while depth < sect.depth:  | 
|
1054  | 
if sect is sect.parent:  | 
|
1055  | 
                # we've reached the top level already
 | 
|
1056  | 
raise SyntaxError  | 
|
1057  | 
sect = sect.parent  | 
|
1058  | 
if sect.depth == depth:  | 
|
1059  | 
return sect  | 
|
1060  | 
        # shouldn't get here
 | 
|
1061  | 
raise SyntaxError  | 
|
1062  | 
||
1063  | 
def _handle_error(self, text, ErrorClass, infile, cur_index):  | 
|
1064  | 
"""  | 
|
1065  | 
        Handle an error according to the error settings.
 | 
|
1066  | 
        
 | 
|
1067  | 
        Either raise the error or store it.
 | 
|
1068  | 
        The error will have occured at ``cur_index``
 | 
|
1069  | 
        """
 | 
|
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
 | 
|
1075  | 
raise error  | 
|
1076  | 
        # store the error
 | 
|
1077  | 
        # reraise when parsing has finished
 | 
|
1078  | 
self._errors.append(error)  | 
|
1079  | 
||
1080  | 
def _unquote(self, value):  | 
|
1081  | 
"""Return an unquoted version of a value"""  | 
|
1082  | 
if (value[0] == value[-1]) and (value[0] in ('"', "'")):  | 
|
1083  | 
value = value[1:-1]  | 
|
1084  | 
return value  | 
|
1085  | 
||
1086  | 
def _quote(self, value, multiline=True):  | 
|
1087  | 
"""  | 
|
1088  | 
        Return a safely quoted version of a value.
 | 
|
1089  | 
        
 | 
|
1090  | 
        Raise a ConfigObjError if the value cannot be safely quoted.
 | 
|
1091  | 
        If multiline is ``True`` (default) then use triple quotes
 | 
|
1092  | 
        if necessary.
 | 
|
1093  | 
        
 | 
|
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.
 | 
|
1098  | 
        """
 | 
|
1099  | 
if isinstance(value, (list, tuple)):  | 
|
1100  | 
if not value:  | 
|
1101  | 
return ','  | 
|
1102  | 
elif len(value) == 1:  | 
|
1103  | 
return self._quote(value[0], multiline=False) + ','  | 
|
1104  | 
return ','.join([self._quote(val, multiline=False)  | 
|
1105  | 
for val in value])  | 
|
1106  | 
if not isinstance(value, StringTypes):  | 
|
1107  | 
if self.stringify:  | 
|
1108  | 
value = str(value)  | 
|
1109  | 
else:  | 
|
1110  | 
raise TypeError, 'Value "%s" is not a string.' % value  | 
|
1111  | 
squot = "'%s'"  | 
|
1112  | 
dquot = '"%s"'  | 
|
1113  | 
noquot = "%s"  | 
|
1114  | 
wspace_plus = ' \r\t\n\v\t\'"'  | 
|
1115  | 
tsquot = '"""%s"""'  | 
|
1116  | 
tdquot = "'''%s'''"  | 
|
1117  | 
if not value:  | 
|
1118  | 
return '""'  | 
|
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
 | 
|
1122  | 
if '\n' in value:  | 
|
1123  | 
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %  | 
|
1124  | 
value)  | 
|
1125  | 
if ((value[0] not in wspace_plus) and  | 
|
1126  | 
(value[-1] not in wspace_plus) and  | 
|
1127  | 
(',' not in value)):  | 
|
1128  | 
quot = noquot  | 
|
1129  | 
else:  | 
|
1130  | 
if ("'" in value) and ('"' in value):  | 
|
1131  | 
raise ConfigObjError, (  | 
|
1132  | 
'Value "%s" cannot be safely quoted.' % value)  | 
|
1133  | 
elif '"' in value:  | 
|
1134  | 
quot = squot  | 
|
1135  | 
else:  | 
|
1136  | 
quot = dquot  | 
|
1137  | 
else:  | 
|
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:  | 
|
1143  | 
quot = tdquot  | 
|
1144  | 
else:  | 
|
1145  | 
quot = tsquot  | 
|
1146  | 
return quot % value  | 
|
1147  | 
||
1148  | 
def _handle_value(self, value):  | 
|
1149  | 
"""  | 
|
1150  | 
        Given a value string, unquote, remove comment,
 | 
|
1151  | 
        handle lists. (including empty and single member lists)
 | 
|
1152  | 
        
 | 
|
1153  | 
        Testing list values.
 | 
|
1154  | 
        
 | 
|
1155  | 
        >>> testconfig3 = '''
 | 
|
1156  | 
        ... a = ,
 | 
|
1157  | 
        ... b = test,
 | 
|
1158  | 
        ... c = test1, test2   , test3
 | 
|
1159  | 
        ... d = test1, test2, test3,
 | 
|
1160  | 
        ... '''
 | 
|
1161  | 
        >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
 | 
|
1162  | 
        >>> d['a'] == []
 | 
|
1163  | 
        1
 | 
|
1164  | 
        >>> d['b'] == ['test']
 | 
|
1165  | 
        1
 | 
|
1166  | 
        >>> d['c'] == ['test1', 'test2', 'test3']
 | 
|
1167  | 
        1
 | 
|
1168  | 
        >>> d['d'] == ['test1', 'test2', 'test3']
 | 
|
1169  | 
        1
 | 
|
1170  | 
        
 | 
|
1171  | 
        Testing with list values off.
 | 
|
1172  | 
        
 | 
|
1173  | 
        >>> e = ConfigObj(
 | 
|
1174  | 
        ...     testconfig3.split('\\n'),
 | 
|
1175  | 
        ...     raise_errors=True,
 | 
|
1176  | 
        ...     list_values=False)
 | 
|
1177  | 
        >>> e['a'] == ','
 | 
|
1178  | 
        1
 | 
|
1179  | 
        >>> e['b'] == 'test,'
 | 
|
1180  | 
        1
 | 
|
1181  | 
        >>> e['c'] == 'test1, test2   , test3'
 | 
|
1182  | 
        1
 | 
|
1183  | 
        >>> e['d'] == 'test1, test2, test3,'
 | 
|
1184  | 
        1
 | 
|
1185  | 
        
 | 
|
1186  | 
        Testing creating from a dictionary.
 | 
|
1187  | 
        
 | 
|
1188  | 
        >>> f = {
 | 
|
1189  | 
        ...     'key1': 'val1',
 | 
|
1190  | 
        ...     'key2': 'val2',
 | 
|
1191  | 
        ...     'section 1': {
 | 
|
1192  | 
        ...         'key1': 'val1',
 | 
|
1193  | 
        ...         'key2': 'val2',
 | 
|
1194  | 
        ...         'section 1b': {
 | 
|
1195  | 
        ...             'key1': 'val1',
 | 
|
1196  | 
        ...             'key2': 'val2',
 | 
|
1197  | 
        ...         },
 | 
|
1198  | 
        ...     },
 | 
|
1199  | 
        ...     'section 2': {
 | 
|
1200  | 
        ...         'key1': 'val1',
 | 
|
1201  | 
        ...         'key2': 'val2',
 | 
|
1202  | 
        ...         'section 2b': {
 | 
|
1203  | 
        ...             'key1': 'val1',
 | 
|
1204  | 
        ...             'key2': 'val2',
 | 
|
1205  | 
        ...         },
 | 
|
1206  | 
        ...     },
 | 
|
1207  | 
        ...      'key3': 'val3',
 | 
|
1208  | 
        ... }
 | 
|
1209  | 
        >>> g = ConfigObj(f)
 | 
|
1210  | 
        >>> f == g
 | 
|
1211  | 
        1
 | 
|
1212  | 
        
 | 
|
1213  | 
        Testing we correctly detect badly built list values (4 of them).
 | 
|
1214  | 
        
 | 
|
1215  | 
        >>> testconfig4 = '''
 | 
|
1216  | 
        ... config = 3,4,,
 | 
|
1217  | 
        ... test = 3,,4
 | 
|
1218  | 
        ... fish = ,,
 | 
|
1219  | 
        ... dummy = ,,hello, goodbye
 | 
|
1220  | 
        ... '''
 | 
|
1221  | 
        >>> try:
 | 
|
1222  | 
        ...     ConfigObj(testconfig4.split('\\n'))
 | 
|
1223  | 
        ... except ConfigObjError, e:
 | 
|
1224  | 
        ...     len(e.errors)
 | 
|
1225  | 
        4
 | 
|
1226  | 
        
 | 
|
1227  | 
        Testing we correctly detect badly quoted values (4 of them).
 | 
|
1228  | 
        
 | 
|
1229  | 
        >>> testconfig5 = '''
 | 
|
1230  | 
        ... config = "hello   # comment
 | 
|
1231  | 
        ... test = 'goodbye
 | 
|
1232  | 
        ... fish = 'goodbye   # comment
 | 
|
1233  | 
        ... dummy = "hello again
 | 
|
1234  | 
        ... '''
 | 
|
1235  | 
        >>> try:
 | 
|
1236  | 
        ...     ConfigObj(testconfig5.split('\\n'))
 | 
|
1237  | 
        ... except ConfigObjError, e:
 | 
|
1238  | 
        ...     len(e.errors)
 | 
|
1239  | 
        4
 | 
|
1240  | 
        """
 | 
|
1241  | 
        # do we look for lists in values ?
 | 
|
1242  | 
if not self.list_values:  | 
|
1243  | 
mat = self._nolistvalue.match(value)  | 
|
1244  | 
if mat is None:  | 
|
1245  | 
raise SyntaxError  | 
|
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)  | 
|
1250  | 
if mat is None:  | 
|
1251  | 
            # the value is badly constructed, probably badly quoted,
 | 
|
1252  | 
            # or an invalid list
 | 
|
1253  | 
raise SyntaxError  | 
|
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
 | 
|
1257  | 
raise SyntaxError  | 
|
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 == '':  | 
|
1266  | 
            # not a list value
 | 
|
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)  | 
|
1273  | 
||
1274  | 
def _multiline(self, value, infile, cur_index, maxline):  | 
|
1275  | 
"""  | 
|
1276  | 
        Extract the value, where we are in a multiline situation
 | 
|
1277  | 
        
 | 
|
1278  | 
        Testing multiline values.
 | 
|
1279  | 
        
 | 
|
1280  | 
        >>> i == {
 | 
|
1281  | 
        ...     'name4': ' another single line value ',
 | 
|
1282  | 
        ...     'multi section': {
 | 
|
1283  | 
        ...         'name4': '\\n        Well, this is a\\n        multiline '
 | 
|
1284  | 
        ...             'value\\n        ',
 | 
|
1285  | 
        ...         'name2': '\\n        Well, this is a\\n        multiline '
 | 
|
1286  | 
        ...             'value\\n        ',
 | 
|
1287  | 
        ...         'name3': '\\n        Well, this is a\\n        multiline '
 | 
|
1288  | 
        ...             'value\\n        ',
 | 
|
1289  | 
        ...         'name1': '\\n        Well, this is a\\n        multiline '
 | 
|
1290  | 
        ...             'value\\n        ',
 | 
|
1291  | 
        ...     },
 | 
|
1292  | 
        ...     'name2': ' another single line value ',
 | 
|
1293  | 
        ...     'name3': ' a single line value ',
 | 
|
1294  | 
        ...     'name1': ' a single line value ',
 | 
|
1295  | 
        ... }
 | 
|
1296  | 
        1
 | 
|
1297  | 
        """
 | 
|
1298  | 
quot = value[:3]  | 
|
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)  | 
|
1303  | 
if mat is not None:  | 
|
1304  | 
retval = list(mat.groups())  | 
|
1305  | 
retval.append(cur_index)  | 
|
1306  | 
return retval  | 
|
1307  | 
elif newvalue.find(quot) != -1:  | 
|
1308  | 
            # somehow the triple quote is missing
 | 
|
1309  | 
raise SyntaxError  | 
|
1310  | 
        #
 | 
|
1311  | 
while cur_index < maxline:  | 
|
1312  | 
cur_index += 1  | 
|
1313  | 
newvalue += '\n'  | 
|
1314  | 
line = infile[cur_index]  | 
|
1315  | 
if line.find(quot) == -1:  | 
|
1316  | 
newvalue += line  | 
|
1317  | 
else:  | 
|
1318  | 
                # end of multiline, process it
 | 
|
1319  | 
                break
 | 
|
1320  | 
else:  | 
|
1321  | 
            # we've got to the end of the config, oops...
 | 
|
1322  | 
raise SyntaxError  | 
|
1323  | 
mat = multi_line.match(line)  | 
|
1324  | 
if mat is None:  | 
|
1325  | 
            # a badly formed line
 | 
|
1326  | 
raise SyntaxError  | 
|
1327  | 
(value, comment) = mat.groups()  | 
|
1328  | 
return (newvalue + value, comment, cur_index)  | 
|
1329  | 
||
1330  | 
def _handle_configspec(self, configspec):  | 
|
1331  | 
"""Parse the configspec."""  | 
|
1332  | 
try:  | 
|
1333  | 
configspec = ConfigObj(  | 
|
1334  | 
configspec,  | 
|
1335  | 
raise_errors=True,  | 
|
1336  | 
file_error=True,  | 
|
1337  | 
list_values=False)  | 
|
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)  | 
|
1342  | 
except IOError, e:  | 
|
1343  | 
raise IOError('Reading configspec failed: %s' % e)  | 
|
1344  | 
self._set_configspec_value(configspec, self)  | 
|
1345  | 
||
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__':  | 
|
1357  | 
                continue
 | 
|
1358  | 
if not section.has_key(entry):  | 
|
1359  | 
section[entry] = {}  | 
|
1360  | 
self._set_configspec_value(configspec[entry], section[entry])  | 
|
1361  | 
||
1362  | 
def _handle_repeat(self, section, configspec):  | 
|
1363  | 
"""Dynamically assign configspec for repeated section."""  | 
|
1364  | 
try:  | 
|
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  | 
|
1375  | 
scalars = {}  | 
|
1376  | 
sections = {}  | 
|
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  | 
|
1384  | 
                continue
 | 
|
1385  | 
sections[entry] = val  | 
|
1386  | 
        #
 | 
|
1387  | 
section.configspec = scalars  | 
|
1388  | 
for entry in sections:  | 
|
1389  | 
if not section.has_key(entry):  | 
|
1390  | 
section[entry] = {}  | 
|
1391  | 
self._handle_repeat(section[entry], sections[entry])  | 
|
1392  | 
||
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' % (  | 
|
1396  | 
indent_string,  | 
|
1397  | 
self._quote(entry, multiline=False),  | 
|
1398  | 
self._quote(this_entry),  | 
|
1399  | 
comment)  | 
|
1400  | 
||
1401  | 
def _write_marker(self, indent_string, depth, entry, comment):  | 
|
1402  | 
"""Write a section marker line"""  | 
|
1403  | 
return '%s%s%s%s%s' % (  | 
|
1404  | 
indent_string,  | 
|
1405  | 
'[' * depth,  | 
|
1406  | 
self._quote(entry, multiline=False),  | 
|
1407  | 
']' * depth,  | 
|
1408  | 
comment)  | 
|
1409  | 
||
1410  | 
def _handle_comment(self, comment):  | 
|
1411  | 
"""  | 
|
1412  | 
        Deal with a comment.
 | 
|
1413  | 
        
 | 
|
1414  | 
        >>> filename = a.filename
 | 
|
1415  | 
        >>> a.filename = None
 | 
|
1416  | 
        >>> values = a.write()
 | 
|
1417  | 
        >>> index = 0
 | 
|
1418  | 
        >>> while index < 23:
 | 
|
1419  | 
        ...     index += 1
 | 
|
1420  | 
        ...     line = values[index-1]
 | 
|
1421  | 
        ...     assert line.endswith('# comment ' + str(index))
 | 
|
1422  | 
        >>> a.filename = filename
 | 
|
1423  | 
        
 | 
|
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
 | 
|
1433  | 
        1
 | 
|
1434  | 
        >>> nc.final_comment == end_comment
 | 
|
1435  | 
        1
 | 
|
1436  | 
        """
 | 
|
1437  | 
if not comment:  | 
|
1438  | 
return ''  | 
|
1439  | 
if self.indent_type == '\t':  | 
|
1440  | 
start = '\t'  | 
|
1441  | 
else:  | 
|
1442  | 
start = ' ' * NUM_INDENT_SPACES  | 
|
1443  | 
if not comment.startswith('#'):  | 
|
1444  | 
start += '# '  | 
|
1445  | 
return (start + comment)  | 
|
1446  | 
||
1447  | 
def _compute_indent_string(self, depth):  | 
|
1448  | 
"""  | 
|
1449  | 
        Compute the indent string, according to current indent_type and depth
 | 
|
1450  | 
        """
 | 
|
1451  | 
if self.indent_type == '':  | 
|
1452  | 
            # no indentation at all
 | 
|
1453  | 
return ''  | 
|
1454  | 
if self.indent_type == '\t':  | 
|
1455  | 
return '\t' * depth  | 
|
1456  | 
if self.indent_type == ' ':  | 
|
1457  | 
return ' ' * NUM_INDENT_SPACES * depth  | 
|
1458  | 
raise SyntaxError  | 
|
1459  | 
||
1460  | 
    # Public methods
 | 
|
1461  | 
||
1462  | 
def write(self, section=None):  | 
|
1463  | 
"""  | 
|
1464  | 
        Write the current ConfigObj as a file
 | 
|
1465  | 
        
 | 
|
1466  | 
        tekNico: FIXME: use StringIO instead of real files
 | 
|
1467  | 
        
 | 
|
1468  | 
        >>> filename = a.filename
 | 
|
1469  | 
        >>> a.filename = 'test.ini'
 | 
|
1470  | 
        >>> a.write()
 | 
|
1471  | 
        >>> a.filename = filename
 | 
|
1472  | 
        >>> a == ConfigObj('test.ini', raise_errors=True)
 | 
|
1473  | 
        1
 | 
|
1474  | 
        >>> os.remove('test.ini')
 | 
|
1475  | 
        >>> b.filename = 'test.ini'
 | 
|
1476  | 
        >>> b.write()
 | 
|
1477  | 
        >>> b == ConfigObj('test.ini', raise_errors=True)
 | 
|
1478  | 
        1
 | 
|
1479  | 
        >>> os.remove('test.ini')
 | 
|
1480  | 
        >>> i.filename = 'test.ini'
 | 
|
1481  | 
        >>> i.write()
 | 
|
1482  | 
        >>> i == ConfigObj('test.ini', raise_errors=True)
 | 
|
1483  | 
        1
 | 
|
1484  | 
        >>> os.remove('test.ini')
 | 
|
1485  | 
        >>> a = ConfigObj()
 | 
|
1486  | 
        >>> a['DEFAULT'] = {'a' : 'fish'}
 | 
|
1487  | 
        >>> a['a'] = '%(a)s'
 | 
|
1488  | 
        >>> a.write()
 | 
|
1489  | 
        ['a = %(a)s', '[DEFAULT]', 'a = fish']
 | 
|
1490  | 
        """
 | 
|
1491  | 
int_val = 'test'  | 
|
1492  | 
if self.indent_type is None:  | 
|
1493  | 
            # this can be true if initialised from a dictionary
 | 
|
1494  | 
self.indent_type = DEFAULT_INDENT_TYPE  | 
|
1495  | 
        #
 | 
|
1496  | 
out = []  | 
|
1497  | 
return_list = True  | 
|
1498  | 
if section is None:  | 
|
1499  | 
int_val = self.interpolation  | 
|
1500  | 
self.interpolation = False  | 
|
1501  | 
section = self  | 
|
1502  | 
return_list = False  | 
|
1503  | 
for line in self.initial_comment:  | 
|
1504  | 
stripped_line = line.strip()  | 
|
1505  | 
if stripped_line and not stripped_line.startswith('#'):  | 
|
1506  | 
line = '# ' + line  | 
|
1507  | 
out.append(line)  | 
|
1508  | 
        #
 | 
|
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
 | 
|
1513  | 
                continue
 | 
|
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])  | 
|
1521  | 
            #
 | 
|
1522  | 
if isinstance(this_entry, dict):  | 
|
1523  | 
                # a section
 | 
|
1524  | 
out.append(self._write_marker(  | 
|
1525  | 
indent_string,  | 
|
1526  | 
this_entry.depth,  | 
|
1527  | 
entry,  | 
|
1528  | 
comment))  | 
|
1529  | 
out.extend(self.write(this_entry))  | 
|
1530  | 
else:  | 
|
1531  | 
out.append(self._write_line(  | 
|
1532  | 
indent_string,  | 
|
1533  | 
entry,  | 
|
1534  | 
this_entry,  | 
|
1535  | 
comment))  | 
|
1536  | 
        #
 | 
|
1537  | 
if not return_list:  | 
|
1538  | 
for line in self.final_comment:  | 
|
1539  | 
stripped_line = line.strip()  | 
|
1540  | 
if stripped_line and not stripped_line.startswith('#'):  | 
|
1541  | 
line = '# ' + line  | 
|
1542  | 
out.append(line)  | 
|
1543  | 
        #
 | 
|
1544  | 
if int_val != 'test':  | 
|
1545  | 
self.interpolation = int_val  | 
|
1546  | 
        #
 | 
|
1547  | 
if (return_list) or (self.filename is None):  | 
|
1548  | 
return out  | 
|
1549  | 
        #
 | 
|
1550  | 
if isinstance(self.filename, StringTypes):  | 
|
1551  | 
h = open(self.filename, 'w')  | 
|
1552  | 
h.write(self.BOM or '')  | 
|
1553  | 
h.write('\n'.join(out))  | 
|
1554  | 
h.close()  | 
|
1555  | 
else:  | 
|
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
 | 
|
1561  | 
||
1562  | 
def validate(self, validator, section=None):  | 
|
1563  | 
"""  | 
|
1564  | 
        Test the ConfigObj against a configspec.
 | 
|
1565  | 
        
 | 
|
1566  | 
        It uses the ``validator`` object from *validate.py*.
 | 
|
1567  | 
        
 | 
|
1568  | 
        To run ``validate`` on the current ConfigObj, call: ::
 | 
|
1569  | 
        
 | 
|
1570  | 
            test = config.validate(validator)
 | 
|
1571  | 
        
 | 
|
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).
 | 
|
1575  | 
        
 | 
|
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
 | 
|
1579  | 
        members fail).
 | 
|
1580  | 
        
 | 
|
1581  | 
        In addition, it converts the values from strings to their native
 | 
|
1582  | 
        types if their checks pass (and ``stringify`` is set).
 | 
|
1583  | 
        
 | 
|
1584  | 
        >>> try:
 | 
|
1585  | 
        ...     from validate import Validator
 | 
|
1586  | 
        ... except ImportError:
 | 
|
1587  | 
        ...     print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
 | 
|
1588  | 
        ... else:
 | 
|
1589  | 
        ...     config = '''
 | 
|
1590  | 
        ...     test1=40
 | 
|
1591  | 
        ...     test2=hello
 | 
|
1592  | 
        ...     test3=3
 | 
|
1593  | 
        ...     test4=5.0
 | 
|
1594  | 
        ...     [section]
 | 
|
1595  | 
        ...         test1=40
 | 
|
1596  | 
        ...         test2=hello
 | 
|
1597  | 
        ...         test3=3
 | 
|
1598  | 
        ...         test4=5.0
 | 
|
1599  | 
        ...         [[sub section]]
 | 
|
1600  | 
        ...             test1=40
 | 
|
1601  | 
        ...             test2=hello
 | 
|
1602  | 
        ...             test3=3
 | 
|
1603  | 
        ...             test4=5.0
 | 
|
1604  | 
        ... '''.split('\\n')
 | 
|
1605  | 
        ...     configspec = '''
 | 
|
1606  | 
        ...     test1='integer(30,50)'
 | 
|
1607  | 
        ...     test2='string'
 | 
|
1608  | 
        ...     test3='integer'
 | 
|
1609  | 
        ...     test4='float(6.0)'
 | 
|
1610  | 
        ...     [section ]
 | 
|
1611  | 
        ...         test1='integer(30,50)'
 | 
|
1612  | 
        ...         test2='string'
 | 
|
1613  | 
        ...         test3='integer'
 | 
|
1614  | 
        ...         test4='float(6.0)'
 | 
|
1615  | 
        ...         [[sub section]]
 | 
|
1616  | 
        ...             test1='integer(30,50)'
 | 
|
1617  | 
        ...             test2='string'
 | 
|
1618  | 
        ...             test3='integer'
 | 
|
1619  | 
        ...             test4='float(6.0)'
 | 
|
1620  | 
        ...     '''.split('\\n')
 | 
|
1621  | 
        ...     val = Validator()
 | 
|
1622  | 
        ...     c1 = ConfigObj(config, configspec=configspec)
 | 
|
1623  | 
        ...     test = c1.validate(val)
 | 
|
1624  | 
        ...     test == {
 | 
|
1625  | 
        ...         'test1': True,
 | 
|
1626  | 
        ...         'test2': True,
 | 
|
1627  | 
        ...         'test3': True,
 | 
|
1628  | 
        ...         'test4': False,
 | 
|
1629  | 
        ...         'section': {
 | 
|
1630  | 
        ...             'test1': True,
 | 
|
1631  | 
        ...             'test2': True,
 | 
|
1632  | 
        ...             'test3': True,
 | 
|
1633  | 
        ...             'test4': False,
 | 
|
1634  | 
        ...             'sub section': {
 | 
|
1635  | 
        ...                 'test1': True,
 | 
|
1636  | 
        ...                 'test2': True,
 | 
|
1637  | 
        ...                 'test3': True,
 | 
|
1638  | 
        ...                 'test4': False,
 | 
|
1639  | 
        ...             },
 | 
|
1640  | 
        ...         },
 | 
|
1641  | 
        ...     }
 | 
|
1642  | 
        1
 | 
|
1643  | 
        >>> val.check(c1.configspec['test4'], c1['test4'])
 | 
|
1644  | 
        Traceback (most recent call last):
 | 
|
1645  | 
        VdtValueTooSmallError: the value "5.0" is too small.
 | 
|
1646  | 
        
 | 
|
1647  | 
        >>> val_test_config = '''
 | 
|
1648  | 
        ...     key = 0
 | 
|
1649  | 
        ...     key2 = 1.1
 | 
|
1650  | 
        ...     [section]
 | 
|
1651  | 
        ...     key = some text
 | 
|
1652  | 
        ...     key2 = 1.1, 3.0, 17, 6.8
 | 
|
1653  | 
        ...         [[sub-section]]
 | 
|
1654  | 
        ...         key = option1
 | 
|
1655  | 
        ...         key2 = True'''.split('\\n')
 | 
|
1656  | 
        >>> val_test_configspec = '''
 | 
|
1657  | 
        ...     key = integer
 | 
|
1658  | 
        ...     key2 = float
 | 
|
1659  | 
        ...     [section]
 | 
|
1660  | 
        ...     key = string
 | 
|
1661  | 
        ...     key2 = float_list(4)
 | 
|
1662  | 
        ...        [[sub-section]]
 | 
|
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)
 | 
|
1667  | 
        1
 | 
|
1668  | 
        >>> val_test['key'] = 'text not a digit'
 | 
|
1669  | 
        >>> val_res = val_test.validate(val)
 | 
|
1670  | 
        >>> val_res == {'key2': True, 'section': True, 'key': False}
 | 
|
1671  | 
        1
 | 
|
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)'
 | 
|
1677  | 
        ...     [section ]
 | 
|
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)'
 | 
|
1682  | 
        ...         [[sub section]]
 | 
|
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)
 | 
|
1689  | 
        >>> default_test
 | 
|
1690  | 
        {'test1': '30', 'section': {'sub section': {}}}
 | 
|
1691  | 
        >>> default_test.validate(val)
 | 
|
1692  | 
        1
 | 
|
1693  | 
        >>> default_test == {
 | 
|
1694  | 
        ...     'test1': 30,
 | 
|
1695  | 
        ...     'test2': 'hello',
 | 
|
1696  | 
        ...     'test3': 3,
 | 
|
1697  | 
        ...     'test4': 6.0,
 | 
|
1698  | 
        ...     'section': {
 | 
|
1699  | 
        ...         'test1': 40,
 | 
|
1700  | 
        ...         'test2': 'hello',
 | 
|
1701  | 
        ...         'test3': 3,
 | 
|
1702  | 
        ...         'test4': 6.0,
 | 
|
1703  | 
        ...         'sub section': {
 | 
|
1704  | 
        ...             'test1': 40,
 | 
|
1705  | 
        ...             'test3': 3,
 | 
|
1706  | 
        ...             'test2': 'hello',
 | 
|
1707  | 
        ...             'test4': 6.0,
 | 
|
1708  | 
        ...         },
 | 
|
1709  | 
        ...     },
 | 
|
1710  | 
        ... }
 | 
|
1711  | 
        1
 | 
|
1712  | 
        
 | 
|
1713  | 
        Now testing with repeated sections : BIG TEST
 | 
|
1714  | 
        
 | 
|
1715  | 
        >>> repeated_1 = '''
 | 
|
1716  | 
        ... [dogs]
 | 
|
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)
 | 
|
1724  | 
        ... [cats]
 | 
|
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 = '''
 | 
|
1734  | 
        ... [dogs]
 | 
|
1735  | 
        ... 
 | 
|
1736  | 
        ...     # blank dogs with puppies
 | 
|
1737  | 
        ...     # should be filled in by the configspec
 | 
|
1738  | 
        ...     [[dog1]]
 | 
|
1739  | 
        ...         [[[puppy1]]]
 | 
|
1740  | 
        ...         [[[puppy2]]]
 | 
|
1741  | 
        ...         [[[puppy3]]]
 | 
|
1742  | 
        ...     [[dog2]]
 | 
|
1743  | 
        ...         [[[puppy1]]]
 | 
|
1744  | 
        ...         [[[puppy2]]]
 | 
|
1745  | 
        ...         [[[puppy3]]]
 | 
|
1746  | 
        ...     [[dog3]]
 | 
|
1747  | 
        ...         [[[puppy1]]]
 | 
|
1748  | 
        ...         [[[puppy2]]]
 | 
|
1749  | 
        ...         [[[puppy3]]]
 | 
|
1750  | 
        ... [cats]
 | 
|
1751  | 
        ... 
 | 
|
1752  | 
        ...     # blank cats with kittens
 | 
|
1753  | 
        ...     # should be filled in by the configspec
 | 
|
1754  | 
        ...     [[cat1]]
 | 
|
1755  | 
        ...         [[[kitten1]]]
 | 
|
1756  | 
        ...         [[[kitten2]]]
 | 
|
1757  | 
        ...         [[[kitten3]]]
 | 
|
1758  | 
        ...     [[cat2]]
 | 
|
1759  | 
        ...         [[[kitten1]]]
 | 
|
1760  | 
        ...         [[[kitten2]]]
 | 
|
1761  | 
        ...         [[[kitten3]]]
 | 
|
1762  | 
        ...     [[cat3]]
 | 
|
1763  | 
        ...         [[[kitten1]]]
 | 
|
1764  | 
        ...         [[[kitten2]]]
 | 
|
1765  | 
        ...         [[[kitten3]]]
 | 
|
1766  | 
        ... '''.split('\\n')
 | 
|
1767  | 
        >>> repeated_3 = '''
 | 
|
1768  | 
        ... [dogs]
 | 
|
1769  | 
        ... 
 | 
|
1770  | 
        ...     [[dog1]]
 | 
|
1771  | 
        ...     [[dog2]]
 | 
|
1772  | 
        ...     [[dog3]]
 | 
|
1773  | 
        ... [cats]
 | 
|
1774  | 
        ... 
 | 
|
1775  | 
        ...     [[cat1]]
 | 
|
1776  | 
        ...     [[cat2]]
 | 
|
1777  | 
        ...     [[cat3]]
 | 
|
1778  | 
        ... '''.split('\\n')
 | 
|
1779  | 
        >>> repeated_4 = '''
 | 
|
1780  | 
        ... [__many__]
 | 
|
1781  | 
        ... 
 | 
|
1782  | 
        ...     name = string(default=Michael)
 | 
|
1783  | 
        ...     age = float(default=0.0)
 | 
|
1784  | 
        ...     sex = option(m, f, default=m)
 | 
|
1785  | 
        ... '''.split('\\n')
 | 
|
1786  | 
        >>> repeated_5 = '''
 | 
|
1787  | 
        ... [cats]
 | 
|
1788  | 
        ... [[__many__]]
 | 
|
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)
 | 
|
1795  | 
        ...         [[[[coat]]]]
 | 
|
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)
 | 
|
1803  | 
        1
 | 
|
1804  | 
        >>> repeater == {
 | 
|
1805  | 
        ...     'dogs': {
 | 
|
1806  | 
        ...         'dog1': {
 | 
|
1807  | 
        ...             'fleas': True,
 | 
|
1808  | 
        ...             'tail': 'long',
 | 
|
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},
 | 
|
1813  | 
        ...         },
 | 
|
1814  | 
        ...         'dog2': {
 | 
|
1815  | 
        ...             'fleas': True,
 | 
|
1816  | 
        ...             'tail': 'long',
 | 
|
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},
 | 
|
1821  | 
        ...         },
 | 
|
1822  | 
        ...         'dog3': {
 | 
|
1823  | 
        ...             'fleas': True,
 | 
|
1824  | 
        ...             'tail': 'long',
 | 
|
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},
 | 
|
1829  | 
        ...         },
 | 
|
1830  | 
        ...     },
 | 
|
1831  | 
        ...     'cats': {
 | 
|
1832  | 
        ...         'cat1': {
 | 
|
1833  | 
        ...             'fleas': True,
 | 
|
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},
 | 
|
1839  | 
        ...         },
 | 
|
1840  | 
        ...         'cat2': {
 | 
|
1841  | 
        ...             'fleas': True,
 | 
|
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},
 | 
|
1847  | 
        ...         },
 | 
|
1848  | 
        ...         'cat3': {
 | 
|
1849  | 
        ...             'fleas': True,
 | 
|
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},
 | 
|
1855  | 
        ...         },
 | 
|
1856  | 
        ...     },
 | 
|
1857  | 
        ... }
 | 
|
1858  | 
        1
 | 
|
1859  | 
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
 | 
|
1860  | 
        >>> repeater.validate(val)
 | 
|
1861  | 
        1
 | 
|
1862  | 
        >>> repeater == {
 | 
|
1863  | 
        ...     'cats': {
 | 
|
1864  | 
        ...         'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 | 
|
1865  | 
        ...         'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 | 
|
1866  | 
        ...         'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
 | 
|
1867  | 
        ...     },
 | 
|
1868  | 
        ...     'dogs': {
 | 
|
1869  | 
        ...         'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 | 
|
1870  | 
        ...         'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 | 
|
1871  | 
        ...         'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
 | 
|
1872  | 
        ...     },
 | 
|
1873  | 
        ... }
 | 
|
1874  | 
        1
 | 
|
1875  | 
        >>> repeater = ConfigObj(configspec=repeated_4)
 | 
|
1876  | 
        >>> repeater['Michael'] = {}
 | 
|
1877  | 
        >>> repeater.validate(val)
 | 
|
1878  | 
        1
 | 
|
1879  | 
        >>> repeater == {
 | 
|
1880  | 
        ...     'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
 | 
|
1881  | 
        ... }
 | 
|
1882  | 
        1
 | 
|
1883  | 
        >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
 | 
|
1884  | 
        >>> repeater == {
 | 
|
1885  | 
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 | 
|
1886  | 
        ...     'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
 | 
|
1887  | 
        ... }
 | 
|
1888  | 
        1
 | 
|
1889  | 
        >>> repeater.validate(val)
 | 
|
1890  | 
        1
 | 
|
1891  | 
        >>> repeater == {
 | 
|
1892  | 
        ...     'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
 | 
|
1893  | 
        ...     'cats': {
 | 
|
1894  | 
        ...         'cat1': {
 | 
|
1895  | 
        ...             'fleas': True,
 | 
|
1896  | 
        ...             'tail': 'short',
 | 
|
1897  | 
        ...             'name': 'pussy',
 | 
|
1898  | 
        ...             'description': {
 | 
|
1899  | 
        ...                 'weight': 6.0,
 | 
|
1900  | 
        ...                 'height': 3.2999999999999998,
 | 
|
1901  | 
        ...                 'coat': {'fur': 'black', 'condition': 5},
 | 
|
1902  | 
        ...             },
 | 
|
1903  | 
        ...         },
 | 
|
1904  | 
        ...         'cat2': {
 | 
|
1905  | 
        ...             'fleas': True,
 | 
|
1906  | 
        ...             'tail': 'short',
 | 
|
1907  | 
        ...             'name': 'pussy',
 | 
|
1908  | 
        ...             'description': {
 | 
|
1909  | 
        ...                 'weight': 6.0,
 | 
|
1910  | 
        ...                 'height': 3.2999999999999998,
 | 
|
1911  | 
        ...                 'coat': {'fur': 'black', 'condition': 5},
 | 
|
1912  | 
        ...             },
 | 
|
1913  | 
        ...         },
 | 
|
1914  | 
        ...         'cat3': {
 | 
|
1915  | 
        ...             'fleas': True,
 | 
|
1916  | 
        ...             'tail': 'short',
 | 
|
1917  | 
        ...             'name': 'pussy',
 | 
|
1918  | 
        ...             'description': {
 | 
|
1919  | 
        ...                 'weight': 6.0,
 | 
|
1920  | 
        ...                 'height': 3.2999999999999998,
 | 
|
1921  | 
        ...                 'coat': {'fur': 'black', 'condition': 5},
 | 
|
1922  | 
        ...             },
 | 
|
1923  | 
        ...         },
 | 
|
1924  | 
        ...     },
 | 
|
1925  | 
        ... }
 | 
|
1926  | 
        1
 | 
|
1927  | 
        
 | 
|
1928  | 
        Test that interpolation is preserved for validated string values.
 | 
|
1929  | 
        >>> t = ConfigObj()
 | 
|
1930  | 
        >>> t['DEFAULT'] = {}
 | 
|
1931  | 
        >>> t['DEFAULT']['test'] = 'a'
 | 
|
1932  | 
        >>> t['test'] = '%(test)s'
 | 
|
1933  | 
        >>> t['test']
 | 
|
1934  | 
        'a'
 | 
|
1935  | 
        >>> v = Validator()
 | 
|
1936  | 
        >>> t.configspec = {'test': 'string'}
 | 
|
1937  | 
        >>> t.validate(v)
 | 
|
1938  | 
        1
 | 
|
1939  | 
        >>> t.interpolation = False
 | 
|
1940  | 
        >>> t
 | 
|
1941  | 
        {'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
 | 
|
1942  | 
        
 | 
|
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
 | 
|
1945  | 
        tests)
 | 
|
1946  | 
        """
 | 
|
1947  | 
if section is None:  | 
|
1948  | 
if self.configspec is None:  | 
|
1949  | 
raise ValueError, 'No configspec supplied.'  | 
|
1950  | 
section = self  | 
|
1951  | 
        #
 | 
|
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)  | 
|
1959  | 
        #
 | 
|
1960  | 
out = {}  | 
|
1961  | 
ret_true = True  | 
|
1962  | 
ret_false = True  | 
|
1963  | 
for entry in spec_section:  | 
|
1964  | 
if entry == '__many__':  | 
|
1965  | 
                continue
 | 
|
1966  | 
if (not entry in section.scalars) or (entry in section.defaults):  | 
|
1967  | 
                # missing entries
 | 
|
1968  | 
                # or entries from defaults
 | 
|
1969  | 
missing = True  | 
|
1970  | 
val = None  | 
|
1971  | 
else:  | 
|
1972  | 
missing = False  | 
|
1973  | 
val = section[entry]  | 
|
1974  | 
try:  | 
|
1975  | 
check = validator.check(spec_section[entry],  | 
|
1976  | 
val,  | 
|
1977  | 
missing=missing)  | 
|
1978  | 
except validator.baseErrorClass:  | 
|
1979  | 
out[entry] = False  | 
|
1980  | 
ret_true = False  | 
|
1981  | 
            # MIKE: we want to raise all other exceptions, not just print ?
 | 
|
1982  | 
##            except Exception, err:
 | 
|
1983  | 
##                print err
 | 
|
1984  | 
else:  | 
|
1985  | 
ret_false = False  | 
|
1986  | 
out[entry] = True  | 
|
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)):  | 
|
1992  | 
                            # preserve lists
 | 
|
1993  | 
check = [str(item) for item in check]  | 
|
1994  | 
elif missing and check is None:  | 
|
1995  | 
                            # convert the None from a default to a ''
 | 
|
1996  | 
check = ''  | 
|
1997  | 
else:  | 
|
1998  | 
check = str(check)  | 
|
1999  | 
if (check != val) or missing:  | 
|
2000  | 
section[entry] = check  | 
|
2001  | 
if missing and entry not in section.defaults:  | 
|
2002  | 
section.defaults.append(entry)  | 
|
2003  | 
        #
 | 
|
2004  | 
for entry in section.sections:  | 
|
2005  | 
check = self.validate(validator, section[entry])  | 
|
2006  | 
out[entry] = check  | 
|
2007  | 
if check == False:  | 
|
2008  | 
ret_true = False  | 
|
2009  | 
elif check == True:  | 
|
2010  | 
ret_false = False  | 
|
2011  | 
else:  | 
|
2012  | 
ret_true = False  | 
|
2013  | 
ret_false = False  | 
|
2014  | 
        #
 | 
|
2015  | 
if ret_true:  | 
|
2016  | 
return True  | 
|
2017  | 
elif ret_false:  | 
|
2018  | 
return False  | 
|
2019  | 
else:  | 
|
2020  | 
return out  | 
|
2021  | 
||
2022  | 
class SimpleVal(object):  | 
|
2023  | 
"""  | 
|
2024  | 
    A simple validator.
 | 
|
2025  | 
    Can be used to check that all members expected are present.
 | 
|
2026  | 
    
 | 
|
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``)
 | 
|
2032  | 
    
 | 
|
2033  | 
    >>> val = SimpleVal()
 | 
|
2034  | 
    >>> config = '''
 | 
|
2035  | 
    ... test1=40
 | 
|
2036  | 
    ... test2=hello
 | 
|
2037  | 
    ... test3=3
 | 
|
2038  | 
    ... test4=5.0
 | 
|
2039  | 
    ... [section]
 | 
|
2040  | 
    ... test1=40
 | 
|
2041  | 
    ... test2=hello
 | 
|
2042  | 
    ... test3=3
 | 
|
2043  | 
    ... test4=5.0
 | 
|
2044  | 
    ...     [[sub section]]
 | 
|
2045  | 
    ...     test1=40
 | 
|
2046  | 
    ...     test2=hello
 | 
|
2047  | 
    ...     test3=3
 | 
|
2048  | 
    ...     test4=5.0
 | 
|
2049  | 
    ... '''.split('\\n')
 | 
|
2050  | 
    >>> configspec = '''
 | 
|
2051  | 
    ... test1=''
 | 
|
2052  | 
    ... test2=''
 | 
|
2053  | 
    ... test3=''
 | 
|
2054  | 
    ... test4=''
 | 
|
2055  | 
    ... [section]
 | 
|
2056  | 
    ... test1=''
 | 
|
2057  | 
    ... test2=''
 | 
|
2058  | 
    ... test3=''
 | 
|
2059  | 
    ... test4=''
 | 
|
2060  | 
    ...     [[sub section]]
 | 
|
2061  | 
    ...     test1=''
 | 
|
2062  | 
    ...     test2=''
 | 
|
2063  | 
    ...     test3=''
 | 
|
2064  | 
    ...     test4=''
 | 
|
2065  | 
    ... '''.split('\\n')
 | 
|
2066  | 
    >>> o = ConfigObj(config, configspec=configspec)
 | 
|
2067  | 
    >>> o.validate(val)
 | 
|
2068  | 
    1
 | 
|
2069  | 
    >>> o = ConfigObj(configspec=configspec)
 | 
|
2070  | 
    >>> o.validate(val)
 | 
|
2071  | 
    0
 | 
|
2072  | 
    """
 | 
|
2073  | 
||
2074  | 
def __init__(self):  | 
|
2075  | 
self.baseErrorClass = ConfigObjError  | 
|
2076  | 
||
2077  | 
def check(self, check, member, missing=False):  | 
|
2078  | 
"""A dummy check method, always returns the value unchanged."""  | 
|
2079  | 
if missing:  | 
|
2080  | 
raise self.baseErrorClass  | 
|
2081  | 
return member  | 
|
2082  | 
||
2083  | 
# FIXME: test error code for badly built multiline values
 | 
|
2084  | 
# FIXME: test handling of StringIO
 | 
|
2085  | 
# FIXME: test interpolation with writing
 | 
|
2086  | 
||
2087  | 
def _doctest():  | 
|
2088  | 
"""  | 
|
2089  | 
    Dummy function to hold some of the doctests.
 | 
|
2090  | 
    
 | 
|
2091  | 
    >>> a.depth
 | 
|
2092  | 
    0
 | 
|
2093  | 
    >>> a == {
 | 
|
2094  | 
    ...     'key2': 'val',
 | 
|
2095  | 
    ...     'key1': 'val',
 | 
|
2096  | 
    ...     'lev1c': {
 | 
|
2097  | 
    ...         'lev2c': {
 | 
|
2098  | 
    ...             'lev3c': {
 | 
|
2099  | 
    ...                 'key1': 'val',
 | 
|
2100  | 
    ...             },
 | 
|
2101  | 
    ...         },
 | 
|
2102  | 
    ...     },
 | 
|
2103  | 
    ...     'lev1b': {
 | 
|
2104  | 
    ...         'key2': 'val',
 | 
|
2105  | 
    ...         'key1': 'val',
 | 
|
2106  | 
    ...         'lev2ba': {
 | 
|
2107  | 
    ...             'key1': 'val',
 | 
|
2108  | 
    ...         },
 | 
|
2109  | 
    ...         'lev2bb': {
 | 
|
2110  | 
    ...             'key1': 'val',
 | 
|
2111  | 
    ...         },
 | 
|
2112  | 
    ...     },
 | 
|
2113  | 
    ...     'lev1a': {
 | 
|
2114  | 
    ...         'key2': 'val',
 | 
|
2115  | 
    ...         'key1': 'val',
 | 
|
2116  | 
    ...     },
 | 
|
2117  | 
    ... }
 | 
|
2118  | 
    1
 | 
|
2119  | 
    >>> b.depth
 | 
|
2120  | 
    0
 | 
|
2121  | 
    >>> b == {
 | 
|
2122  | 
    ...     'key3': 'val3',
 | 
|
2123  | 
    ...     'key2': 'val2',
 | 
|
2124  | 
    ...     'key1': 'val1',
 | 
|
2125  | 
    ...     'section 1': {
 | 
|
2126  | 
    ...         'keys11': 'val1',
 | 
|
2127  | 
    ...         'keys13': 'val3',
 | 
|
2128  | 
    ...         'keys12': 'val2',
 | 
|
2129  | 
    ...     },
 | 
|
2130  | 
    ...     'section 2': {
 | 
|
2131  | 
    ...         'section 2 sub 1': {
 | 
|
2132  | 
    ...             'fish': '3',
 | 
|
2133  | 
    ...     },
 | 
|
2134  | 
    ...     'keys21': 'val1',
 | 
|
2135  | 
    ...     'keys22': 'val2',
 | 
|
2136  | 
    ...     'keys23': 'val3',
 | 
|
2137  | 
    ...     },
 | 
|
2138  | 
    ... }
 | 
|
2139  | 
    1
 | 
|
2140  | 
    >>> t = '''
 | 
|
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'] = ''
 | 
|
2147  | 
    >>> del t2['a']
 | 
|
2148  | 
    >>> assert t2.write() == ['','b = b', '']
 | 
|
2149  | 
    """
 | 
|
2150  | 
||
2151  | 
if __name__ == '__main__':  | 
|
2152  | 
    # run the code tests in doctest format
 | 
|
2153  | 
    #
 | 
|
2154  | 
testconfig1 = """\  | 
|
2155  | 
    key1= val    # comment 1
 | 
|
2156  | 
    key2= val    # comment 2
 | 
|
2157  | 
    # comment 3
 | 
|
2158  | 
    [lev1a]     # comment 4
 | 
|
2159  | 
    key1= val    # comment 5
 | 
|
2160  | 
    key2= val    # comment 6
 | 
|
2161  | 
    # comment 7
 | 
|
2162  | 
    [lev1b]    # comment 8
 | 
|
2163  | 
    key1= val    # comment 9
 | 
|
2164  | 
    key2= val    # comment 10
 | 
|
2165  | 
    # comment 11
 | 
|
2166  | 
        [[lev2ba]]    # comment 12
 | 
|
2167  | 
        key1= val    # comment 13
 | 
|
2168  | 
        # comment 14
 | 
|
2169  | 
        [[lev2bb]]    # comment 15
 | 
|
2170  | 
        key1= val    # comment 16
 | 
|
2171  | 
    # comment 17
 | 
|
2172  | 
    [lev1c]    # comment 18
 | 
|
2173  | 
    # comment 19
 | 
|
2174  | 
        [[lev2c]]    # comment 20
 | 
|
2175  | 
        # comment 21
 | 
|
2176  | 
            [[[lev3c]]]    # comment 22
 | 
|
2177  | 
            key1 = val    # comment 23"""
 | 
|
2178  | 
    #
 | 
|
2179  | 
testconfig2 = """\  | 
|
2180  | 
                        key1 = 'val1'
 | 
|
2181  | 
                        key2 =   "val2"
 | 
|
2182  | 
                        key3 = val3
 | 
|
2183  | 
                        ["section 1"] # comment
 | 
|
2184  | 
                        keys11 = val1
 | 
|
2185  | 
                        keys12 = val2
 | 
|
2186  | 
                        keys13 = val3
 | 
|
2187  | 
                        [section 2]
 | 
|
2188  | 
                        keys21 = val1
 | 
|
2189  | 
                        keys22 = val2
 | 
|
2190  | 
                        keys23 = val3
 | 
|
2191  | 
                        
 | 
|
2192  | 
                            [['section 2 sub 1']]
 | 
|
2193  | 
                            fish = 3
 | 
|
2194  | 
    """
 | 
|
2195  | 
    #
 | 
|
2196  | 
testconfig6 = '''  | 
|
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 \'''  | 
|
2201  | 
        [ "multi section" ]
 | 
|
2202  | 
        name1 = """
 | 
|
2203  | 
        Well, this is a
 | 
|
2204  | 
        multiline value
 | 
|
2205  | 
        """
 | 
|
2206  | 
name2 = \'''  | 
|
2207  | 
        Well, this is a
 | 
|
2208  | 
        multiline value
 | 
|
2209  | 
\'''  | 
|
2210  | 
        name3 = """
 | 
|
2211  | 
        Well, this is a
 | 
|
2212  | 
        multiline value
 | 
|
2213  | 
        """     # a comment
 | 
|
2214  | 
name4 = \'''  | 
|
2215  | 
        Well, this is a
 | 
|
2216  | 
        multiline value
 | 
|
2217  | 
\''' # I guess this is a comment too  | 
|
2218  | 
    '''
 | 
|
2219  | 
    #
 | 
|
2220  | 
import doctest  | 
|
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)  | 
|
2226  | 
globs.update({  | 
|
2227  | 
'INTP_VER': INTP_VER,  | 
|
2228  | 
'a': a,  | 
|
2229  | 
'b': b,  | 
|
2230  | 
'i': i,  | 
|
2231  | 
    })
 | 
|
2232  | 
doctest.testmod(m, globs=globs)  | 
|
2233  | 
||
2234  | 
"""
 | 
|
2235  | 
    BUGS
 | 
|
2236  | 
    ====
 | 
|
2237  | 
    
 | 
|
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)
 | 
|
2241  | 
    
 | 
|
2242  | 
    TODO
 | 
|
2243  | 
    ====
 | 
|
2244  | 
    
 | 
|
2245  | 
    A method to optionally remove uniform indentation from multiline values.
 | 
|
2246  | 
    (do as an example of using ``walk`` - along with string-escape)
 | 
|
2247  | 
    
 | 
|
2248  | 
    INCOMPATIBLE CHANGES
 | 
|
2249  | 
    ====================
 | 
|
2250  | 
    
 | 
|
2251  | 
    (I have removed a lot of needless complications - this list is probably not
 | 
|
2252  | 
    conclusive, many option/attribute/method names have changed)
 | 
|
2253  | 
    
 | 
|
2254  | 
    Case sensitive
 | 
|
2255  | 
    
 | 
|
2256  | 
    The only valid divider is '='
 | 
|
2257  | 
    
 | 
|
2258  | 
    We've removed line continuations with '\'
 | 
|
2259  | 
    
 | 
|
2260  | 
    No recursive lists in values
 | 
|
2261  | 
    
 | 
|
2262  | 
    No empty section
 | 
|
2263  | 
    
 | 
|
2264  | 
    No distinction between flatfiles and non flatfiles
 | 
|
2265  | 
    
 | 
|
2266  | 
    Change in list syntax - use commas to indicate list, not parentheses
 | 
|
2267  | 
    (square brackets and parentheses are no longer recognised as lists)
 | 
|
2268  | 
    
 | 
|
2269  | 
    ';' is no longer valid for comments and no multiline comments
 | 
|
2270  | 
    
 | 
|
2271  | 
    No attribute access
 | 
|
2272  | 
    
 | 
|
2273  | 
    We don't allow empty values - have to use '' or ""
 | 
|
2274  | 
    
 | 
|
2275  | 
    In ConfigObj 3 - setting a non-flatfile member to ``None`` would
 | 
|
2276  | 
    initialise it as an empty section.
 | 
|
2277  | 
    
 | 
|
2278  | 
    The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
 | 
|
2279  | 
    replaced by triple quote, multiple line values.
 | 
|
2280  | 
    
 | 
|
2281  | 
    The ``newline``, ``force_return``, and ``default`` options have gone
 | 
|
2282  | 
    
 | 
|
2283  | 
    The ``encoding`` and ``backup_encoding`` methods have gone - replaced
 | 
|
2284  | 
    with the ``encode`` and ``decode`` methods.
 | 
|
2285  | 
    
 | 
|
2286  | 
    ``fileerror`` and ``createempty`` options have become ``file_error`` and
 | 
|
2287  | 
    ``create_empty``
 | 
|
2288  | 
    
 | 
|
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.
 | 
|
2292  | 
    
 | 
|
2293  | 
    Exceeding the maximum depth of recursion in string interpolation now
 | 
|
2294  | 
    raises an error ``InterpolationDepthError``.
 | 
|
2295  | 
    
 | 
|
2296  | 
    Specifying a value for interpolation which doesn't exist now raises an
 | 
|
2297  | 
    error ``MissingInterpolationOption`` (instead of merely being ignored).
 | 
|
2298  | 
    
 | 
|
2299  | 
    The ``writein`` method has been removed.
 | 
|
2300  | 
    
 | 
|
2301  | 
    The comments attribute is now a list (``inline_comments`` equates to the
 | 
|
2302  | 
    old comments attribute)
 | 
|
2303  | 
    
 | 
|
2304  | 
    ISSUES
 | 
|
2305  | 
    ======
 | 
|
2306  | 
    
 | 
|
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.
 | 
|
2309  | 
    
 | 
|
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.
 | 
|
2313  | 
    
 | 
|
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 ?
 | 
|
2317  | 
    
 | 
|
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.
 | 
|
2324  | 
    
 | 
|
2325  | 
    Does it matter that we don't support the ':' divider, which is supported
 | 
|
2326  | 
    by ``ConfigParser`` ?
 | 
|
2327  | 
    
 | 
|
2328  | 
    Following error with "list_values=False" : ::
 | 
|
2329  | 
    
 | 
|
2330  | 
        >>> a = ["a='hello', 'goodbye'"]
 | 
|
2331  | 
        >>>
 | 
|
2332  | 
        >>> c(a, list_values=False)
 | 
|
2333  | 
        {'a': "hello', 'goodbye"}
 | 
|
2334  | 
    
 | 
|
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.)
 | 
|
2341  | 
    
 | 
|
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
 | 
|
2346  | 
    will be.
 | 
|
2347  | 
    
 | 
|
2348  | 
    List Value Syntax
 | 
|
2349  | 
    =================
 | 
|
2350  | 
    
 | 
|
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.
 | 
|
2353  | 
    
 | 
|
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. : ::
 | 
|
2357  | 
    
 | 
|
2358  | 
        keyword = value1, 'value 2', "value 3"
 | 
|
2359  | 
    
 | 
|
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. : ::
 | 
|
2362  | 
    
 | 
|
2363  | 
        keyword = "single value",
 | 
|
2364  | 
    
 | 
|
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. : ::
 | 
|
2367  | 
    
 | 
|
2368  | 
        keyword = ,     # an empty list
 | 
|
2369  | 
    
 | 
|
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.
 | 
|
2374  | 
      
 | 
|
2375  | 
    CHANGELOG
 | 
|
2376  | 
    =========
 | 
|
2377  | 
    
 | 
|
2378  | 
    2005/10/09
 | 
|
2379  | 
    ----------
 | 
|
2380  | 
    
 | 
|
2381  | 
    Fixed typo in ``write`` method. (Testing for the wrong value when resetting
 | 
|
2382  | 
    ``interpolation``).
 | 
|
2383  | 
    
 | 
|
2384  | 
    2005/09/16
 | 
|
2385  | 
    ----------
 | 
|
2386  | 
    
 | 
|
2387  | 
    Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
 | 
|
2388  | 
    a reference to the new section.
 | 
|
2389  | 
    
 | 
|
2390  | 
    2005/09/09
 | 
|
2391  | 
    ----------
 | 
|
2392  | 
    
 | 
|
2393  | 
    Removed ``PositionError``.
 | 
|
2394  | 
    
 | 
|
2395  | 
    Allowed quotes around keys as documented.
 | 
|
2396  | 
    
 | 
|
2397  | 
    Fixed bug with commas in comments. (matched as a list value)
 | 
|
2398  | 
    
 | 
|
2399  | 
    Beta 5
 | 
|
2400  | 
    
 | 
|
2401  | 
    2005/09/07
 | 
|
2402  | 
    ----------
 | 
|
2403  | 
    
 | 
|
2404  | 
    Fixed bug in initialising ConfigObj from a ConfigObj.
 | 
|
2405  | 
    
 | 
|
2406  | 
    Changed the mailing list address.
 | 
|
2407  | 
    
 | 
|
2408  | 
    Beta 4
 | 
|
2409  | 
    
 | 
|
2410  | 
    2005/09/03
 | 
|
2411  | 
    ----------
 | 
|
2412  | 
    
 | 
|
2413  | 
    Fixed bug in ``Section__delitem__`` oops.
 | 
|
2414  | 
    
 | 
|
2415  | 
    2005/08/28
 | 
|
2416  | 
    ----------
 | 
|
2417  | 
    
 | 
|
2418  | 
    Interpolation is switched off before writing out files.
 | 
|
2419  | 
    
 | 
|
2420  | 
    Fixed bug in handling ``StringIO`` instances. (Thanks to report from
 | 
|
2421  | 
    "Gustavo Niemeyer" <gustavo@niemeyer.net>)
 | 
|
2422  | 
    
 | 
|
2423  | 
    Moved the doctests from the ``__init__`` method to a separate function.
 | 
|
2424  | 
    (For the sake of IDE calltips).
 | 
|
2425  | 
    
 | 
|
2426  | 
    Beta 3
 | 
|
2427  | 
    
 | 
|
2428  | 
    2005/08/26
 | 
|
2429  | 
    ----------
 | 
|
2430  | 
    
 | 
|
2431  | 
    String values unchanged by validation *aren't* reset. This preserves
 | 
|
2432  | 
    interpolation in string values.
 | 
|
2433  | 
    
 | 
|
2434  | 
    2005/08/18
 | 
|
2435  | 
    ----------
 | 
|
2436  | 
    
 | 
|
2437  | 
    None from a default is turned to '' if stringify is off - because setting 
 | 
|
2438  | 
    a value to None raises an error.
 | 
|
2439  | 
    
 | 
|
2440  | 
    Version 4.0.0-beta2
 | 
|
2441  | 
    
 | 
|
2442  | 
    2005/08/16
 | 
|
2443  | 
    ----------
 | 
|
2444  | 
    
 | 
|
2445  | 
    By Nicola Larosa
 | 
|
2446  | 
    
 | 
|
2447  | 
    Actually added the RepeatSectionError class ;-)
 | 
|
2448  | 
    
 | 
|
2449  | 
    2005/08/15
 | 
|
2450  | 
    ----------
 | 
|
2451  | 
    
 | 
|
2452  | 
    If ``stringify`` is off - list values are preserved by the ``validate``
 | 
|
2453  | 
    method. (Bugfix)
 | 
|
2454  | 
    
 | 
|
2455  | 
    2005/08/14
 | 
|
2456  | 
    ----------
 | 
|
2457  | 
    
 | 
|
2458  | 
    By Michael Foord
 | 
|
2459  | 
    
 | 
|
2460  | 
    Fixed ``simpleVal``.
 | 
|
2461  | 
    
 | 
|
2462  | 
    Added ``RepeatSectionError`` error if you have additional sections in a
 | 
|
2463  | 
    section with a ``__many__`` (repeated) section.
 | 
|
2464  | 
    
 | 
|
2465  | 
    By Nicola Larosa
 | 
|
2466  | 
    
 | 
|
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
 | 
|
2470  | 
    
 | 
|
2471  | 
    Reshaped the ConfigObj._multiline method to better reflect its semantics
 | 
|
2472  | 
    
 | 
|
2473  | 
    Changed the "default_test" test in ConfigObj.validate to check the fix for
 | 
|
2474  | 
    the bug in validate.Validator.check
 | 
|
2475  | 
    
 | 
|
2476  | 
    2005/08/13
 | 
|
2477  | 
    ----------
 | 
|
2478  | 
    
 | 
|
2479  | 
    By Nicola Larosa
 | 
|
2480  | 
    
 | 
|
2481  | 
    Updated comments at top
 | 
|
2482  | 
    
 | 
|
2483  | 
    2005/08/11
 | 
|
2484  | 
    ----------
 | 
|
2485  | 
    
 | 
|
2486  | 
    By Michael Foord
 | 
|
2487  | 
    
 | 
|
2488  | 
    Implemented repeated sections.
 | 
|
2489  | 
    
 | 
|
2490  | 
    By Nicola Larosa
 | 
|
2491  | 
    
 | 
|
2492  | 
    Added test for interpreter version: raises RuntimeError if earlier than
 | 
|
2493  | 
    2.2
 | 
|
2494  | 
    
 | 
|
2495  | 
    2005/08/10
 | 
|
2496  | 
    ----------
 | 
|
2497  | 
   
 | 
|
2498  | 
    By Michael Foord
 | 
|
2499  | 
     
 | 
|
2500  | 
    Implemented default values in configspecs.
 | 
|
2501  | 
    
 | 
|
2502  | 
    By Nicola Larosa
 | 
|
2503  | 
    
 | 
|
2504  | 
    Fixed naked except: clause in validate that was silencing the fact
 | 
|
2505  | 
    that Python2.2 does not have dict.pop
 | 
|
2506  | 
    
 | 
|
2507  | 
    2005/08/08
 | 
|
2508  | 
    ----------
 | 
|
2509  | 
    
 | 
|
2510  | 
    By Michael Foord
 | 
|
2511  | 
    
 | 
|
2512  | 
    Bug fix causing error if file didn't exist.
 | 
|
2513  | 
    
 | 
|
2514  | 
    2005/08/07
 | 
|
2515  | 
    ----------
 | 
|
2516  | 
    
 | 
|
2517  | 
    By Nicola Larosa
 | 
|
2518  | 
    
 | 
|
2519  | 
    Adjusted doctests for Python 2.2.3 compatibility
 | 
|
2520  | 
    
 | 
|
2521  | 
    2005/08/04
 | 
|
2522  | 
    ----------
 | 
|
2523  | 
    
 | 
|
2524  | 
    By Michael Foord
 | 
|
2525  | 
    
 | 
|
2526  | 
    Added the inline_comments attribute
 | 
|
2527  | 
    
 | 
|
2528  | 
    We now preserve and rewrite all comments in the config file
 | 
|
2529  | 
    
 | 
|
2530  | 
    configspec is now a section attribute
 | 
|
2531  | 
    
 | 
|
2532  | 
    The validate method changes values in place
 | 
|
2533  | 
    
 | 
|
2534  | 
    Added InterpolationError
 | 
|
2535  | 
    
 | 
|
2536  | 
    The errors now have line number, line, and message attributes. This
 | 
|
2537  | 
    simplifies error handling
 | 
|
2538  | 
    
 | 
|
2539  | 
    Added __docformat__
 | 
|
2540  | 
    
 | 
|
2541  | 
    2005/08/03
 | 
|
2542  | 
    ----------
 | 
|
2543  | 
    
 | 
|
2544  | 
    By Michael Foord
 | 
|
2545  | 
    
 | 
|
2546  | 
    Fixed bug in Section.pop (now doesn't raise KeyError if a default value
 | 
|
2547  | 
    is specified)
 | 
|
2548  | 
    
 | 
|
2549  | 
    Replaced ``basestring`` with ``types.StringTypes``
 | 
|
2550  | 
    
 | 
|
2551  | 
    Removed the ``writein`` method
 | 
|
2552  | 
    
 | 
|
2553  | 
    Added __version__
 | 
|
2554  | 
    
 | 
|
2555  | 
    2005/07/29
 | 
|
2556  | 
    ----------
 | 
|
2557  | 
    
 | 
|
2558  | 
    By Nicola Larosa
 | 
|
2559  | 
    
 | 
|
2560  | 
    Indentation in config file is not significant anymore, subsections are
 | 
|
2561  | 
    designated by repeating square brackets
 | 
|
2562  | 
    
 | 
|
2563  | 
    Adapted all tests and docs to the new format
 | 
|
2564  | 
    
 | 
|
2565  | 
    2005/07/28
 | 
|
2566  | 
    ----------
 | 
|
2567  | 
    
 | 
|
2568  | 
    By Nicola Larosa
 | 
|
2569  | 
    
 | 
|
2570  | 
    Added more tests
 | 
|
2571  | 
    
 | 
|
2572  | 
    2005/07/23
 | 
|
2573  | 
    ----------
 | 
|
2574  | 
    
 | 
|
2575  | 
    By Nicola Larosa
 | 
|
2576  | 
    
 | 
|
2577  | 
    Reformatted final docstring in ReST format, indented it for easier folding
 | 
|
2578  | 
    
 | 
|
2579  | 
    Code tests converted to doctest format, and scattered them around
 | 
|
2580  | 
    in various docstrings
 | 
|
2581  | 
    
 | 
|
2582  | 
    Walk method rewritten using scalars and sections attributes
 | 
|
2583  | 
    
 | 
|
2584  | 
    2005/07/22
 | 
|
2585  | 
    ----------
 | 
|
2586  | 
    
 | 
|
2587  | 
    By Nicola Larosa
 | 
|
2588  | 
    
 | 
|
2589  | 
    Changed Validator and SimpleVal "test" methods to "check"
 | 
|
2590  | 
    
 | 
|
2591  | 
    More code cleanup
 | 
|
2592  | 
    
 | 
|
2593  | 
    2005/07/21
 | 
|
2594  | 
    ----------
 | 
|
2595  | 
    
 | 
|
2596  | 
    Changed Section.sequence to Section.scalars and Section.sections
 | 
|
2597  | 
    
 | 
|
2598  | 
    Added Section.configspec
 | 
|
2599  | 
    
 | 
|
2600  | 
    Sections in the root section now have no extra indentation
 | 
|
2601  | 
    
 | 
|
2602  | 
    Comments now better supported in Section and preserved by ConfigObj
 | 
|
2603  | 
    
 | 
|
2604  | 
    Comments also written out
 | 
|
2605  | 
    
 | 
|
2606  | 
    Implemented initial_comment and final_comment
 | 
|
2607  | 
    
 | 
|
2608  | 
    A scalar value after a section will now raise an error
 | 
|
2609  | 
    
 | 
|
2610  | 
    2005/07/20
 | 
|
2611  | 
    ----------
 | 
|
2612  | 
    
 | 
|
2613  | 
    Fixed a couple of bugs
 | 
|
2614  | 
    
 | 
|
2615  | 
    Can now pass a tuple instead of a list
 | 
|
2616  | 
    
 | 
|
2617  | 
    Simplified dict and walk methods
 | 
|
2618  | 
    
 | 
|
2619  | 
    Added __str__ to Section
 | 
|
2620  | 
    
 | 
|
2621  | 
    2005/07/10
 | 
|
2622  | 
    ----------
 | 
|
2623  | 
    
 | 
|
2624  | 
    By Nicola Larosa
 | 
|
2625  | 
    
 | 
|
2626  | 
    More code cleanup
 | 
|
2627  | 
    
 | 
|
2628  | 
    2005/07/08
 | 
|
2629  | 
    ----------
 | 
|
2630  | 
    
 | 
|
2631  | 
    The stringify option implemented. On by default.
 | 
|
2632  | 
    
 | 
|
2633  | 
    2005/07/07
 | 
|
2634  | 
    ----------
 | 
|
2635  | 
    
 | 
|
2636  | 
    Renamed private attributes with a single underscore prefix.
 | 
|
2637  | 
    
 | 
|
2638  | 
    Changes to interpolation - exceeding recursion depth, or specifying a
 | 
|
2639  | 
    missing value, now raise errors.
 | 
|
2640  | 
    
 | 
|
2641  | 
    Changes for Python 2.2 compatibility. (changed boolean tests - removed
 | 
|
2642  | 
    ``is True`` and ``is False``)
 | 
|
2643  | 
    
 | 
|
2644  | 
    Added test for duplicate section and member (and fixed bug)
 | 
|
2645  | 
    
 | 
|
2646  | 
    2005/07/06
 | 
|
2647  | 
    ----------
 | 
|
2648  | 
    
 | 
|
2649  | 
    By Nicola Larosa
 | 
|
2650  | 
    
 | 
|
2651  | 
    Code cleanup
 | 
|
2652  | 
    
 | 
|
2653  | 
    2005/07/02
 | 
|
2654  | 
    ----------
 | 
|
2655  | 
    
 | 
|
2656  | 
    Version 0.1.0
 | 
|
2657  | 
    
 | 
|
2658  | 
    Now properly handles values including comments and lists.
 | 
|
2659  | 
    
 | 
|
2660  | 
    Better error handling.
 | 
|
2661  | 
    
 | 
|
2662  | 
    String interpolation.
 | 
|
2663  | 
    
 | 
|
2664  | 
    Some options implemented.
 | 
|
2665  | 
    
 | 
|
2666  | 
    You can pass a Section a dictionary to initialise it.
 | 
|
2667  | 
    
 | 
|
2668  | 
    Setting a Section member to a dictionary will create a Section instance.
 | 
|
2669  | 
    
 | 
|
2670  | 
    2005/06/26
 | 
|
2671  | 
    ----------
 | 
|
2672  | 
    
 | 
|
2673  | 
    Version 0.0.1
 | 
|
2674  | 
    
 | 
|
2675  | 
    Experimental reader.
 | 
|
2676  | 
    
 | 
|
2677  | 
    A reasonably elegant implementation - a basic reader in 160 lines of code.
 | 
|
2678  | 
    
 | 
|
2679  | 
    *A programming language is a medium of expression.* - Paul Graham
 | 
|
2680  | 
"""
 | 
|
2681  |