/[theodore]/bunnyblog/modules/configobj.py


UCC Code Repository

Contents of /bunnyblog/modules/configobj.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations) (download) (as text)
Tue Jan 29 14:32:01 2008 UTC (12 years, 4 months ago) by svn-admin
File MIME type: text/x-python
File size: 89640 byte(s)
Re-import of repository after repository database corruption.

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 140 2005-10-20 10:04:58Z 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

Managed by UCC Webmasters ViewVC Help
Powered by ViewVC 1.1.26