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


UCC Code Repository

Contents of /bunnyblog/modules/configobj.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (hide annotations) (download) (as text)
Tue Jan 29 14:32:01 2008 UTC (12 years, 2 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 svn-admin 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